Mirror of https://github.com/roostorg/coop github.com/roostorg/coop
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 175 lines 6.8 kB view raw
1import { 2 type InsertQueryBuilder, 3 type Selection, 4 type SelectQueryBuilder, 5 type SelectType, 6} from 'kysely'; 7import { type IfAny, type Simplify, type UnionToIntersection } from 'type-fest'; 8 9import { type PickEach } from './typescript-types.js'; 10 11export const isUniqueViolationError = isPgErrorWithCode.bind(null, '23505'); 12export const isForeignKeyViolationError = isPgErrorWithCode.bind(null, '23503'); 13export const isNotNullViolationError = isPgErrorWithCode.bind(null, '23502'); 14export const isCheckViolationError = isPgErrorWithCode.bind(null, '23514'); 15 16// See https://github.com/postgres/postgres/blob/eb81e8e7902f63c4d292638edc8b7e92b766a692/src/backend/utils/errcodes.txt#L227 17function isPgErrorWithCode(code: string, error: unknown) { 18 return ( 19 typeof error === 'object' && 20 error !== null && 21 (error as { code?: unknown }).code === code 22 ); 23} 24 25type SelectionToAliasMap<T extends string> = Simplify< 26 UnionToIntersection< 27 T extends `${infer RawColumn} as ${infer Alias}` 28 ? { [K in RawColumn]: Alias } 29 : { [K in T]: K } 30 > 31>; 32 33type PickEachNoAliasing< 34 RowType, 35 SelectionType extends readonly string[], 36> = PickEach< 37 RowType, 38 keyof SelectionToAliasMap<SelectionType[number]> & keyof RowType 39>; 40 41type ApplyAliases< 42 UnaliasedRowType, 43 SelectionType extends readonly string[], 44> = UnaliasedRowType extends object 45 ? SelectionToAliasMap<SelectionType[number]> extends { 46 [k: string]: string; 47 } 48 ? ApplyAlias<UnaliasedRowType, SelectionToAliasMap<SelectionType[number]>> 49 : never 50 : never; 51 52type ApplyAlias<T extends object, AliasMap extends { [k: string]: string }> = { 53 [K in keyof T as AliasMap[K & keyof AliasMap]]: SelectType<T[K]>; 54}; 55 56/** 57 * Kysely represents the type of each row as an object type (with the ability to 58 * have different types per column on SELECT/INSERT/UPDATE). However, if certain 59 * fields have correlated types (e.g., if column `a` can be type `X | Y` and 60 * column `b` can be type `A | B`, but column `a` having type `X` implies column 61 * `b` must have type `Y`), kysely has no way to capture this in query results, 62 * because it computes the type of the returned selection (i.e., portion of a 63 * row) on a column-by-column basis. 64 * 65 * This type is solely designed to work around that problem. You give it: 66 * 67 * - RowTypeUnion: a type for your row which contains a union type to capture 68 * the correlation between fields; 69 * - SelectionType: a list of column names (optionally with aliases) matching 70 * exactly what you'd pass to `builder.select()` in kysely. This could also be 71 * a list of keys (with aliases and possibly partial) that are going to 72 * represent the row in an insert/update. 73 * - ConditionalSelectionType: a kysely query can have columns that are _only 74 * sometimes_ selected using $if() calls. This type should be a list of column 75 * names (optionally with aliases) matching the conditional selection. See 76 * https://github.com/kysely-org/kysely/blob/e4de7bb8f7f22ad5d7af72dfe0285eb7a85cdd9a/site/docs/recipes/conditional-selects.md 77 * - Mode: an indication of whether the produced type is supposed to represent 78 * the shape of the data as it's needed in an insert, update, or select. 79 * 80 * Then, it returns a union type, with only the keys in the selection and with 81 * their aliases applied, for the row. 82 */ 83export type FixKyselyRowCorrelation< 84 RowTypeUnion, 85 SelectionType extends readonly string[], 86 ConditionalSelectionType extends readonly string[] = [], 87> = Simplify< 88 ApplyAliases<PickEachNoAliasing<RowTypeUnion, SelectionType>, SelectionType> & 89 Partial< 90 ApplyAliases< 91 PickEachNoAliasing<RowTypeUnion, ConditionalSelectionType>, 92 ConditionalSelectionType 93 > 94 > 95>; 96 97// When a kysely select query includes a $if() call, the type of the 98// SelectQueryBuilder's third parameter, which reflects the shape of the query's 99// returned rows, is set by Kysely to be: 100// `MergePartial<RequiredSelection, SelectionWhenTheIfConditionIsTrue>`. 101// In the FixSingleTableSelectRowType, we need to extract these two selections, 102// so we duplicate Kysely's definition of MergePartial here so that we can use 103// it FixSingleTableSelectRowType. 104type MergePartial<T1, T2> = T1 & Partial<Omit<T2, keyof T1>>; 105 106/** 107 * A small abstraction over `FixKyselyRowCorrelation` that only works for SELECT 108 * queries on single tables (no joins; no subqueries) or INSERT queries with 109 * `RETURNING`, but that saves boilerplate in those cases, by taking the type of 110 * the whole `SelectQueryBuilder`/`InsertQueryBuilder` as it's only required 111 * parameter. 112 */ 113export type FixSingleTableReturnedRowType< 114 // prettier-ignore 115 Builder extends 116 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 117 SelectQueryBuilder<any, any, Selection<any, any, any>> 118 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 119 InsertQueryBuilder<any, any, Selection<any, any, any>>, 120 WhereClause = unknown, 121> = 122 // We need to destructure the two selections out of the MergePartial in order 123 // to properly track that the second set of columns are optional in the 124 // result. See `MergePartial` and https://github.com/roostorg/coop/pull/1248 125 Builder extends SelectQueryBuilder< 126 infer DB, 127 infer TB, 128 MergePartial< 129 // eslint-disable-next-line @typescript-eslint/no-explicit-any 130 infer Part1 extends Selection<any, any, any>, 131 // eslint-disable-next-line @typescript-eslint/no-explicit-any 132 infer Part2 extends Selection<any, any, any> 133 > 134 > 135 ? Part1 extends Selection<DB, TB, infer Sel1> 136 ? Part2 extends Selection<DB, TB, infer Sel2> 137 ? FixKyselyRowCorrelation< 138 DB[TB] & WhereClause, 139 readonly (Sel1 & string)[], 140 IfAny<Sel2, readonly [], readonly (Sel2 & string)[]> 141 > 142 : never 143 : never 144 : Builder extends 145 | SelectQueryBuilder< 146 infer DB, 147 infer TB, 148 Selection<infer DB, infer TB, infer SelectionType> 149 > 150 | InsertQueryBuilder< 151 infer DB, 152 infer TB, 153 Selection<infer DB, infer TB, infer SelectionType> 154 > 155 ? FixKyselyRowCorrelation< 156 DB[TB] & WhereClause, 157 readonly (SelectionType & string)[], 158 [] 159 > 160 : never; 161 162/** 163 * Creates an object with a key, where the type of the value for that key 164 * excludes a given value. 165 * 166 * E.g., `Excluding<{ a: 'foo' | 'bar' }, 'a', 'foo'>` is `{ a: 'bar' }`. 167 * 168 * K can be a union type to exclude V from all keys in the union. 169 * 170 * Similarly, V can be a union type to exclude all values in the union, assuming 171 * that the values being excluded are legal values for every given key. 172 */ 173export type Excluding<O extends object, K extends keyof O, V extends O[K]> = { 174 [K2 in K]: Exclude<O[K2], V>; 175};