Last active
April 24, 2025 17:35
-
-
Save oliveryasuna/2197a5a8951236adaf265ed001f2db93 to your computer and use it in GitHub Desktop.
Kysely Repository (incomplete)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type {AnyTable} from './types'; | |
import type {ReferenceExpression} from 'kysely'; | |
const UNARY_COMPARISON_OPERATORS_COMMON = (['is null', 'is not null'] as const); | |
const UNARY_COMPARISON_OPERATORS_BOOLEAN_ONLY = (['is true', 'is not true', 'is false', 'is not false', 'is unknown', 'is not unknown'] as const); | |
const UNARY_COMPARISON_OPERATORS = [...UNARY_COMPARISON_OPERATORS_COMMON, ...UNARY_COMPARISON_OPERATORS_BOOLEAN_ONLY] as const; | |
type UnaryComparisonOperatorCommon = (typeof UNARY_COMPARISON_OPERATORS_COMMON[number]); | |
type UnaryComparisonOperatorBooleanOnly = (typeof UNARY_COMPARISON_OPERATORS_BOOLEAN_ONLY[number]); | |
type UnaryComparisonOperator<T> = (T extends boolean | |
? (UnaryComparisonOperatorCommon | UnaryComparisonOperatorBooleanOnly) | |
: UnaryComparisonOperatorCommon); | |
const BINARY_COMPARISON_OPERATORS = (['<', '>', '<=', '>=', '=', '<>', 'is distinct from', 'is not distinct from'] as const); | |
type BinaryComparisonOperator = (typeof BINARY_COMPARISON_OPERATORS[number]); | |
// type BinaryComparisonInOperator = ('in' | 'not in'); | |
const TERTIARY_COMPARISON_OPERATORS = (['between', 'not between', 'between symmetric', 'not between symmetric'] as const); | |
type TertiaryComparisonOperator = (typeof TERTIARY_COMPARISON_OPERATORS[number]); | |
const PATTERN_MATCHING_OPERATORS = (['like', 'not like', 'ilike', 'not ilike', 'similar to', 'not similar to'] as const); | |
type PatternMatchingOperator = (typeof PATTERN_MATCHING_OPERATORS[number]); | |
// const ARRAY_OPERATORS = (['@>', '<@', '&&'] as const); | |
// | |
// type ArrayOperator = (typeof ARRAY_OPERATORS[number]); | |
type Condition<DB, TB extends AnyTable<DB>, CL extends (((keyof TB) & ReferenceExpression<DB, TB> & string))> = ( | |
// Comparison operators | |
//-------------------------------------------------- | |
// Unary | |
UnaryComparisonOperator<TB[CL]> | |
| {operator: UnaryComparisonOperator<TB[CL]>;} | |
// Binary | |
| { | |
operator: BinaryComparisonOperator; | |
value: TB[CL]; | |
} | |
| [BinaryComparisonOperator, TB[CL]] | |
// TODO: Support `in`. | |
// // Binary In | |
// | ( | |
// TB[CL] extends [] | |
// ? never | |
// : ( | |
// | {operator: BinaryComparisonInOperator; value: TB[CL][];} | |
// | [BinaryComparisonInOperator, TB[CL][]] | |
// ) | |
// ) | |
// Tertiary | |
| { | |
operator: TertiaryComparisonOperator; | |
value1: TB[CL]; | |
value2: TB[CL]; | |
} | |
| [TertiaryComparisonOperator, TB[CL], TB[CL]] | |
| [TertiaryComparisonOperator, [TB[CL], TB[CL]]] | |
// Pattern matching operators | |
//-------------------------------------------------- | |
| (TB[CL] extends string | |
? ( | |
| { | |
operator: PatternMatchingOperator; | |
value: TB[CL]; | |
} | |
| [PatternMatchingOperator, TB[CL]] | |
) | |
: never) | |
// TODO: Support array operators. | |
// // Array operators | |
// //-------------------------------------------------- | |
// | |
// | ( | |
// TB[CL] extends [] | |
// ? ( | |
// | {operator: ArrayOperator; value: TB[CL];} | |
// | [ArrayOperator, TB[CL]] | |
// ) | |
// : never | |
// ) | |
); | |
// TODO: This does not work with aliases. | |
type Conditions<DB, TB extends AnyTable<DB>> = { | |
[CL in ((keyof TB) & ReferenceExpression<DB, TB> & string)]?: Condition<DB, TB, CL>; | |
}; | |
export type { | |
UnaryComparisonOperatorCommon, | |
UnaryComparisonOperatorBooleanOnly, | |
UnaryComparisonOperator, | |
BinaryComparisonOperator, | |
TertiaryComparisonOperator, | |
PatternMatchingOperator, | |
Condition, | |
Conditions | |
}; | |
export { | |
UNARY_COMPARISON_OPERATORS_COMMON, | |
UNARY_COMPARISON_OPERATORS_BOOLEAN_ONLY, | |
UNARY_COMPARISON_OPERATORS, | |
BINARY_COMPARISON_OPERATORS, | |
TERTIARY_COMPARISON_OPERATORS, | |
PATTERN_MATCHING_OPERATORS | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { | |
AnyColumn, | |
DeleteResult, | |
ExpressionBuilder, | |
InsertObject, | |
OnConflictBuilder, | |
OnConflictDatabase, | |
OnConflictTables, | |
OnConflictUpdateBuilder, | |
OperandExpression, | |
ReferenceExpression, | |
SqlBool, | |
UpdateObject, | |
UpdateResult | |
} from 'kysely'; | |
import {Kysely, sql} from 'kysely'; | |
import type {ExtractKyselySelection, Returning} from './returning'; | |
import type {SimplifyResult, SimplifySingleResult} from 'kysely/dist/cjs/util/type-utils'; | |
import type {Upsert} from './upsert'; | |
import type {OnConflictDoNothingBuilder} from 'kysely/dist/cjs/query-builder/on-conflict-builder'; | |
import type {InsertResult} from 'kysely/dist/cjs/query-builder/insert-result'; | |
import type {AllSelection} from 'kysely/dist/cjs/parser/select-parser'; | |
import type {AnySimpleColumn, AnyTable, OperandExpressionFactory} from './types'; | |
import type {Condition, Conditions} from './conditions'; | |
import {BINARY_COMPARISON_OPERATORS, PATTERN_MATCHING_OPERATORS, TERTIARY_COMPARISON_OPERATORS, UNARY_COMPARISON_OPERATORS} from './conditions'; | |
import type {ExtractTableAlias} from 'kysely/dist/cjs/parser/table-parser'; | |
class Repository<DB, TB extends ((keyof DB) & string)> { | |
// Properties | |
//-------------------------------------------------- | |
protected _tableName: TB; | |
// Constructor | |
//-------------------------------------------------- | |
public constructor(tableName: TB) { | |
this._tableName = tableName; | |
} | |
// Methods | |
//-------------------------------------------------- | |
// Create | |
// | |
public insertOne(db: Kysely<DB>, value: InsertObject<DB, TB>): Promise<SimplifySingleResult<InsertResult>>; | |
public insertOne<Ret extends Returning<DB, TB>>( | |
db: Kysely<DB>, | |
value: InsertObject<DB, TB>, | |
returning: Ret | |
): Promise<SimplifySingleResult<ExtractKyselySelection<DB, TB, Ret>>>; | |
public insertOne<Ret extends Returning<DB, TB>>( | |
db: Kysely<DB>, | |
value: InsertObject<DB, TB>, | |
returning?: Ret | |
): (Promise<SimplifySingleResult<InsertResult> | SimplifySingleResult<ExtractKyselySelection<DB, TB, Ret>>>) { | |
// TODO: Could use `$if`. | |
if(returning) { | |
return (db | |
.insertInto(this._tableName) | |
.values(value) | |
.returning(this._normalizeReturning(returning)) | |
.executeTakeFirst() as (Promise<SimplifySingleResult<InsertResult> | SimplifySingleResult<ExtractKyselySelection<DB, TB, Ret>>>)); | |
} else { | |
return db | |
.insertInto(this._tableName) | |
.values(value) | |
.executeTakeFirst(); | |
} | |
} | |
public insertOneUpsert(db: Kysely<DB>, value: InsertObject<DB, TB>, upsert: Upsert<DB, TB>): Promise<SimplifySingleResult<InsertResult>>; | |
public insertOneUpsert<Ret extends Returning<DB, TB>>( | |
db: Kysely<DB>, | |
value: InsertObject<DB, TB>, | |
upsert: Upsert<DB, TB>, | |
returning: Ret | |
): Promise<SimplifySingleResult<ExtractKyselySelection<DB, TB, Ret>>>; | |
public insertOneUpsert<Ret extends Returning<DB, TB>>( | |
db: Kysely<DB>, | |
value: InsertObject<DB, TB>, | |
upsert: Upsert<DB, TB>, | |
returning?: Ret | |
): (Promise<SimplifySingleResult<InsertResult> | SimplifySingleResult<ExtractKyselySelection<DB, TB, Ret>>>) { | |
const onConflict = this._normalizeUpsert(upsert); | |
// TODO: Could use `$if`. | |
if(returning) { | |
return (db | |
.insertInto(this._tableName) | |
.values(value) | |
.onConflict(onConflict) | |
.returning(this._normalizeReturning(returning)) | |
.executeTakeFirst() as (Promise<SimplifySingleResult<InsertResult> | SimplifySingleResult<ExtractKyselySelection<DB, TB, Ret>>>)); | |
} else { | |
return db | |
.insertInto(this._tableName) | |
.values(value) | |
.onConflict(onConflict) | |
.executeTakeFirst(); | |
} | |
} | |
public insertAll(db: Kysely<DB>, values: InsertObject<DB, TB>[]): Promise<SimplifyResult<InsertResult>[]>; | |
public insertAll<Ret extends Returning<DB, TB>>( | |
db: Kysely<DB>, | |
values: InsertObject<DB, TB>[], | |
returning: Ret | |
): Promise<SimplifyResult<ExtractKyselySelection<DB, TB, Ret>>[]>; | |
public insertAll<Ret extends Returning<DB, TB>>( | |
db: Kysely<DB>, | |
values: InsertObject<DB, TB>[], | |
returning?: Ret | |
): (Promise<SimplifyResult<InsertResult>[] | SimplifyResult<ExtractKyselySelection<DB, TB, Ret>>[]>) { | |
// TODO: Could use `$if`. | |
if(returning) { | |
return (db | |
.insertInto(this._tableName) | |
.values(values) | |
.returning(this._normalizeReturning(returning)) | |
.execute() as (Promise<SimplifyResult<InsertResult>[] | SimplifyResult<ExtractKyselySelection<DB, TB, Ret>>[]>)); | |
} else { | |
return db | |
.insertInto(this._tableName) | |
.values(values) | |
.execute(); | |
} | |
} | |
public insertAllUpsert(db: Kysely<DB>, values: InsertObject<DB, TB>[], upsert: Upsert<DB, TB>): Promise<SimplifyResult<InsertResult>[]>; | |
public insertAllUpsert<Ret extends Returning<DB, TB>>( | |
db: Kysely<DB>, | |
values: InsertObject<DB, TB>[], | |
upsert: Upsert<DB, TB>, | |
returning: Ret | |
): Promise<SimplifyResult<ExtractKyselySelection<DB, TB, Ret>>[]>; | |
public insertAllUpsert<Ret extends Returning<DB, TB>>( | |
db: Kysely<DB>, | |
values: InsertObject<DB, TB>[], | |
upsert: Upsert<DB, TB>, | |
returning?: Ret | |
): (Promise<SimplifyResult<InsertResult>[] | SimplifyResult<ExtractKyselySelection<DB, TB, Ret>>[]>) { | |
const onConflict = this._normalizeUpsert(upsert); | |
// TODO: Could use `$if`. | |
if(returning) { | |
return (db | |
.insertInto(this._tableName) | |
.values(values) | |
.onConflict(onConflict) | |
.returning(this._normalizeReturning(returning)) | |
.execute() as (Promise<SimplifyResult<InsertResult>[] | SimplifyResult<ExtractKyselySelection<DB, TB, Ret>>[]>)); | |
} else { | |
return db | |
.insertInto(this._tableName) | |
.values(values) | |
.onConflict(onConflict) | |
.execute(); | |
} | |
} | |
// Read | |
// | |
public selectOne(db: Kysely<DB>, conditions: Conditions<DB, ExtractTableAlias<DB, TB>>): Promise<SimplifySingleResult<AllSelection<DB, TB>>>; | |
public selectOne<Ret extends Returning<DB, ExtractTableAlias<DB, TB>>>( | |
db: Kysely<DB>, | |
conditions: Conditions<DB, ExtractTableAlias<DB, TB>>, | |
values: Ret | |
): Promise<SimplifySingleResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>>>; | |
public selectOne<Ret extends Returning<DB, ExtractTableAlias<DB, TB>>>( | |
db: Kysely<DB>, | |
conditions: Conditions<DB, ExtractTableAlias<DB, TB>>, | |
values?: Ret | |
): Promise<SimplifySingleResult<AllSelection<DB, ExtractTableAlias<DB, TB>>> | SimplifySingleResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>>> { | |
const where = this._normalizeConditions(conditions); | |
// TODO: Could use `$if`. | |
if(values) { | |
return (db | |
.selectFrom(this._tableName) | |
.select(this._normalizeReturning(values)) | |
.where(where) | |
.executeTakeFirst() as Promise<SimplifySingleResult<AllSelection<DB, ExtractTableAlias<DB, TB>>> | SimplifySingleResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>>>); | |
} else { | |
return (db | |
.selectFrom(this._tableName) | |
.selectAll() | |
.where(where) | |
.executeTakeFirst() as Promise<SimplifySingleResult<AllSelection<DB, ExtractTableAlias<DB, TB>>>>); | |
} | |
} | |
public selectAll(db: Kysely<DB>, conditions: Conditions<DB, ExtractTableAlias<DB, TB>>): Promise<SimplifyResult<AllSelection<DB, TB>[]>>; | |
public selectAll<Ret extends Returning<DB, ExtractTableAlias<DB, TB>>>( | |
db: Kysely<DB>, | |
conditions: Conditions<DB, ExtractTableAlias<DB, TB>>, | |
values: Ret | |
): Promise<SimplifyResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>[]>>; | |
public selectAll<Ret extends Returning<DB, ExtractTableAlias<DB, TB>>>( | |
db: Kysely<DB>, | |
conditions: Conditions<DB, ExtractTableAlias<DB, TB>>, | |
values?: Ret | |
): Promise<SimplifyResult<AllSelection<DB, ExtractTableAlias<DB, TB>>[]> | SimplifyResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>[]>> { | |
const where = this._normalizeConditions(conditions); | |
// TODO: Could use `$if`. | |
if(values) { | |
return (db | |
.selectFrom(this._tableName) | |
.select(this._normalizeReturning(values)) | |
.where(where) | |
.execute() as Promise<SimplifyResult<AllSelection<DB, ExtractTableAlias<DB, TB>>[]> | SimplifyResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>[]>>); | |
} else { | |
return (db | |
.selectFrom(this._tableName) | |
.selectAll() | |
.where(where) | |
.execute() as Promise<SimplifyResult<AllSelection<DB, ExtractTableAlias<DB, TB>>[]>>); | |
} | |
} | |
public count(db: Kysely<DB>): Promise<number>; | |
public count(db: Kysely<DB>, conditions: Conditions<DB, ExtractTableAlias<DB, TB>>): Promise<number>; | |
public count(db: Kysely<DB>, conditions?: Conditions<DB, ExtractTableAlias<DB, TB>>): Promise<number> { | |
// TODO: Could use `$if`. | |
if(conditions) { | |
return (db | |
.selectFrom(this._tableName) | |
.select(db.fn.countAll<number>().as('count')) | |
.where(this._normalizeConditions(conditions)) | |
.executeTakeFirstOrThrow() | |
// TODO: Why `any`? | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access | |
.then((result: any): number => result.count)); | |
} else { | |
return (db | |
.selectFrom(this._tableName) | |
.select(db.fn.countAll<number>().as('count')) | |
.executeTakeFirstOrThrow() | |
// TODO: Why `any`? | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access | |
.then((result: any): number => result.count)); | |
} | |
} | |
public exists(db: Kysely<DB>): Promise<boolean>; | |
public exists(db: Kysely<DB>, conditions: Conditions<DB, ExtractTableAlias<DB, TB>>): Promise<boolean>; | |
public exists(db: Kysely<DB>, conditions?: Conditions<DB, ExtractTableAlias<DB, TB>>): Promise<boolean> { | |
// TODO: Could use `$if`. | |
if(conditions === undefined) { | |
return (db | |
.selectFrom(this._tableName) | |
.select(eb => eb.lit(true).as('exists')) | |
.limit(1) | |
.executeTakeFirst() | |
// TODO: Why `any`? | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
.then((result: any): boolean => (result !== undefined))); | |
} else { | |
return (db | |
.selectFrom(this._tableName) | |
.select(eb => eb.lit(true).as('exists')) | |
.where(this._normalizeConditions(conditions)) | |
.limit(1) | |
.executeTakeFirst() | |
// TODO: Why `any`? | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
.then((result: any): boolean => (result !== undefined))); | |
} | |
} | |
// Update | |
// | |
public update(db: Kysely<DB>, conditions: Conditions<DB, ExtractTableAlias<DB, TB>>, value: UpdateObject<DB, ExtractTableAlias<DB, TB>>): Promise<UpdateResult[]>; | |
public update<Ret extends Returning<DB, ExtractTableAlias<DB, TB>>>( | |
db: Kysely<DB>, | |
conditions: Conditions<DB, ExtractTableAlias<DB, TB>>, | |
value: UpdateObject<DB, ExtractTableAlias<DB, TB>>, | |
returning: Ret | |
): Promise<SimplifySingleResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>>>; | |
public update<Ret extends Returning<DB, ExtractTableAlias<DB, TB>>>( | |
db: Kysely<DB>, | |
conditions: Conditions<DB, ExtractTableAlias<DB, TB>>, | |
value: UpdateObject<DB, ExtractTableAlias<DB, TB>>, | |
returning?: Ret | |
): Promise<UpdateResult[] | SimplifySingleResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>>> { | |
const where = this._normalizeConditions(conditions); | |
// TODO: Could use `$if`. | |
if(returning) { | |
return (db | |
.updateTable(this._tableName) | |
.set(value) | |
.where(where) | |
.returning(this._normalizeReturning(returning)) | |
.execute() as Promise<UpdateResult[] | SimplifySingleResult<ExtractKyselySelection<DB, ExtractTableAlias<DB, TB>, Ret>>>); | |
} else { | |
return db | |
.updateTable(this._tableName) | |
.set(value) | |
.where(where) | |
.execute(); | |
} | |
} | |
// Delete | |
// | |
public delete(db: Kysely<DB>): Promise<DeleteResult[]>; | |
public delete(db: Kysely<DB>, conditions: Conditions<DB, ExtractTableAlias<DB, TB>>): Promise<DeleteResult[]>; | |
public delete(db: Kysely<DB>, conditions?: Conditions<DB, ExtractTableAlias<DB, TB>>): Promise<DeleteResult[]> { | |
// TODO: Could use `$if`. | |
if(conditions) { | |
return db | |
.deleteFrom(this._tableName) | |
.where(this._normalizeConditions(conditions)) | |
.execute(); | |
} else { | |
return db | |
.deleteFrom(this._tableName) | |
.execute(); | |
} | |
} | |
// Helpers | |
// | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
protected _resolveReturning<DB, TB extends AnyTable<DB>>(input: (Kysely<any> | Returning<DB, TB>)): Returning<DB, TB> { | |
if(input instanceof Kysely) { | |
return []; | |
} | |
return input; | |
} | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
protected _resolveConditions<DB, TB extends AnyTable<DB>>(input: (Kysely<any> | Conditions<DB, TB>)): Conditions<DB, TB> { | |
if(input instanceof Kysely) { | |
return {}; | |
} | |
return input; | |
} | |
protected _normalizeReturning<DB, TB extends AnyTable<DB>>(returning: Returning<DB, TB>): AnySimpleColumn<DB, TB>[] { | |
if(Array.isArray(returning)) { | |
return returning; | |
} | |
const result: AnySimpleColumn<DB, TB>[] = []; | |
for(const [columnName, value] of Object.entries(returning)) { | |
if((typeof value === 'boolean') && value) { | |
result.push(columnName as AnySimpleColumn<DB, TB>); | |
} else if(typeof value === 'string') { | |
result.push(`${columnName} as ${value}` as AnySimpleColumn<DB, TB>); | |
} | |
} | |
return result; | |
} | |
protected _normalizeUpsert( | |
upsert: Upsert<DB, TB> | |
): ((b: OnConflictBuilder<DB, TB>) => (OnConflictUpdateBuilder<OnConflictDatabase<DB, TB>, OnConflictTables<TB>> | OnConflictDoNothingBuilder<DB, TB>)) { | |
if(typeof upsert === 'function') { | |
// Callback function for `onConflict`. | |
return upsert; | |
} | |
return ((b: OnConflictBuilder<DB, TB>): (OnConflictUpdateBuilder<OnConflictDatabase<DB, TB>, OnConflictTables<TB>> | OnConflictDoNothingBuilder<DB, TB>) => { | |
const conflictColumns: AnyColumn<DB, TB>[] = (Array.isArray(upsert) | |
// `UpsertArray`. | |
// eslint-disable-next-line @typescript-eslint/no-misused-spread | |
? ([...upsert[0]] as AnyColumn<DB, TB>[]) | |
// `UpsertObject`. | |
// eslint-disable-next-line @typescript-eslint/no-misused-spread | |
: ([...upsert.conflict] as AnyColumn<DB, TB>[])); | |
const updateColumns: AnyColumn<DB, TB>[] = (Array.isArray(upsert) | |
// `UpsertArray`. | |
// eslint-disable-next-line @typescript-eslint/no-misused-spread | |
? ([...upsert[1]] as AnyColumn<DB, TB>[]) | |
// `UpsertObject`. | |
// eslint-disable-next-line @typescript-eslint/no-misused-spread | |
: ([...upsert.update] as AnyColumn<DB, TB>[])); | |
if(updateColumns.length === 0) { | |
return b.doNothing(); | |
} | |
return b | |
.columns(conflictColumns) | |
// TODO: This is the suggested way. | |
// See https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#onConflict. | |
// .doUpdateSet(updateColumns.reduce( | |
// (( | |
// updateObj: UpdateObject<OnConflictDatabase<Database, Table>, OnConflictTables<Table>, OnConflictTables<Table>>, | |
// columnName | |
// ): UpdateObject<OnConflictDatabase<Database, Table>, OnConflictTables<Table>, OnConflictTables<Table>> => { | |
// (updateObj as any)[columnName] = (( | |
// eb: ExpressionBuilder<OnConflictDatabase<Database, Table>, OnConflictTables<Table>> | |
// ): OperandExpression<UpdateType<OnConflictDatabase<Database, Table>[OnConflictTables<Table>][any]>> => eb.ref(`excluded.${columnName}` as any)); | |
// | |
// return updateObj; | |
// }), | |
// {} | |
// )); | |
.doUpdateSet(( | |
b2: ExpressionBuilder<OnConflictDatabase<DB, TB>, OnConflictTables<TB>> | |
): UpdateObject<OnConflictDatabase<DB, TB>, OnConflictTables<TB>, OnConflictTables<TB>> => | |
updateColumns.reduce( | |
(( | |
result: UpdateObject<OnConflictDatabase<DB, TB>, OnConflictTables<TB>, OnConflictTables<TB>>, | |
key: AnyColumn<DB, TB> | |
): UpdateObject<OnConflictDatabase<DB, TB>, OnConflictTables<TB>, OnConflictTables<TB>> => { | |
// TODO: Why `as any`? | |
// TODO: Why `as any`? | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access | |
(result as any)[key] = b2.ref(`excluded.${key}` as any); | |
return result; | |
}), | |
{} | |
)); | |
}); | |
} | |
protected _normalizeConditions<DB, TB extends AnyTable<DB>>(condition: Conditions<DB, TB>): OperandExpressionFactory<DB, TB, SqlBool> { | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
const entries: [(keyof Conditions<DB, TB>), Condition<DB, TB, any>][] = (Object.entries(condition) as any); | |
if(entries.length === 0) { | |
// TODO: Test this. | |
return ((eb: ExpressionBuilder<DB, TB>): OperandExpression<SqlBool> => eb.val(true)); | |
} | |
return ((eb: ExpressionBuilder<DB, TB>): OperandExpression<SqlBool> => | |
eb.and(entries.reduce( | |
((result: OperandExpression<SqlBool>[], [columnName, condition]): OperandExpression<SqlBool>[] => { | |
result.push(this._normalizeCondition(columnName, condition)); | |
return result; | |
}), | |
[] | |
))); | |
} | |
// TODO: Are all of these usages of `sql` vulnerable to SQL injection? | |
protected _normalizeCondition<CL extends ((keyof TB) & ReferenceExpression<DB, TB> & string)>(columnName: CL, condition: Condition<DB, TB, CL>): OperandExpression<SqlBool> { | |
if(typeof condition === 'string') { | |
// Unary comparison operator. | |
return sql<SqlBool>`(${sql.ref(columnName)} ${sql.raw(condition)})`; | |
// TODO: Why `as any`? | |
} else if(typeof condition === 'object') { | |
if(Array.isArray(condition)) { | |
const operator = condition[0]; | |
if(BINARY_COMPARISON_OPERATORS.includes(operator) || PATTERN_MATCHING_OPERATORS.includes(operator)) { | |
// Binary comparison operator or pattern matching operator. | |
return sql<SqlBool>`(${sql.ref(columnName)} ${sql.raw(operator)} ${sql.val(condition[1])})`; | |
} else if(TERTIARY_COMPARISON_OPERATORS.includes(operator)) { | |
// Tertiary comparison operator. | |
const [value1, value2] = (Array.isArray(condition[1]) ? condition[1] : [condition[1], condition[2]]); | |
return sql<SqlBool>`(${sql.ref(columnName)} ${sql.raw(operator)} ${sql.val(value1)} and ${sql.val(value2)})`; | |
} else { | |
throw (new Error(`Unknown operator: ${operator}`)); | |
} | |
} else { | |
const operator = condition.operator; | |
if(UNARY_COMPARISON_OPERATORS.includes(operator)) { | |
// Unary comparison operator. | |
return sql<SqlBool>`(${sql.ref(columnName)} ${sql.raw(operator)})`; | |
} else if(BINARY_COMPARISON_OPERATORS.includes(operator) || PATTERN_MATCHING_OPERATORS.includes(operator)) { | |
// Binary comparison operator or pattern matching operator. | |
return sql<SqlBool>`(${sql.ref(columnName)} ${sql.raw(operator)} ${sql.val((condition as {value: TB[CL];}).value)})`; | |
} else if(TERTIARY_COMPARISON_OPERATORS.includes(operator)) { | |
// Tertiary comparison operator. | |
const {value1, value2} = (condition as { | |
value1: TB[CL]; | |
value2: TB[CL]; | |
}); | |
return sql<SqlBool>`(${sql.ref(columnName)} ${sql.raw(operator)} ${sql.val(value1)} and ${sql.val(value2)})`; | |
} else { | |
throw (new Error(`Unknown operator: ${operator}`)); | |
} | |
} | |
} | |
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
throw (new Error(`Unknown condition: ${condition}`)); | |
} | |
} | |
export { | |
Repository | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type {Selection} from 'kysely'; | |
import type {AnySimpleColumn, AnyTable} from './types'; | |
type ReturningObject<DB, TB extends AnyTable<DB>> = { | |
[Col in AnySimpleColumn<DB, TB>]?: (boolean | string); | |
}; | |
type Returning<DB, TB extends AnyTable<DB>> = (AnySimpleColumn<DB, TB>[] | ReturningObject<DB, TB>); | |
type ReturningObjectToArray<DB, TB extends AnyTable<DB>, RetObj extends ReturningObject<DB, TB>> = { | |
[CL in (keyof RetObj)]: (RetObj[CL] extends true | |
? (CL & string) & AnySimpleColumn<DB, TB> | |
: (RetObj[CL] extends string | |
? `${CL & string} as ${RetObj[CL]}` & AnySimpleColumn<DB, TB> | |
: never)); | |
}[keyof RetObj][]; | |
type ExtractKyselySelection<DB, TB extends AnyTable<DB>, Ret extends Returning<DB, TB>> = | |
(Ret extends AnySimpleColumn<DB, TB>[] | |
? Selection<DB, TB, Ret> | |
: (Ret extends ReturningObject<DB, TB> | |
? ExtractKyselySelection<DB, TB, ReturningObjectToArray<DB, TB, Ret>> | |
: never)); | |
export type { | |
ReturningObject, | |
Returning, | |
ReturningObjectToArray, | |
ExtractKyselySelection | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type {AnyAliasedColumn, AnyAliasedColumnWithTable, AnyColumnWithTable} from 'kysely/dist/cjs/util/type-utils'; | |
import type {AnyColumn, ExpressionBuilder, OperandExpression} from 'kysely'; | |
type AnyTable<DB> = ({ | |
[TB in (keyof DB)]: TB; | |
}[keyof DB] & string); | |
type AnySimpleColumnUnaliased<DB, TB extends AnyTable<DB>> = (AnyColumn<DB, TB> | AnyColumnWithTable<DB, TB>); | |
type AnySimpleColumnAliased<DB, TB extends AnyTable<DB>> = (AnyAliasedColumn<DB, TB> | AnyAliasedColumnWithTable<DB, TB>); | |
type AnySimpleColumn<DB, TB extends AnyTable<DB>> = (AnySimpleColumnUnaliased<DB, TB> | AnySimpleColumnAliased<DB, TB>); | |
// Not exported from Kysely. | |
type OperandExpressionFactory<DB, TB extends (keyof DB), V> = ((eb: ExpressionBuilder<DB, TB>) => OperandExpression<V>); | |
export type { | |
AnyTable, | |
AnySimpleColumnUnaliased, | |
AnySimpleColumnAliased, | |
AnySimpleColumn, | |
OperandExpressionFactory | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type {AnyColumn, OnConflictBuilder, OnConflictDatabase, OnConflictTables, OnConflictUpdateBuilder} from 'kysely'; | |
import type {OnConflictDoNothingBuilder} from 'kysely/dist/cjs/query-builder/on-conflict-builder'; | |
import type {AnyTable} from './types'; | |
interface UpsertObject<DB, TB extends AnyTable<DB>> { | |
conflict: (AnyColumn<DB, TB> | AnyColumn<DB, TB>[]); | |
update: (AnyColumn<DB, TB> | AnyColumn<DB, TB>[]); | |
} | |
type UpsertArray<DB, TB extends AnyTable<DB>> = [ | |
(AnyColumn<DB, TB> | AnyColumn<DB, TB>[]), | |
(AnyColumn<DB, TB> | AnyColumn<DB, TB>[]) | |
]; | |
type Upsert<DB, TB extends AnyTable<DB>> = ( | |
((builder: OnConflictBuilder<DB, TB>) => (OnConflictUpdateBuilder<OnConflictDatabase<DB, TB>, OnConflictTables<TB>> | OnConflictDoNothingBuilder<DB, TB>)) | |
| UpsertObject<DB, TB> | |
| UpsertArray<DB, TB> | |
); | |
export type { | |
UpsertObject, | |
UpsertArray, | |
Upsert | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment