import type { GeneratedSchema, SchemaLike } from '@logto/schemas'; import { has } from '@silverhand/essentials'; import type { CommonQueryMethods, IdentifierSqlToken } from '@silverhand/slonik'; import { sql } from '@silverhand/slonik'; import { InsertionError } from '#src/errors/SlonikError/index.js'; import assertThat from '#src/utils/assert-that.js'; import { type OmitAutoSetFields, convertToIdentifiers, excludeAutoSetFields, convertToPrimitiveOrSql, conditionalSql, } from '#src/utils/sql.js'; const setExcluded = (...fields: IdentifierSqlToken[]) => sql.join( fields.map((field) => sql`${field}=excluded.${field}`), sql`, ` ); type OnConflict = | { fields: IdentifierSqlToken[]; setExcludedFields: IdentifierSqlToken[]; ignore?: false; } | { ignore: true; }; type InsertIntoConfigReturning = { returning: true; onConflict?: OnConflict; }; type InsertIntoConfig = { returning?: false; onConflict?: OnConflict; }; type BuildInsertInto = { < Key extends string, CreateSchema extends Partial>, Schema extends SchemaLike, >( { fieldKeys, ...rest }: GeneratedSchema, config: InsertIntoConfigReturning ): (data: OmitAutoSetFields) => Promise; < Key extends string, CreateSchema extends Partial>, Schema extends SchemaLike, >( { fieldKeys, ...rest }: GeneratedSchema, config?: InsertIntoConfig ): (data: OmitAutoSetFields) => Promise; }; export const buildInsertIntoWithPool = (pool: CommonQueryMethods): BuildInsertInto => < Key extends string, CreateSchema extends Partial>, Schema extends SchemaLike, >( schema: GeneratedSchema, config?: InsertIntoConfig | InsertIntoConfigReturning ) => { const { fieldKeys, ...rest } = schema; const { table, fields } = convertToIdentifiers(rest); const keys = excludeAutoSetFields(fieldKeys); const returning = Boolean(config?.returning); const onConflict = config?.onConflict; return async (data: OmitAutoSetFields): Promise => { const insertingKeys = keys.filter((key) => has(data, key)); const { rows: [entry], } = await pool.query(sql` insert into ${table} (${sql.join( insertingKeys.map((key) => fields[key]), sql`, ` )}) values (${sql.join( insertingKeys.map((key) => convertToPrimitiveOrSql(key, data[key] ?? null)), sql`, ` )}) ${conditionalSql(onConflict, (config) => config.ignore ? sql` on conflict do nothing ` : sql` on conflict (${sql.join(config.fields, sql`, `)}) do update set ${setExcluded(...config.setExcludedFields)} ` )} ${conditionalSql(returning, () => sql`returning *`)} `); assertThat(!returning || entry, new InsertionError(schema, data)); return entry; }; };