import { type AliasableExpression, type ColumnBuilderCallback, type ColumnDataType, type Compilable, type CompiledQuery, type DatabaseIntrospector, type Dialect, type Kysely, type KyselyPlugin, type OnModifyForeignAction, type QueryResult, type SelectQueryBuilder, type SelectQueryNode, sql, } from "kysely"; import type { jsonArrayFrom, jsonBuildObject, jsonObjectFrom } from "kysely/helpers/sqlite"; import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector"; import type { DB } from "bknd"; import type { Constructor } from "core/registry/Registry"; import { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner"; import type { Field } from "data/fields/Field"; export type QB = SelectQueryBuilder; export type IndexMetadata = { name: string; table: string; isUnique: boolean; columns: { name: string; order: number }[]; }; export interface SelectQueryBuilderExpression extends AliasableExpression { get isSelectQueryBuilder(): true; toOperationNode(): SelectQueryNode; } export type SchemaResponse = [string, ColumnDataType, ColumnBuilderCallback] | undefined; export const FieldSpecTypes = [ "text", "integer", "real", "blob", "date", "datetime", "timestamp", "boolean", "json", ] as const; export type FieldSpec = { type: (typeof FieldSpecTypes)[number]; name: string; nullable?: boolean; dflt?: any; unique?: boolean; primary?: boolean; references?: string; onDelete?: OnModifyForeignAction; onUpdate?: OnModifyForeignAction; }; export type IndexSpec = { name: string; columns: string[]; unique?: boolean; }; export type DbFunctions = { jsonObjectFrom: typeof jsonObjectFrom; jsonArrayFrom: typeof jsonArrayFrom; jsonBuildObject: typeof jsonBuildObject; }; export type ConnQuery = CompiledQuery | Compilable; export type ConnQueryResult = T extends CompiledQuery ? QueryResult : T extends Compilable ? QueryResult : never; export type ConnQueryResults = { [K in keyof T]: ConnQueryResult; }; const CONN_SYMBOL = Symbol.for("bknd:connection"); export type Features = { batching: boolean; softscans: boolean; }; export abstract class Connection { abstract name: string; protected initialized = false; protected pluginRunner: KyselyPluginRunner; protected readonly supported: Partial = { batching: false, softscans: true, }; kysely: Kysely; client!: Client; constructor( kysely: Kysely, public fn: Partial = {}, protected plugins: KyselyPlugin[] = [], ) { this.kysely = kysely; this[CONN_SYMBOL] = true; this.pluginRunner = new KyselyPluginRunner(plugins); } // @todo: consider moving constructor logic here, required by sqlocal async init(): Promise { this.initialized = true; } /** * This is a helper function to manage Connection classes * coming from different places * @param conn */ static isConnection(conn: unknown): conn is Connection { if (!conn) return false; return conn[CONN_SYMBOL] === true; } getIntrospector(): BaseIntrospector { return this.kysely.introspection as any; } supports(feature: keyof typeof this.supported): boolean { return this.supported[feature] ?? false; } async ping(): Promise { const res = await sql`SELECT 1`.execute(this.kysely); return res.rows.length > 0; } protected async transformResultRows(result: any[]): Promise { return await this.pluginRunner.transformResultRows(result); } /** * Execute a query and return the result including all metadata * returned from the dialect. */ async executeQueries(...qbs: O): Promise> { return Promise.all(qbs.map(async (qb) => await this.kysely.executeQuery(qb))) as any; } async executeQuery(qb: O): Promise> { const res = await this.executeQueries(qb); return res[0] as any; } protected getCompiled(...qbs: ConnQuery[]): CompiledQuery[] { return qbs.map((qb) => { if ("compile" in qb) { return qb.compile(); } return qb; }); } protected async withTransformedRows< Key extends string = "rows", O extends { [K in Key]: any[] }[] = [], >(result: O, _key?: Key): Promise { return (await Promise.all( result.map(async (row) => { const key = _key ?? "rows"; const { [key]: rows, ...r } = row; return { ...r, rows: await this.transformResultRows(rows), }; }), )) as any; } protected validateFieldSpecType(type: string): type is FieldSpec["type"] { if (!FieldSpecTypes.includes(type as any)) { throw new Error( `Invalid field type "${type}". Allowed types are: ${FieldSpecTypes.join(", ")}`, ); } return true; } abstract getFieldSchema(spec: FieldSpec, strict?: boolean): SchemaResponse; toDriver(value: unknown, field: Field): unknown { return value; } fromDriver(value: any, field: Field): unknown { return value; } async close(): Promise { // no-op by default } } export function customIntrospector>( dialect: T, introspector: Constructor, options: BaseIntrospectorConfig = {}, ) { return { create(...args: ConstructorParameters) { return new (class extends dialect { override createIntrospector(db: Kysely): DatabaseIntrospector { return new introspector(db, options); } })(...args); }, }; } export class DummyConnection extends Connection { override name = "dummy"; constructor() { super(undefined as any); } override getFieldSchema(): SchemaResponse { throw new Error("Method not implemented."); } }