@happyvertical/sql 0.102.2 → 0.102.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/AGENT.md +1 -1
  2. package/README.md +99 -4
  3. package/dist/chunks/{duckdb-DfikVgov.js → duckdb-LMyfKZGi.js} +3 -3
  4. package/dist/chunks/{duckdb-DfikVgov.js.map → duckdb-LMyfKZGi.js.map} +1 -1
  5. package/dist/chunks/postgres-dialect-Bfswr4Vs.js +2728 -0
  6. package/dist/chunks/postgres-dialect-Bfswr4Vs.js.map +1 -0
  7. package/dist/chunks/{sqlite-Cz2NMiDs.js → sqlite-0PMv1va2.js} +5 -5
  8. package/dist/chunks/{sqlite-Cz2NMiDs.js.map → sqlite-0PMv1va2.js.map} +1 -1
  9. package/dist/chunks/{sqlite-connection-cache-BAakj2HO.js → sqlite-connection-cache-ChWav9iY.js} +2 -2
  10. package/dist/chunks/{sqlite-connection-cache-BAakj2HO.js.map → sqlite-connection-cache-ChWav9iY.js.map} +1 -1
  11. package/dist/chunks/{sqlite-native-ByGwGEbS.js → sqlite-native--z4BnolL.js} +4 -4
  12. package/dist/chunks/{sqlite-native-ByGwGEbS.js.map → sqlite-native--z4BnolL.js.map} +1 -1
  13. package/dist/index.d.ts +5 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +163 -1365
  16. package/dist/index.js.map +1 -1
  17. package/dist/pglite.d.ts +150 -0
  18. package/dist/pglite.d.ts.map +1 -0
  19. package/dist/pglite.js +339 -0
  20. package/dist/pglite.js.map +1 -0
  21. package/dist/postgres.d.ts.map +1 -1
  22. package/dist/shared/postgres-dialect.d.ts +153 -0
  23. package/dist/shared/postgres-dialect.d.ts.map +1 -0
  24. package/dist/shared/types.d.ts +4 -4
  25. package/metadata.json +1 -0
  26. package/package.json +13 -2
  27. package/dist/chunks/connection-cache-Da5UcY5u.js +0 -1305
  28. package/dist/chunks/connection-cache-Da5UcY5u.js.map +0 -1
  29. package/dist/chunks/types-CwWJ-yju.js +0 -139
  30. package/dist/chunks/types-CwWJ-yju.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/aggregate.ts","../src/doctor.ts","../src/json.ts","../src/postgres.ts","../src/index.ts"],"sourcesContent":["import type { WhereClause } from './shared/types.js';\nimport {\n buildWhere,\n parseConditionKey,\n raw,\n type SqlAdapterType,\n unwrapRawKey,\n} from './shared/utils.js';\n\nexport type AggregateTimeBucketUnit =\n | 'minute'\n | 'hour'\n | 'day'\n | 'week'\n | 'month'\n | 'quarter'\n | 'year';\n\nexport type AggregateFunction =\n | 'count'\n | 'sum'\n | 'avg'\n | 'min'\n | 'max'\n | (string & {});\n\nexport type AggregateSelectExpr =\n | { column: string; as?: string }\n | {\n fn: AggregateFunction;\n column?: string;\n as: string;\n distinct?: boolean;\n }\n | {\n bucket: AggregateTimeBucketUnit;\n column: string;\n as: string;\n };\n\nexport interface AggregateSpec {\n from: string;\n select: AggregateSelectExpr[];\n where?: WhereClause;\n groupBy?: string[];\n having?: WhereClause;\n orderBy?: string | string[];\n limit?: number;\n offset?: number;\n}\n\nexport interface AggregateBuildResult {\n sql: string;\n values: any[];\n}\n\nconst VALID_DIRECTIONS = new Set(['ASC', 'DESC']);\nconst VALID_BUCKET_UNITS = new Set<AggregateTimeBucketUnit>([\n 'minute',\n 'hour',\n 'day',\n 'week',\n 'month',\n 'quarter',\n 'year',\n]);\n\nfunction validateSqlIdentifier(identifier: string): string {\n // Reject non-strings before the regex. `test` coerces via `toString`, so a\n // value whose `toString` differs across reads could pass here and interpolate\n // a different identifier into the aggregate SQL than the one validated.\n if (typeof identifier !== 'string' || !/^[a-zA-Z0-9_.]+$/.test(identifier)) {\n throw new Error(`Invalid SQL identifier: ${identifier}`);\n }\n return identifier;\n}\n\nfunction normalizeAdapter(adapterType?: SqlAdapterType): SqlAdapterType {\n return adapterType ?? 'sqlite';\n}\n\nfunction placeholder(index: number): string {\n return `$${index}`;\n}\n\nexport function bucketExpr(\n adapterType: SqlAdapterType | undefined,\n unit: AggregateTimeBucketUnit,\n column: string,\n): string {\n if (!VALID_BUCKET_UNITS.has(unit)) {\n throw new Error(`Invalid aggregate bucket unit: ${unit}`);\n }\n\n const col = validateSqlIdentifier(column);\n const adapter = normalizeAdapter(adapterType);\n\n if (adapter === 'postgres' || adapter === 'duckdb' || adapter === 'json') {\n return `date_trunc('${unit}', ${col})`;\n }\n\n switch (unit) {\n case 'minute':\n return `strftime('%Y-%m-%d %H:%M:00', ${col})`;\n case 'hour':\n return `strftime('%Y-%m-%d %H:00:00', ${col})`;\n case 'day':\n return `strftime('%Y-%m-%d 00:00:00', ${col})`;\n case 'week':\n return `strftime('%Y-%m-%d 00:00:00', date(${col}, printf('-%d days', (CAST(strftime('%w', ${col}) AS INTEGER) + 6) % 7)))`;\n case 'month':\n return `strftime('%Y-%m-01 00:00:00', ${col})`;\n case 'quarter':\n return `strftime('%Y-%m-%d 00:00:00', date(strftime('%Y-', ${col}) || printf('%02d', ((CAST(strftime('%m', ${col}) AS INTEGER) - 1) / 3) * 3 + 1) || '-01'))`;\n case 'year':\n return `strftime('%Y-01-01 00:00:00', ${col})`;\n }\n}\n\nfunction aggregateExpression(\n expr: Extract<AggregateSelectExpr, { fn: AggregateFunction }>,\n): string {\n const normalizedFn = String(expr.fn).toLowerCase();\n const fn = validateSqlIdentifier(String(expr.fn)).toUpperCase();\n if (normalizedFn === 'count' && !expr.column) {\n if (expr.distinct) {\n throw new Error(`Aggregate ${expr.fn} with distinct requires a column`);\n }\n return 'COUNT(*)';\n }\n if (!expr.column) {\n throw new Error(`Aggregate ${expr.fn} requires a column`);\n }\n const col = validateSqlIdentifier(expr.column);\n const distinct = expr.distinct ? 'DISTINCT ' : '';\n return `${fn}(${distinct}${col})`;\n}\n\nfunction selectExpression(\n expr: AggregateSelectExpr,\n adapterType: SqlAdapterType | undefined,\n): { expression: string; alias: string; groupingCandidate: boolean } {\n if ('column' in expr && !('fn' in expr) && !('bucket' in expr)) {\n const column = validateSqlIdentifier(expr.column);\n const alias = validateSqlIdentifier(expr.as ?? expr.column);\n return { expression: column, alias, groupingCandidate: true };\n }\n if ('bucket' in expr) {\n const expression = bucketExpr(adapterType, expr.bucket, expr.column);\n const alias = validateSqlIdentifier(expr.as);\n return { expression, alias, groupingCandidate: true };\n }\n\n const expression = aggregateExpression(expr);\n const alias = validateSqlIdentifier(expr.as);\n return { expression, alias, groupingCandidate: false };\n}\n\nfunction expandHavingKey(\n key: string,\n expressionByAlias: Map<string, string>,\n): string {\n // A key the caller already wrapped in raw() is used verbatim: raw() means\n // \"this exact SQL\", so it deliberately bypasses select-alias expansion.\n if (unwrapRawKey(key).isRaw) return key;\n\n const parsed = parseConditionKey(key);\n const expression =\n expressionByAlias.get(parsed.field) ?? validateSqlIdentifier(parsed.field);\n\n // The caller's key is validated above (as an alias or a plain identifier);\n // what comes out is a select expression such as `SUM(total) >`, which only\n // buildWhere's raw channel accepts.\n return raw(`${expression} ${parsed.operator}`);\n}\n\nfunction expandHaving(\n having: WhereClause,\n expressionByAlias: Map<string, string>,\n): WhereClause {\n const expandCondition = (condition: Record<string, any>) =>\n Object.fromEntries(\n Object.entries(condition).map(([key, value]) => [\n expandHavingKey(key, expressionByAlias),\n value,\n ]),\n );\n if (Array.isArray(having)) {\n return having.map((andGroup: Record<string, any>[]) =>\n andGroup.map((condition: Record<string, any>) =>\n expandCondition(condition),\n ),\n );\n }\n\n return expandCondition(having);\n}\n\nfunction buildHaving(\n having: AggregateSpec['having'],\n expressionByAlias: Map<string, string>,\n startIndex: number,\n adapterType: SqlAdapterType | undefined,\n): AggregateBuildResult {\n if (!having) return { sql: '', values: [] };\n const expanded = expandHaving(having, expressionByAlias);\n const built = buildWhere(expanded, startIndex, adapterType);\n\n return {\n sql: built.sql ? built.sql.replace(/^WHERE/, 'HAVING') : '',\n values: built.values,\n };\n}\n\nfunction buildOrderBy(orderBy: string | string[] | undefined): string {\n if (!orderBy) return '';\n const items = Array.isArray(orderBy) ? orderBy : [orderBy];\n const parts = items.map((item) => {\n const [field, direction = 'ASC'] = item.trim().split(/\\s+/);\n const normalizedDirection = direction.toUpperCase();\n if (!VALID_DIRECTIONS.has(normalizedDirection)) {\n throw new Error(`Invalid aggregate order direction: ${direction}`);\n }\n const orderExpr = validateSqlIdentifier(field);\n return `${orderExpr} ${normalizedDirection}`;\n });\n return `ORDER BY ${parts.join(', ')}`;\n}\n\nexport function buildAggregate(\n spec: AggregateSpec,\n startIndex = 1,\n adapterType?: SqlAdapterType,\n): AggregateBuildResult {\n const from = validateSqlIdentifier(spec.from);\n if (spec.select.length === 0) {\n throw new Error(\n 'AggregateSpec.select must contain at least one expression',\n );\n }\n\n const expressionByAlias = new Map<string, string>();\n const groupingAliases: string[] = [];\n const selectSql = spec.select.map((expr) => {\n const selected = selectExpression(expr, adapterType);\n expressionByAlias.set(selected.alias, selected.expression);\n if (selected.groupingCandidate) {\n groupingAliases.push(selected.alias);\n }\n return `${selected.expression} AS ${selected.alias}`;\n });\n\n const where = spec.where\n ? buildWhere(spec.where, startIndex, adapterType)\n : { sql: '', values: [] };\n let nextParamIndex = startIndex + where.values.length;\n\n const groupByItems = spec.groupBy ?? groupingAliases;\n const groupBySql =\n groupByItems.length > 0\n ? `GROUP BY ${groupByItems\n .map((item) =>\n expressionByAlias.has(item)\n ? expressionByAlias.get(item)\n : validateSqlIdentifier(item),\n )\n .join(', ')}`\n : '';\n\n const having = buildHaving(\n spec.having,\n expressionByAlias,\n nextParamIndex,\n adapterType,\n );\n nextParamIndex += having.values.length;\n\n const orderBySql = buildOrderBy(spec.orderBy);\n\n const pagingValues: any[] = [];\n const pagingSql: string[] = [];\n if (spec.limit !== undefined) {\n pagingSql.push(`LIMIT ${placeholder(nextParamIndex++)}`);\n pagingValues.push(spec.limit);\n } else if (\n spec.offset !== undefined &&\n normalizeAdapter(adapterType) === 'sqlite'\n ) {\n pagingSql.push('LIMIT -1');\n }\n if (spec.offset !== undefined) {\n pagingSql.push(`OFFSET ${placeholder(nextParamIndex++)}`);\n pagingValues.push(spec.offset);\n }\n\n const sql = [\n `SELECT ${selectSql.join(', ')}`,\n `FROM ${from}`,\n where.sql,\n groupBySql,\n having.sql,\n orderBySql,\n pagingSql.join(' '),\n ]\n .filter(Boolean)\n .join(' ');\n\n return {\n sql,\n values: [...where.values, ...having.values, ...pagingValues],\n };\n}\n","/**\n * Database integrity-check framework.\n *\n * The runner is a thin loop over user-supplied checks; the value here is\n * the reusable check builders (expected tables, unique columns, FK-like\n * relationships) that codify the common shapes of integrity bugs that\n * sneak past schema migrations.\n *\n * No SMRT awareness — checks operate on the `DatabaseInterface` from this\n * package and on plain table/column names.\n *\n * **Postgres-only today.** The bundled check builders and\n * `listPublicTables` use Postgres syntax (`pg_tables`, `::int`/`::text`\n * casts). The shape is portable in principle — `DoctorContext` carries a\n * generic `DatabaseInterface` so apps can add engine-aware checks — but\n * the out-of-the-box runner expects a Postgres connection. `runDoctor`\n * throws early if pointed at a non-Postgres URL rather than failing\n * mid-execution with a confusing `pg_tables`-doesn't-exist error.\n */\nimport { redactDatabaseUrl } from './postgres-cli';\nimport type { DatabaseInterface } from './shared/types';\n\nexport type DoctorLevel = 'fail' | 'warn';\n\nexport interface DoctorIssue {\n level: DoctorLevel;\n message: string;\n}\n\nexport interface DoctorContext {\n db: DatabaseInterface;\n tables: Set<string>;\n}\n\nexport type DoctorCheck = (ctx: DoctorContext) => Promise<DoctorIssue[]>;\n\nexport interface RunDoctorOptions {\n db: DatabaseInterface;\n checks: DoctorCheck[];\n}\n\nexport interface DoctorResult {\n failures: DoctorIssue[];\n warnings: DoctorIssue[];\n issues: DoctorIssue[];\n}\n\n/**\n * Execute every check against the public-schema tables of `db` and\n * partition the results by severity. Returns the aggregate; callers\n * format/exit as they see fit.\n *\n * The current table set is enumerated once and shared across checks so\n * each check can cheaply decide whether its target tables exist.\n */\nexport async function runDoctor(\n options: RunDoctorOptions,\n): Promise<DoctorResult> {\n assertPostgresAdapter(options.db);\n const tables = await listPublicTables(options.db);\n const issues: DoctorIssue[] = [];\n\n for (const check of options.checks) {\n const checkIssues = await check({ db: options.db, tables });\n issues.push(...checkIssues);\n }\n\n return {\n failures: issues.filter((issue) => issue.level === 'fail'),\n warnings: issues.filter((issue) => issue.level === 'warn'),\n issues,\n };\n}\n\n/**\n * Throw early if the supplied database isn't Postgres. The check builders\n * and `listPublicTables` use Postgres syntax; pointing them at SQLite or\n * DuckDB would fail mid-execution with a confusing \"table pg_tables does\n * not exist\" error from the adapter. Fail fast with a useful message\n * instead.\n */\nfunction assertPostgresAdapter(db: DatabaseInterface): void {\n const url = String(db.url ?? '');\n if (!url.startsWith('postgres://') && !url.startsWith('postgresql://')) {\n throw new Error(\n `runDoctor requires a Postgres database connection. Got URL: ${url ? redactDatabaseUrl(url) : '(empty)'}. ` +\n 'The bundled check builders use Postgres-specific syntax (pg_tables, ::int/::text casts).',\n );\n }\n}\n\n/**\n * Emit a 'warn' for each table in `expected` that's missing.\n *\n * Marked as `warn` rather than `fail` because a missing table often\n * indicates a migration that hasn't been run yet, not active data\n * corruption — callers can promote to fail by mapping the issues.\n */\nexport function checkExpectedTables(expected: readonly string[]): DoctorCheck {\n return async ({ tables }) => {\n const issues: DoctorIssue[] = [];\n for (const table of expected) {\n if (!tables.has(table)) {\n issues.push({\n level: 'warn',\n message: `Missing expected table: ${table}`,\n });\n }\n }\n return issues;\n };\n}\n\nexport interface UniqueColumnSpec {\n table: string;\n column: string;\n /** Human-readable label used in the issue message. */\n label: string;\n}\n\n/**\n * Emit a 'fail' for each value that appears more than once in\n * `(table, column)` (ignoring NULL and empty-string values).\n *\n * Useful for catching slug/key collisions that the schema didn't\n * declare a UNIQUE constraint for (or where the constraint was added\n * after data already existed).\n */\nexport function checkUniqueColumn(spec: UniqueColumnSpec): DoctorCheck {\n return async ({ db, tables }) => {\n if (!tables.has(spec.table)) return [];\n const result = await db.query(`\n SELECT ${quoteIdentifier(spec.column)} AS value, count(*)::text AS count\n FROM ${quoteIdentifier(spec.table)}\n WHERE NULLIF(trim(${quoteIdentifier(spec.column)}::text), '') IS NOT NULL\n GROUP BY ${quoteIdentifier(spec.column)}\n HAVING count(*) > 1\n ORDER BY count DESC, ${quoteIdentifier(spec.column)}\n `);\n return result.rows.map((row) => ({\n level: 'fail' as const,\n message: `Duplicate ${spec.label} \"${String(row.value)}\" in ${spec.table} (${countValueFromRow(\n row.count,\n )} rows).`,\n }));\n };\n}\n\nexport interface RelationshipSpec {\n childTable: string;\n childColumn: string;\n parentTable: string;\n /** Defaults to 'id'. */\n parentColumn?: string;\n /** Human-readable label used in the issue message. */\n label: string;\n}\n\n/**\n * Emit a 'fail' counting orphan rows where the child column points at a\n * parent row that doesn't exist (FK-shaped check, but doesn't require an\n * actual FK constraint to be declared — useful for soft references like\n * tag-id-or-slug lookup columns).\n */\nexport function checkRelationship(spec: RelationshipSpec): DoctorCheck {\n return async ({ db, tables }) => {\n if (!tables.has(spec.childTable) || !tables.has(spec.parentTable))\n return [];\n const parentColumn = spec.parentColumn ?? 'id';\n const result = await db.query(`\n SELECT count(*)::text AS count\n FROM ${quoteIdentifier(spec.childTable)} child\n LEFT JOIN ${quoteIdentifier(spec.parentTable)} parent\n ON parent.${quoteIdentifier(parentColumn)} = child.${quoteIdentifier(spec.childColumn)}\n WHERE NULLIF(trim(child.${quoteIdentifier(spec.childColumn)}::text), '') IS NOT NULL\n AND parent.${quoteIdentifier(parentColumn)} IS NULL\n `);\n const orphanCount = countValueFromRow(result.rows[0]?.count);\n if (orphanCount === '0') return [];\n return [\n {\n level: 'fail',\n message: `${spec.label}: ${orphanCount} orphaned row${orphanCount === '1' ? '' : 's'}.`,\n },\n ];\n };\n}\n\nasync function listPublicTables(db: DatabaseInterface): Promise<Set<string>> {\n const result = await db.query(\n \"SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename\",\n );\n return new Set(result.rows.map((row) => String(row.tablename ?? '')));\n}\n\nfunction quoteIdentifier(value: string): string {\n return `\"${value.replace(/\"/gu, '\"\"')}\"`;\n}\n\nfunction countValueFromRow(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'bigint') {\n return String(value);\n }\n return '0';\n}\n","import { mkdir, readdir, stat } from 'node:fs/promises';\nimport { basename, extname, join } from 'node:path';\nimport { DatabaseError } from '@happyvertical/utils';\nimport { DatabaseSchemaManager } from './schema-manager';\nimport {\n escapeQuotedIdentifier,\n escapeStringLiteral,\n validateColumnNames,\n} from './shared/alter-utils';\nimport {\n ConnectionCache,\n enableCachedValidation,\n} from './shared/connection-cache';\nimport {\n createDuckDBResourceCloser,\n throwWithDuckDBCleanup,\n} from './shared/duckdb-resources';\nimport { convertUniqueIndexesToInlineConstraints } from './shared/duckdb-schema-utils';\nimport { redactDatabaseUrl } from './shared/redact-database-url';\nimport { createTransactionLock } from './shared/transaction-lock';\nimport type {\n DatabaseInterface,\n JSONOptions,\n JSONTableLoadError,\n QueryResult,\n SchemaInitializationOptions,\n TableInterface,\n TransactionHandle,\n UpsertOptions,\n} from './shared/types';\nimport { NestedTransactionError, resolveSchemas } from './shared/types';\nimport {\n buildWhere,\n formatDbError,\n resolveInsertColumns,\n wrapDatabaseError,\n} from './shared/utils';\n\n/**\n * Extend globalThis to include our connection cache properties.\n * Using globalThis ensures all module instances share the same cache,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * @see https://github.com/happyvertical/sdk/issues/678\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __haveSqlJSONConnectionCache:\n | ConnectionCache<DatabaseInterface>\n | undefined;\n}\n\n/**\n * Connection cache for in-memory DuckDB instances keyed by data directory URL\n * Enables sharing of in-memory databases across multiple getDatabase() calls\n * with the same data directory.\n *\n * Uses globalThis to ensure cache is shared across all module instances,\n * fixing the lost update bug in monorepos with workspace symlinks.\n *\n * @see https://github.com/happyvertical/sdk/issues/678\n */\nglobalThis.__haveSqlJSONConnectionCache ??=\n new ConnectionCache<DatabaseInterface>();\nconst memoryConnectionCache = globalThis.__haveSqlJSONConnectionCache;\nenableCachedValidation(memoryConnectionCache);\n\ntype JSONSourceCacheState = {\n activeWrites: Set<Promise<void>>;\n closing: boolean;\n dataDir: string;\n fingerprint: string | undefined;\n sourceWriteTail: Promise<void>;\n trackExternalChanges: boolean;\n};\n\nconst JSON_SOURCE_CACHE_STATE = Symbol.for(\n '@happyvertical/sql/json-source-cache-state/v1',\n);\n\ntype TrackedJSONDatabase = DatabaseInterface &\n Record<symbol, JSONSourceCacheState | undefined>;\n\nconst SOURCE_SNAPSHOT_ATTEMPTS = 3;\n\nfunction isMissingPathError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n error.code === 'ENOENT'\n );\n}\n\nasync function listJSONSourceFiles(dataDir: string): Promise<string[] | null> {\n try {\n return (await readdir(dataDir))\n .filter((file) => {\n const lower = file.toLowerCase();\n return lower.endsWith('.json') || lower.endsWith('.schema.sql');\n })\n .sort();\n } catch (error) {\n if (isMissingPathError(error)) return null;\n throw error;\n }\n}\n\n/**\n * Captures the JSON adapter's file-backed inputs without reading their contents.\n * Directory membership plus nanosecond timestamps, size and inode detect edits,\n * replacements, additions and removals while keeping cache hits inexpensive.\n */\nasync function getJSONSourceFingerprint(dataDir: string): Promise<string> {\n const statFiles = (files: string[]) =>\n Promise.all(\n files.map(async (file) => {\n const info = await stat(join(dataDir, file), { bigint: true });\n return [\n file,\n info.size.toString(),\n info.mtimeNs.toString(),\n info.ctimeNs.toString(),\n info.ino.toString(),\n ];\n }),\n );\n const sameFiles = (left: string[], right: string[] | null) =>\n right !== null &&\n left.length === right.length &&\n left.every((file, index) => file === right[index]);\n\n for (let attempt = 0; attempt < SOURCE_SNAPSHOT_ATTEMPTS; attempt += 1) {\n const filesBefore = await listJSONSourceFiles(dataDir);\n if (filesBefore === null) return 'directory:missing';\n\n try {\n const entriesBefore = await statFiles(filesBefore);\n const filesDuring = await listJSONSourceFiles(dataDir);\n if (!sameFiles(filesBefore, filesDuring)) continue;\n\n // Re-stat after the membership check. A same-file edit can otherwise\n // complete after that file's first stat while leaving the name list\n // unchanged, causing this acquisition to accept stale metadata.\n const entriesAfter = await statFiles(filesBefore);\n const filesAfter = await listJSONSourceFiles(dataDir);\n const fingerprintAfter = JSON.stringify(entriesAfter);\n if (\n sameFiles(filesBefore, filesAfter) &&\n JSON.stringify(entriesBefore) === fingerprintAfter\n ) {\n return fingerprintAfter;\n }\n } catch (error) {\n if (!isMissingPathError(error)) throw error;\n }\n }\n\n throw new DatabaseError(\n 'JSON source files changed while checking connection cache freshness',\n { dataDir },\n );\n}\n\nfunction getJSONSourceCacheState(\n db: DatabaseInterface,\n): JSONSourceCacheState | undefined {\n return (db as TrackedJSONDatabase)[JSON_SOURCE_CACHE_STATE];\n}\n\nfunction setJSONSourceCacheState(\n db: DatabaseInterface,\n state: JSONSourceCacheState,\n): void {\n Object.defineProperty(db, JSON_SOURCE_CACHE_STATE, {\n configurable: false,\n enumerable: false,\n value: state,\n writable: false,\n });\n}\n\nasync function waitForJSONSourceWrites(\n state: JSONSourceCacheState,\n): Promise<void> {\n while (state.activeWrites.size > 0) {\n await Promise.allSettled([...state.activeWrites]);\n }\n}\n\nasync function isCachedJSONDatabaseFresh(\n db: DatabaseInterface,\n): Promise<boolean> {\n const state = getJSONSourceCacheState(db);\n // A cached adapter created by an older module instance has no trustworthy\n // snapshot, so replace it once and establish the current tracking contract.\n if (!state) return false;\n if (!state.trackExternalChanges) return true;\n\n while (true) {\n await waitForJSONSourceWrites(state);\n const fingerprint = await getJSONSourceFingerprint(state.dataDir);\n if (state.activeWrites.size === 0) {\n return fingerprint === state.fingerprint;\n }\n }\n}\n\nasync function trackJSONSourceWrite<T>(\n db: DatabaseInterface,\n table: string,\n write: () => Promise<T>,\n): Promise<T> {\n const state = getJSONSourceCacheState(db);\n if (!state) return write();\n if (state.closing) {\n throw new DatabaseError('JSON database connection is closing');\n }\n\n const previousWrite = state.sourceWriteTail;\n let releaseWrite!: () => void;\n const currentWrite = new Promise<void>((resolve) => {\n releaseWrite = resolve;\n });\n state.sourceWriteTail = previousWrite.then(\n () => currentWrite,\n () => currentWrite,\n );\n\n const operation = (async () => {\n await previousWrite.catch(() => {});\n let succeeded = false;\n let fingerprintBeforeWrite: string | undefined;\n try {\n if (state.trackExternalChanges) {\n fingerprintBeforeWrite = await getJSONSourceFingerprint(state.dataDir);\n if (fingerprintBeforeWrite !== state.fingerprint) {\n state.fingerprint = undefined;\n throw new DatabaseError(\n 'JSON source files changed externally before the adapter could persist its write; acquire a fresh database and retry',\n { dataDir: state.dataDir, table },\n );\n }\n }\n const result = await write();\n succeeded = true;\n return result;\n } finally {\n if (succeeded && state.trackExternalChanges) {\n try {\n const fingerprintAfterWrite = await getJSONSourceFingerprint(\n state.dataDir,\n );\n const ownedFiles = new Set([`${table}.json`, `${table}.schema.sql`]);\n const excludeOwnedFiles = (fingerprint: string) =>\n JSON.stringify(\n (JSON.parse(fingerprint) as string[][]).filter(\n ([file]) => !ownedFiles.has(file),\n ),\n );\n\n // Only the table's own data/schema files may change during this\n // export. Preserve evidence of any unrelated external mutation so\n // the waiting cache acquisition replaces this adapter.\n state.fingerprint =\n fingerprintBeforeWrite !== undefined &&\n excludeOwnedFiles(fingerprintBeforeWrite) ===\n excludeOwnedFiles(fingerprintAfterWrite)\n ? fingerprintAfterWrite\n : undefined;\n } catch (error) {\n // The write itself succeeded. Mark the snapshot unknown so the next\n // acquisition replaces this adapter, but do not report a false write\n // failure after DuckDB has already persisted the data.\n state.fingerprint = undefined;\n console.warn(\n `[json-adapter] Could not refresh source fingerprint: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n } else if (!succeeded) {\n state.fingerprint = undefined;\n }\n releaseWrite();\n }\n })();\n const activeWrite = operation.then(\n () => undefined,\n () => undefined,\n );\n state.activeWrites.add(activeWrite);\n try {\n return await operation;\n } finally {\n state.activeWrites.delete(activeWrite);\n }\n}\n\n/**\n * Clears and closes all cached connections.\n * Callers must await completion before opening a replacement connection.\n */\nexport async function clearConnectionCache(): Promise<void> {\n await memoryConnectionCache.clear(closeJSONDatabase);\n}\n\nasync function closeJSONDatabase(db: DatabaseInterface): Promise<void> {\n await db.close?.();\n}\n\n/**\n * Creates a JSON database connection using DuckDB in-memory engine\n *\n * This adapter uses DuckDB as a query engine for JSON files, storing everything\n * in-memory and writing changes back to JSON files. No WAL files or persistent\n * database files are created.\n *\n * @param options - JSON database options\n * @returns Promise resolving to a DuckDB connection\n */\nasync function createJSONConnection(options: JSONOptions) {\n const { url, autoRegister = true, eagerLoadTables = true } = options;\n const schemas = resolveSchemas(options.schemas) ?? {};\n\n if (!url) {\n throw new DatabaseError('url is required for JSON adapter', {\n options,\n });\n }\n\n // Track JSON files pending deferred loading (tableName → filePath)\n const pendingJSONFiles = new Map<string, string>();\n\n // Track tables created with inferred schemas (need DROP before syncSchema())\n const inferredSchemaTables = new Set<string>();\n\n // Accumulate data-load failures so calling code can detect a table that was\n // created but left empty because its data could not be inserted (issue #1139)\n const tableLoadErrors: JSONTableLoadError[] = [];\n const reportLoadError = (info: JSONTableLoadError): void => {\n tableLoadErrors.push(info);\n // A caller callback must never break loading of the remaining tables\n try {\n options.onTableLoadError?.(info);\n } catch (callbackError) {\n console.error(\n '[json-adapter] onTableLoadError callback threw:',\n callbackError,\n );\n }\n };\n\n let instance: any;\n let connection: any;\n try {\n // Dynamic import to avoid bundling\n const duckdbModule = '@duckdb/node-api';\n const { DuckDBInstance } = await import(/* @vite-ignore */ duckdbModule);\n\n // Always use in-memory database - no persistent files\n instance = await DuckDBInstance.create(':memory:');\n connection = await instance.connect();\n\n // Ensure data directory exists\n try {\n await mkdir(url, { recursive: true });\n } catch (_error) {\n // Directory might already exist, that's okay\n }\n\n // Scan JSON files - tables with schemas are created now, others are deferred\n if (autoRegister) {\n await loadJSONTables(\n connection,\n url,\n schemas,\n pendingJSONFiles,\n eagerLoadTables,\n inferredSchemaTables,\n reportLoadError,\n );\n }\n\n return {\n instance,\n connection,\n pendingJSONFiles,\n inferredSchemaTables,\n tableLoadErrors,\n reportLoadError,\n };\n } catch (error) {\n const errorMessage = redactDatabaseUrl(\n error instanceof Error ? error.message : String(error),\n );\n const databaseError = new DatabaseError(\n `Failed to create JSON database connection: ${errorMessage}`,\n {\n url: redactDatabaseUrl(url),\n originalError: errorMessage,\n },\n );\n return throwWithDuckDBCleanup(\n databaseError,\n createDuckDBResourceCloser(connection, instance),\n );\n }\n}\n\n/**\n * Loads JSON data into an existing table\n *\n * This function only loads data - it assumes the table already exists with proper schema.\n * Used after DDL creates the table to load persisted JSON data.\n *\n * An unreadable or malformed file is tolerated with a warning - the table\n * structure is what matters and there is no data to lose.\n *\n * A failure to insert records that DID parse is logged loudly rather than\n * swallowed with the old dismissive \"that's okay\" warning: the resulting empty\n * table is a real surprise the caller should see (issue #1132). It is logged\n * rather than thrown so that one unloadable table cannot abort the load of every\n * other table sharing the same data directory or syncSchema() call - both of\n * those paths already load each table independently.\n *\n * @param connection - DuckDB connection\n * @param tableName - Name of the table to load data into\n * @param filePath - Path to the JSON file\n * @param reportLoadError - Sink invoked when parsed records fail to insert (issue #1139)\n */\nasync function loadJSONData(\n connection: any,\n tableName: string,\n filePath: string,\n reportLoadError?: (info: JSONTableLoadError) => void,\n): Promise<void> {\n let records: unknown;\n\n try {\n const { readFile } = await import('node:fs/promises');\n const jsonContent = await readFile(filePath, 'utf-8');\n records = JSON.parse(jsonContent);\n } catch (error) {\n // Missing, empty, or malformed file - table structure is what matters\n console.warn(\n `[json-adapter] Could not read data for ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n return;\n }\n\n if (!Array.isArray(records) || records.length === 0) {\n return;\n }\n\n try {\n const inserted = await insertRecordsWithCast(\n connection,\n tableName,\n records,\n );\n if (inserted > 0) {\n console.log(\n `[json-adapter] Loaded ${inserted} records into ${tableName} from JSON`,\n );\n }\n } catch (error) {\n // Pass the raw error as a second argument so its context (the underlying\n // DuckDB cause, e.g. a NOT NULL violation) is rendered, not just the wrapper\n // message - diagnosability is the point of surfacing this (issue #1132)\n console.error(\n `[json-adapter] Failed to load data for ${tableName}; the table will be empty:`,\n error,\n );\n // Also surface it to calling code, which cannot otherwise tell an empty\n // table apart from a table that failed to load (issue #1139)\n reportLoadError?.({ table: tableName, filePath, error });\n }\n}\n\n/**\n * Scans the data directory and registers JSON files for deferred loading\n *\n * Unlike the DuckDB adapter which creates views, this creates actual tables\n * so that indexes can be added.\n *\n * Schema resolution priority:\n * 1. Explicit schemas provided via options.schemas - table created immediately\n * 2. Schema file (.schema.sql) in data directory - table created with schema\n * 3. All other tables - DEFERRED until syncSchema() provides DDL\n *\n * IMPORTANT: Tables without explicit schemas are NOT auto-created from JSON.\n * This ensures UNIQUE constraints from syncSchema() are in place before data\n * is loaded, which is required for UPSERT operations to work correctly.\n *\n * For users who want auto-inference, use the inferSchemaFromJSON() method\n * after database creation.\n *\n * @param connection - DuckDB connection\n * @param dataDir - Directory containing JSON files\n * @param providedSchemas - Explicit schema definitions provided by caller (optional)\n * @param pendingJSONFiles - Map to store JSON files pending deferred loading\n * @param eagerLoadTables - Whether to eagerly create tables with inferred schemas\n * @param inferredSchemaTables - Set to track tables created with inferred schemas\n * @param reportLoadError - Sink for per-table data-load failures (issue #1139)\n */\nasync function loadJSONTables(\n connection: any,\n dataDir: string,\n providedSchemas: Record<string, import('./shared/types').SchemaProvider> = {},\n pendingJSONFiles: Map<string, string> = new Map(),\n eagerLoadTables: boolean = true,\n inferredSchemaTables: Set<string> = new Set(),\n reportLoadError?: (info: JSONTableLoadError) => void,\n) {\n try {\n const files = await readdir(dataDir);\n const jsonFiles = files.filter(\n (file) => extname(file).toLowerCase() === '.json',\n );\n\n for (const file of jsonFiles) {\n const filePath = join(dataDir, file);\n const tableName = basename(file, '.json');\n\n // Check for explicitly provided schema\n const schema = providedSchemas[tableName];\n\n if (schema) {\n // Create table with provided schema (proper typing)\n console.log(\n `[json-adapter] Creating ${tableName} with provided schema definition`,\n );\n await createTableFromSchema(connection, tableName, schema);\n\n // Load JSON data into properly-typed table\n await loadJSONData(connection, tableName, filePath, reportLoadError);\n } else {\n // Check for schema file first (preserves constraints like PRIMARY KEY, UNIQUE, etc.)\n const schemaPath = join(dataDir, `${tableName}.schema.sql`);\n const { readFile, access } = await import('node:fs/promises');\n const { constants } = await import('node:fs');\n\n let schemaExists = false;\n try {\n await access(schemaPath, constants.F_OK);\n schemaExists = true;\n } catch {\n schemaExists = false;\n }\n\n if (schemaExists) {\n // Schema file exists - create table and load data now\n try {\n const schemaDDL = await readFile(schemaPath, 'utf-8');\n await connection.run(schemaDDL);\n await loadJSONData(\n connection,\n tableName,\n filePath,\n reportLoadError,\n );\n } catch (error) {\n console.warn(\n `[json-adapter] Could not load ${tableName} with schema file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n } else {\n // No schema file - either eager load with inferred schema or defer\n if (eagerLoadTables) {\n // Eager load: create table with inferred schema from JSON\n // This enables cross-table queries before syncSchema() is called\n // Note: Inferred schemas don't have UNIQUE constraints, so UPSERT won't work\n // until syncSchema() is called to upgrade the schema\n try {\n await connection.run(\n `CREATE TABLE IF NOT EXISTS \"${escapeQuotedIdentifier(tableName)}\" AS SELECT * FROM read_json_auto('${escapeStringLiteral(filePath)}')`,\n );\n inferredSchemaTables.add(tableName);\n console.log(\n `[json-adapter] Eagerly created ${tableName} with inferred schema (no UNIQUE constraints)`,\n );\n } catch (error) {\n // Deliberately NOT reported via reportLoadError (issue #1139):\n // CREATE TABLE AS SELECT is atomic, so any read_json_auto failure\n // leaves NO table — which surfaces loudly as a missing-table error\n // when queried, unlike the silent present-but-empty case that\n // getTableLoadErrors()/onTableLoadError target. If this branch ever\n // gains something like read_json(..., ignore_errors := true) that\n // could leave a partially-created/empty table, revisit that scope.\n console.warn(\n `[json-adapter] Could not eagerly load ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n } else {\n console.log(\n `[json-adapter] Deferring ${tableName} - will load after syncSchema() creates table`,\n );\n }\n // Always track for later schema upgrade and data reload\n pendingJSONFiles.set(tableName, filePath);\n }\n }\n }\n } catch (error) {\n // If directory doesn't exist or is empty, that's okay\n if ((error as any).code !== 'ENOENT') {\n // Log the error for debugging\n console.error('[json-adapter] Error loading JSON tables:', error);\n throw new DatabaseError(`Failed to load JSON tables from ${dataDir}`, {\n dataDir,\n originalError: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n }\n }\n}\n\n/**\n * Converts DuckDB-specific type representations to JavaScript types\n *\n * Handles three cases:\n * 1. JavaScript BigInt values → converted to numbers\n * 2. DuckDB hugeint objects ({ hugeint: number }) → converted to strings\n * 3. DuckDB timestamp objects ({ micros: number }) → converted to Date objects\n *\n * DuckDB represents HUGEINT (128-bit integers) as objects with a single\n * property called 'hugeint'. When UUID strings (stored as TEXT) are\n * exported to JSON via COPY TO, DuckDB sometimes converts them to hugeint\n * representation. This function converts them back to strings.\n *\n * DuckDB represents TIMESTAMP values as objects with a single property\n * called 'micros' containing microseconds since Unix epoch. This function\n * converts them to JavaScript Date objects for proper round-trip serialization.\n *\n * @param obj - Object that may contain BigInt, hugeint, or timestamp values\n * @returns Object with DuckDB types converted to JavaScript types\n */\nfunction convertBigInts(obj: any): any {\n if (obj === null || obj === undefined) return obj;\n if (typeof obj === 'bigint') return Number(obj);\n\n // Handle DuckDB hugeint objects: { hugeint: number }\n // Convert to string to preserve UUID/TEXT values\n if (\n typeof obj === 'object' &&\n !Array.isArray(obj) &&\n 'hugeint' in obj &&\n Object.keys(obj).length === 1\n ) {\n // Convert hugeint to string representation\n // For UUIDs and other TEXT values that DuckDB converted\n return String(obj.hugeint);\n }\n\n // Handle DuckDB timestamp objects: { micros: number | bigint }\n // Convert to JavaScript Date for proper round-trip serialization\n // Fixes issue #319: JSON adapter fails to serialize Date fields during UPSERT\n if (\n typeof obj === 'object' &&\n !Array.isArray(obj) &&\n 'micros' in obj &&\n Object.keys(obj).length === 1 &&\n (typeof obj.micros === 'number' || typeof obj.micros === 'bigint')\n ) {\n // Convert microseconds to milliseconds for Date constructor\n // DuckDB stores timestamps as microseconds since Unix epoch\n // Handle both number and bigint types (DuckDB can return either)\n const micros =\n typeof obj.micros === 'bigint' ? Number(obj.micros) : obj.micros;\n const milliseconds = micros / 1000;\n return new Date(milliseconds);\n }\n\n if (Array.isArray(obj)) return obj.map(convertBigInts);\n if (typeof obj === 'object') {\n const result: any = {};\n for (const key in obj) {\n result[key] = convertBigInts(obj[key]);\n }\n return result;\n }\n return obj;\n}\n\n/**\n * Creates a table from a schema definition with proper typing\n *\n * DuckDB has issues with type inference when DEFAULT values are empty strings.\n * This function explicitly casts all DEFAULT values to their column types to\n * prevent DuckDB from inferring ANY type.\n *\n * Additionally, DuckDB has a known limitation (issue #12684) where ON CONFLICT\n * clauses fail with UNIQUE INDEX but work with inline UNIQUE constraints.\n * This function converts UNIQUE indexes to inline constraints for compatibility.\n *\n * @param connection - DuckDB connection\n * @param tableName - Name of the table to create\n * @param schema - Schema definition with DDL and indexes\n */\nasync function createTableFromSchema(\n connection: any,\n tableName: string,\n schema: import('./shared/types').SchemaProvider,\n): Promise<void> {\n // Extract CREATE TABLE statement (without triggers which aren't supported)\n const ddlLines = schema.ddl.split('\\n');\n const createTableEnd = ddlLines.findIndex((line) => {\n const trimmed = line.trim();\n return trimmed === ')' || trimmed.startsWith(');');\n });\n\n if (createTableEnd === -1) {\n throw new DatabaseError('Invalid schema DDL - no closing parenthesis', {\n tableName,\n ddl: schema.ddl,\n });\n }\n\n // Get CREATE TABLE statement\n let createTableSQL = ddlLines.slice(0, createTableEnd + 1).join('\\n');\n\n // Fix DEFAULT values for DuckDB type inference\n // DuckDB infers ANY type when DEFAULT is an empty string without explicit cast\n // Replace patterns like: DEFAULT '' with DEFAULT CAST('' AS TEXT)\n createTableSQL = createTableSQL.replace(\n /\\b(\\w+)\\s+(TEXT|VARCHAR)\\s+DEFAULT\\s+''/g,\n \"$1 $2 DEFAULT CAST('' AS $2)\",\n );\n\n // Also handle DEFAULT NULL cases to ensure proper typing\n createTableSQL = createTableSQL.replace(\n /\\b(\\w+)\\s+(TEXT|VARCHAR)\\s+DEFAULT\\s+NULL/g,\n '$1 $2 DEFAULT CAST(NULL AS $2)',\n );\n\n // Convert UNIQUE indexes to inline UNIQUE constraints for DuckDB compatibility\n // DuckDB's ON CONFLICT requires inline constraints, not separate indexes\n const transformed = convertUniqueIndexesToInlineConstraints(\n createTableSQL,\n schema.indexes,\n );\n createTableSQL = transformed.ddl;\n const indexesToCreate = transformed.indexes;\n\n try {\n await connection.run(createTableSQL);\n\n // Get actual columns after table creation\n let existingColumns: Set<string> = new Set();\n try {\n const schemaInfo = await connection.runAndReadAll(\n `DESCRIBE ${tableName}`,\n );\n const columns = schemaInfo.getRowObjects();\n existingColumns = new Set(\n columns.map((col: any) => col.column_name.toLowerCase()),\n );\n\n console.log(`[json-adapter] Created ${tableName} with schema:`, {\n columns: columns.map((col: any) => ({\n name: col.column_name,\n type: col.column_type,\n null: col.null,\n })),\n });\n } catch (error) {\n // Schema verification failed, but table was created - log warning and continue\n console.warn(\n `[json-adapter] Could not verify schema for ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Add columns from fields that aren't in the DDL (important for STI subclass fields)\n if (schema.fields && existingColumns.size > 0) {\n // Handle both Map and Record formats\n const fieldEntries =\n schema.fields instanceof Map\n ? Array.from(schema.fields.entries())\n : Object.entries(schema.fields);\n\n for (const [fieldName, fieldDef] of fieldEntries) {\n const columnName = fieldName.toLowerCase();\n if (!existingColumns.has(columnName)) {\n // Determine SQL type from field definition\n const sqlType = fieldDef?.type || 'TEXT';\n try {\n await connection.run(\n `ALTER TABLE ${tableName} ADD COLUMN \"${fieldName}\" ${sqlType}`,\n );\n console.log(\n `[json-adapter] Added missing column '${fieldName}' (${sqlType}) to ${tableName}`,\n );\n } catch (alterError) {\n console.warn(\n `[json-adapter] Could not add column '${fieldName}' to ${tableName}: ${alterError instanceof Error ? alterError.message : String(alterError)}`,\n );\n }\n }\n }\n }\n\n // Create remaining indexes (non-UNIQUE indexes only)\n // UNIQUE indexes have been converted to inline constraints\n if (indexesToCreate && indexesToCreate.length > 0) {\n for (const indexSQL of indexesToCreate) {\n try {\n await connection.run(indexSQL);\n } catch (error) {\n // Index creation might fail if column doesn't exist or syntax incompatibility\n console.warn(\n `[json-adapter] Failed to create index: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n } catch (error) {\n throw new DatabaseError('Failed to create table from schema', {\n tableName,\n sql: createTableSQL,\n originalError: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Reads the column names of an existing table, keyed by lowercase name\n *\n * The table-name predicate is case-insensitive: DuckDB resolves identifiers\n * case-insensitively, so a data file named `Items.json` can back a table\n * created as `items`. Matching table_name exactly would find no columns and\n * make the loader wrongly report that every field is unmatched (issue #1132).\n *\n * @param connection - DuckDB connection\n * @param tableName - Name of the table to describe\n * @returns Map of lowercase column name to the column's declared name\n */\nasync function getTableColumns(\n connection: any,\n tableName: string,\n): Promise<Map<string, string>> {\n const reader = await connection.runAndReadAll(\n 'SELECT column_name FROM information_schema.columns WHERE LOWER(table_name) = LOWER($1) ORDER BY ordinal_position',\n [tableName],\n );\n\n return new Map(\n reader\n .getRowObjects()\n .map((row: any) => [\n String(row.column_name).toLowerCase(),\n String(row.column_name),\n ]),\n );\n}\n\n/**\n * Inserts records with proper CAST handling for empty strings\n *\n * This helper avoids using read_json() with auto_detect, which causes DuckDB\n * to re-infer column types from data (causing ANY type for empty strings).\n * Instead, it uses parameterized INSERT with CAST to maintain table schema.\n *\n * Used only for loading a JSON data file, where records having different key\n * sets is normal input rather than a caller error: a document with an optional\n * field omits that key entirely on the records that lack it. Columns are\n * therefore unioned across ALL records and any record missing a key binds NULL.\n * Deriving them from the first record alone bound `undefined` for a missing key\n * (which DuckDB rejects, losing every row) and dropped keys the first record\n * did not have (losing the column silently). See issue #1132.\n *\n * The caller-facing insert() API is a separate implementation (it resolves\n * columns through `resolveInsertColumns`) and is not affected by this\n * file-loading tolerance.\n *\n * @param connection - DuckDB connection\n * @param tableName - Name of the table to insert into\n * @param records - Array of records to insert\n * @returns Number of records inserted (0 if there was nothing loadable)\n * @throws DatabaseError if the resolved records cannot be inserted\n */\nasync function insertRecordsWithCast(\n connection: any,\n tableName: string,\n records: unknown[],\n): Promise<number> {\n if (records.length === 0) return 0;\n\n // Only object records contribute columns AND rows. A non-object element\n // (e.g. a `null` hole in the array) must be skipped entirely - iterating it\n // for values would emit a phantom all-NULL row.\n const objectRecords = records.filter(\n (record): record is Record<string, any> =>\n record != null && typeof record === 'object' && !Array.isArray(record),\n );\n\n if (objectRecords.length === 0) {\n return 0;\n }\n\n // Union keys across every record, preserving first-appearance order\n const unionedKeys: string[] = [];\n const seenKeys = new Set<string>();\n for (const record of objectRecords) {\n for (const key of Object.keys(record)) {\n if (!seenKeys.has(key)) {\n seenKeys.add(key);\n unionedKeys.push(key);\n }\n }\n }\n\n if (unionedKeys.length === 0) {\n console.warn(\n `[json-adapter] Skipping data load for ${tableName}: records have no fields`,\n );\n return 0;\n }\n\n // Resolve keys against the table's real columns. A JSON field with no\n // matching column cannot be inserted; report it instead of failing the whole\n // load, so one stray field does not cost every row. Column matching is\n // case-insensitive (DuckDB folds identifier case), so track which columns are\n // already claimed - two case-variant keys (`id` and `ID`) must not both emit\n // the same column, which would produce a duplicate-column INSERT.\n const tableColumns = await getTableColumns(connection, tableName);\n const keys: string[] = [];\n const columnNames: string[] = [];\n const unmatchedKeys: string[] = [];\n const claimedColumns = new Set<string>();\n\n for (const key of unionedKeys) {\n const column = tableColumns.get(key.toLowerCase());\n if (column === undefined || claimedColumns.has(column.toLowerCase())) {\n // No matching column, or a differently-cased key already claimed it\n unmatchedKeys.push(key);\n } else {\n claimedColumns.add(column.toLowerCase());\n keys.push(key);\n columnNames.push(column);\n }\n }\n\n if (unmatchedKeys.length > 0) {\n console.warn(\n `[json-adapter] Ignoring ${unmatchedKeys.length} field(s) in ${tableName} data with no matching column: ${unmatchedKeys.join(', ')}`,\n );\n }\n\n if (keys.length === 0) {\n throw new DatabaseError(\n `No fields in the ${tableName} data file match a column on the table`,\n {\n tableName,\n fields: unionedKeys,\n columns: [...tableColumns.values()],\n },\n );\n }\n\n // A record that carries none of the matched keys would insert a row of all\n // NULLs - which is not data the file meant to provide and can violate a NOT\n // NULL / PRIMARY KEY column, failing the whole batch. Skip such records so a\n // stray record, like a stray field, does not cost the rest of the load.\n // (keys is non-empty here, and every key came from some record, so at least\n // that record survives - insertableRecords cannot be empty.)\n const insertableRecords = objectRecords.filter((record) =>\n keys.some((key) => key in record),\n );\n\n // Build placeholders - schema has NOT NULL DEFAULT '' to prevent ANY type\n const values: any[] = [];\n let paramIdx = 1;\n\n const placeholders = insertableRecords\n .map((record) => {\n const rowPlaceholders = keys.map((key) => {\n const value = record[key];\n\n // `undefined` covers a key this record omits; binding it directly makes\n // DuckDB reject the whole INSERT (\"Cannot create values of type ANY\"),\n // so bind NULL for the missing field, exactly as duckdb.ts does.\n if (value === null || value === undefined) {\n return 'NULL';\n } else if (value === '' && typeof value === 'string') {\n // CAST empty strings to TEXT to prevent DuckDB ANY type inference on parameters\n values.push(value);\n return `CAST($${paramIdx++} AS TEXT)`;\n } else if (value instanceof Date) {\n // Convert Date objects to ISO strings for DuckDB\n // CAST to TIMESTAMP to prevent DuckDB ANY type inference (issue #540)\n values.push(value.toISOString());\n return `CAST($${paramIdx++} AS TIMESTAMP)`;\n } else if (Array.isArray(value)) {\n // CAST arrays to JSON to prevent DuckDB ANY type inference\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx++} AS JSON)`;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n // CAST plain objects to JSON to prevent DuckDB ANY type inference\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx++} AS JSON)`;\n } else {\n // Direct parameter binding for other values\n values.push(value);\n return `$${paramIdx++}`;\n }\n });\n return `(${rowPlaceholders.join(', ')})`;\n })\n .join(', ');\n\n // Use the table's own column names - they came from information_schema, so\n // no JSON-supplied identifier reaches the statement\n const columnList = columnNames.map((name) => `\"${name}\"`).join(', ');\n const sql = `INSERT INTO \"${tableName}\" (${columnList}) VALUES ${placeholders}`;\n\n try {\n await connection.run(sql, values);\n } catch (e) {\n throw new DatabaseError('Failed to load records into table', {\n tableName,\n recordCount: insertableRecords.length,\n columns: columnNames,\n originalError: formatDbError(e),\n });\n }\n\n return insertableRecords.length;\n}\n\n/**\n * Validates that a table name is valid (alphanumeric + underscores only)\n *\n * @param table - Table name to validate\n * @throws DatabaseError if table name is invalid\n */\nfunction validateTableName(table: string): void {\n // Reject non-strings before the regex: `test` coerces via `toString`, so an\n // object with a stateful `toString` could pass here and interpolate a\n // different name into SQL. See shared/alter-utils.ts:validateTableName.\n if (typeof table !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {\n throw new DatabaseError(`Invalid table name: ${table}`, { table });\n }\n}\n\n/**\n * Creates a JSON database adapter using DuckDB in-memory engine\n *\n * This adapter provides SQL query capabilities over JSON files without creating\n * persistent database files or WAL files. All data is stored in-memory during\n * runtime and written back to JSON files based on the write strategy.\n *\n * @param options - JSON database options\n * @returns Database interface for JSON files\n */\nexport async function getDatabase(\n options: JSONOptions,\n): Promise<DatabaseInterface> {\n // Generate cache key from URL and options that affect database behavior\n // FIX for issues #533/#534: Auto-generate dbid from URL when schemas provided\n // This ensures connection caching ALWAYS happens for the same URL, preventing\n // the lost update bug where multiple connections overwrite each other's data.\n //\n // Priority:\n // 1. Explicit dbid from caller\n // 2. Auto-generated from URL + writeStrategy (ensures caching)\n const cacheKey =\n options.dbid ||\n `json:${options.url}:${options.writeStrategy || 'immediate'}:${options.autoRegister !== false}`;\n return memoryConnectionCache.getOrCreate(\n cacheKey,\n options,\n () => createTrackedJSONDatabase(options),\n closeJSONDatabase,\n isCachedJSONDatabaseFresh,\n );\n}\n\nasync function createTrackedJSONDatabase(\n options: JSONOptions,\n): Promise<DatabaseInterface> {\n const trackExternalChanges = options.autoRegister !== false;\n const dataDir = options.url;\n\n if (!trackExternalChanges || !dataDir) {\n const db = await createJSONDatabase(options);\n setJSONSourceCacheState(db, {\n activeWrites: new Set(),\n closing: false,\n dataDir: dataDir ?? '',\n fingerprint: undefined,\n sourceWriteTail: Promise.resolve(),\n trackExternalChanges: false,\n });\n return db;\n }\n\n for (let attempt = 0; attempt < SOURCE_SNAPSHOT_ATTEMPTS; attempt += 1) {\n const before = await getJSONSourceFingerprint(dataDir);\n const db = await createJSONDatabase(options);\n try {\n const after = await getJSONSourceFingerprint(dataDir);\n if (before === after) {\n setJSONSourceCacheState(db, {\n activeWrites: new Set(),\n closing: false,\n dataDir,\n fingerprint: after,\n sourceWriteTail: Promise.resolve(),\n trackExternalChanges: true,\n });\n return db;\n }\n } catch (error) {\n await closeJSONDatabase(db);\n throw error;\n }\n await closeJSONDatabase(db);\n }\n\n throw new DatabaseError(\n 'JSON source files did not remain stable while creating the database',\n { dataDir },\n );\n}\n\nasync function createJSONDatabase(\n options: JSONOptions,\n): Promise<DatabaseInterface> {\n return (async () => {\n const {\n instance,\n connection,\n pendingJSONFiles,\n inferredSchemaTables,\n tableLoadErrors,\n reportLoadError,\n } = await createJSONConnection(options);\n const writeStrategy = options.writeStrategy || 'immediate';\n const { url } = options;\n let db!: DatabaseInterface;\n\n // One lock per connection. `getDatabase` hands cached callers this same\n // closure, so \"created here\" and \"per connection\" are the same scope —\n // including across duplicated module copies, since the connection cache\n // itself lives on globalThis.\n const connectionLock = createTransactionLock(\n 'json',\n options.transactionQueueTimeout,\n );\n\n /**\n * Inserts one or more records into a table\n *\n * @param table - Table name\n * @param data - Single record or array of records to insert\n * @returns Promise resolving to operation result\n * @throws Error if the insert operation fails\n */\n const insert = async (\n table: string,\n data: Record<string, any> | Record<string, any>[],\n ): Promise<QueryResult> => {\n validateTableName(table);\n\n // Enforce read-only mode\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot insert: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n\n const records = Array.isArray(data) ? data : [data];\n\n if (records.length === 0) {\n return { operation: 'insert', affected: 0 };\n }\n\n const keys = resolveInsertColumns(table, records);\n validateColumnNames(keys);\n\n // Build placeholders with CAST for empty strings\n const values: any[] = [];\n let paramIdx = 1;\n\n const placeholders = records\n .map((record) => {\n const rowPlaceholders = keys.map((key) => {\n const value = record[key];\n\n // DuckDB cannot bind `undefined`, so map it to NULL as duckdb.ts\n // does rather than failing the insert.\n if (value === null || value === undefined) {\n return 'NULL';\n } else if (value === '' && typeof value === 'string') {\n // CAST empty strings to TEXT to prevent DuckDB ANY type inference\n values.push(value);\n return `CAST($${paramIdx++} AS TEXT)`;\n } else if (value instanceof Date) {\n // Convert Date objects to ISO strings for DuckDB (issue #319)\n // CAST to TIMESTAMP to prevent DuckDB ANY type inference (issue #540)\n values.push(value.toISOString());\n return `CAST($${paramIdx++} AS TIMESTAMP)`;\n } else if (Array.isArray(value)) {\n // CAST arrays to JSON to prevent DuckDB ANY type inference\n // DuckDB cannot infer array element types from empty arrays or mixed types\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx++} AS JSON)`;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n // CAST plain objects to JSON to prevent DuckDB ANY type inference\n // Only applies to plain objects (not class instances)\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx++} AS JSON)`;\n } else {\n // Direct parameter binding for other values\n values.push(value);\n return `$${paramIdx++}`;\n }\n });\n return `(${rowPlaceholders.join(', ')})`;\n })\n .join(', ');\n\n const sql = `INSERT INTO \"${table}\" (${keys.join(', ')}) VALUES ${placeholders}`;\n\n try {\n await connection.run(sql, values);\n const affected = records.length;\n\n // Handle write-back strategy\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n return { operation: 'insert', affected };\n } catch (e) {\n throw new DatabaseError('Failed to insert records into table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Retrieves a single record matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records\n * @returns Promise resolving to matching record or null if not found\n * @throws Error if the query fails\n */\n const get = async (\n table: string,\n where: Record<string, any>,\n ): Promise<Record<string, any> | null> => {\n validateTableName(table);\n\n const { sql: whereClause, values } = buildWhere(where, 1, 'json');\n const sql = `SELECT * FROM \"${table}\" ${whereClause} LIMIT 1`;\n\n try {\n const reader = await connection.runAndReadAll(sql, values);\n const rows = reader.getRowObjects();\n return rows.length > 0 ? convertBigInts(rows[0]) : null;\n } catch (e) {\n throw new DatabaseError('Failed to retrieve record from table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Retrieves multiple records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records\n * @returns Promise resolving to array of matching records\n * @throws Error if the query fails\n */\n const list = async (\n table: string,\n where: Record<string, any>,\n ): Promise<Record<string, any>[]> => {\n validateTableName(table);\n\n const { sql: whereClause, values } = buildWhere(where, 1, 'json');\n const sql = `SELECT * FROM \"${table}\" ${whereClause}`;\n\n try {\n const reader = await connection.runAndReadAll(sql, values);\n return convertBigInts(reader.getRowObjects());\n } catch (e) {\n throw new DatabaseError('Failed to list records from table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Updates records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records to update\n * @param data - New data to set\n * @returns Promise resolving to operation result\n * @throws Error if the update operation fails\n */\n const update = async (\n table: string,\n where: Record<string, any>,\n data: Record<string, any>,\n ): Promise<QueryResult> => {\n validateTableName(table);\n\n // Enforce read-only mode\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot update: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n\n const keys = Object.keys(data);\n validateColumnNames(keys);\n const setClause = keys\n .map((key, idx) => `${key} = $${idx + 1}`)\n .join(', ');\n const { sql: whereClause, values: whereValues } = buildWhere(\n where,\n keys.length + 1,\n 'json',\n );\n\n // Read values through the validated key list rather than a second\n // enumeration of `data`. `Object.values` on a hostile Proxy could return\n // values in a different order (or for different keys) than the `keys`\n // that drive `setClause`, binding each value to the wrong column.\n // Convert Date objects to ISO strings for DuckDB (issue #319)\n const dataValues = keys.map((key) => {\n const value = data[key];\n return value instanceof Date ? value.toISOString() : value;\n });\n\n const sql = `UPDATE \"${table}\" SET ${setClause} ${whereClause}`;\n const values = [...dataValues, ...whereValues];\n\n try {\n await connection.run(sql, values);\n\n // Handle write-back strategy\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n // DuckDB doesn't return rowsAffected in the same way, estimate from where clause\n return { operation: 'update', affected: 1 };\n } catch (e) {\n throw new DatabaseError('Failed to update records in table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n const buildJSONValueExpression = (\n value: any,\n values: any[],\n paramIdx: { value: number },\n ): string => {\n if (value === null) {\n return 'NULL';\n }\n if (value === '' && typeof value === 'string') {\n values.push(value);\n return `CAST($${paramIdx.value++} AS TEXT)`;\n }\n if (value instanceof Date) {\n values.push(value.toISOString());\n return `CAST($${paramIdx.value++} AS TIMESTAMP)`;\n }\n if (Array.isArray(value)) {\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx.value++} AS JSON)`;\n }\n if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx.value++} AS JSON)`;\n }\n\n values.push(value);\n return `$${paramIdx.value++}`;\n };\n\n const executeNullAwareJSONUpsert = async (\n table: string,\n conflictColumns: string[],\n data: Record<string, any>,\n ): Promise<QueryResult> => {\n const conflictWhere = Object.fromEntries(\n conflictColumns.map((col) => [col, data[col]]),\n );\n const existing = await get(table, conflictWhere);\n\n if (!existing) {\n return insert(table, data);\n }\n\n const keys = Object.keys(data);\n const values: any[] = [];\n const paramIdx = { value: 1 };\n const assignments = keys.map((key) => {\n const valueExpr = buildJSONValueExpression(data[key], values, paramIdx);\n return `\"${escapeQuotedIdentifier(key)}\" = ${valueExpr}`;\n });\n const { sql: whereClause, values: whereValues } = buildWhere(\n conflictWhere,\n paramIdx.value,\n 'json',\n );\n const sql = `UPDATE \"${table}\" SET ${assignments.join(', ')} ${whereClause}`;\n\n try {\n await connection.run(sql, [...values, ...whereValues]);\n\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n return { operation: 'upsert', affected: 1 };\n } catch (e) {\n throw new DatabaseError('Failed to upsert record into table', {\n table,\n sql,\n values: [...values, ...whereValues],\n conflictColumns,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Inserts a record or updates it if it already exists (UPSERT)\n *\n * @param table - Table name\n * @param conflictColumns - Columns that define the uniqueness constraint\n * @param data - Data to insert or update\n * @returns Promise resolving to operation result\n * @throws Error if the upsert operation fails\n */\n const upsert = async (\n table: string,\n conflictColumns: string[],\n data: Record<string, any>,\n options?: UpsertOptions,\n ): Promise<QueryResult> => {\n validateTableName(table);\n\n // Enforce read-only mode\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot upsert: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n\n // Snapshot the record once. Every read below — the presence check, the\n // column list, the value list, and the null-aware branch — must see the\n // same keys and values, or a hostile object could present one shape to\n // the checks and another to the interpolated `keys`. A plain-object copy\n // cannot observe how many times it is read.\n const record = { ...data };\n // Coerce the conflict columns to primitive strings once, for the same\n // reason as `record`. These are quoted rather than validated (names that\n // need quotes keep working), and `escapeQuotedIdentifier` must run on a\n // primitive. Coercing here also pins the value the presence check sees to\n // the value the `ON CONFLICT` list escapes, so a crafted object cannot read\n // as `id` for the check and as unescaped SQL for the SQL.\n const conflictCols = conflictColumns.map((col) => String(col));\n // Conflict columns reach two very different positions. In the plain\n // `ON CONFLICT(...)` path below they are quoted, so a name that needs\n // quotes — `Full Name`, `user-id`, whatever the source JSON called its\n // keys — is ordinary and has always worked; escaping keeps it working\n // while making it impossible to leave the quotes. In the null-aware path\n // they become `buildWhere` keys instead, which requires a plain\n // identifier and rejects them there, as it did before this change.\n // Validate that all conflict columns are present in the data\n const missingColumns = conflictCols.filter((col) => !(col in record));\n\n if (missingColumns.length > 0) {\n throw new DatabaseError('Conflict columns missing from data', {\n table,\n conflictColumns: conflictCols,\n missingColumns,\n availableColumns: Object.keys(record),\n hint: 'All columns specified in ON CONFLICT must be present in the data being inserted. Undefined values should be replaced with null or an appropriate default.',\n });\n }\n\n if (\n !options?.nullsDistinct &&\n conflictCols.some((col) => record[col] === null)\n ) {\n return executeNullAwareJSONUpsert(table, conflictCols, record);\n }\n\n const keys = Object.keys(record);\n const dataValues = Object.values(record);\n\n // Build placeholders - schema has NOT NULL DEFAULT '' to prevent ANY type\n const placeholders: string[] = [];\n const values: any[] = [];\n let paramIdx = 1;\n\n for (const value of dataValues) {\n if (value === null) {\n placeholders.push('NULL');\n } else if (value === '' && typeof value === 'string') {\n // CAST empty strings to TEXT to prevent DuckDB ANY type inference on parameters\n placeholders.push(`CAST($${paramIdx} AS TEXT)`);\n values.push(value);\n paramIdx++;\n } else if (value instanceof Date) {\n // Convert Date objects to ISO strings for DuckDB\n // CAST to TIMESTAMP to prevent DuckDB ANY type inference (issue #540)\n placeholders.push(`CAST($${paramIdx} AS TIMESTAMP)`);\n values.push(value.toISOString());\n paramIdx++;\n } else if (Array.isArray(value)) {\n // CAST arrays to JSON to prevent DuckDB ANY type inference\n // DuckDB cannot infer array element types from empty arrays or mixed types\n placeholders.push(`CAST($${paramIdx} AS JSON)`);\n values.push(JSON.stringify(value));\n paramIdx++;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n // CAST plain objects to JSON to prevent DuckDB ANY type inference\n // Only applies to plain objects (not class instances)\n placeholders.push(`CAST($${paramIdx} AS JSON)`);\n values.push(JSON.stringify(value));\n paramIdx++;\n } else {\n // Direct parameter binding for other values\n placeholders.push(`$${paramIdx}`);\n values.push(value);\n paramIdx++;\n }\n }\n\n // Build UPDATE SET clause with same approach\n // DO NOT reset paramIdx - parameters must be unique across entire query\n const updateSetParts: string[] = [];\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n const value = dataValues[i];\n\n if (value === null) {\n updateSetParts.push(`${key} = NULL`);\n } else if (value === '' && typeof value === 'string') {\n // CAST empty strings to TEXT to prevent DuckDB ANY type inference on parameters\n updateSetParts.push(`${key} = CAST($${paramIdx} AS TEXT)`);\n values.push(value);\n paramIdx++;\n } else if (value instanceof Date) {\n // Convert Date objects to ISO strings for DuckDB\n // CAST to TIMESTAMP to prevent DuckDB ANY type inference (issue #540)\n updateSetParts.push(`${key} = CAST($${paramIdx} AS TIMESTAMP)`);\n values.push(value.toISOString());\n paramIdx++;\n } else if (Array.isArray(value)) {\n // CAST arrays to JSON to prevent DuckDB ANY type inference\n updateSetParts.push(`${key} = CAST($${paramIdx} AS JSON)`);\n values.push(JSON.stringify(value));\n paramIdx++;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n // CAST plain objects to JSON to prevent DuckDB ANY type inference\n updateSetParts.push(`${key} = CAST($${paramIdx} AS JSON)`);\n values.push(JSON.stringify(value));\n paramIdx++;\n } else {\n // Direct parameter binding for other values\n updateSetParts.push(`${key} = $${paramIdx}`);\n values.push(value);\n paramIdx++;\n }\n }\n\n // Quote ALL column names to match DuckDB's schema generation\n // SchemaGenerator always quotes column names, so UPSERT must match\n const conflict = conflictCols\n .map((col) => `\"${escapeQuotedIdentifier(col)}\"`)\n .join(', ');\n const quotedKeys = keys\n .map((key) => `\"${escapeQuotedIdentifier(key)}\"`)\n .join(', ');\n\n // Quote column names in UPDATE SET clause to match schema and ON CONFLICT\n // Extract the value expression from each updateSetPart (everything after '=')\n const quotedUpdateSetParts = keys.map((key, i) => {\n const part = updateSetParts[i];\n const valueExpr = part.substring(part.indexOf('=') + 1).trim();\n return `\"${escapeQuotedIdentifier(key)}\" = ${valueExpr}`;\n });\n\n const sql = `INSERT INTO \"${table}\" (${quotedKeys}) VALUES (${placeholders.join(', ')}) ON CONFLICT(${conflict}) DO UPDATE SET ${quotedUpdateSetParts.join(', ')}`;\n\n try {\n await connection.run(sql, values);\n\n // Handle write-back strategy\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n return { operation: 'upsert', affected: 1 };\n } catch (e) {\n // Log detailed error information for debugging\n console.error('UPSERT failed:', {\n table,\n sql,\n values,\n valueTypes: values.map((v) => `${typeof v} (${v})`),\n conflictColumns: conflictCols,\n error: formatDbError(e),\n });\n throw new DatabaseError('Failed to upsert record into table', {\n table,\n sql,\n values,\n conflictColumns: conflictCols,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Gets a record matching the where criteria or inserts it if not found\n *\n * @param table - Table name\n * @param where - Criteria to match existing record\n * @param data - Data to insert if no record found\n * @returns Promise resolving to the record (either retrieved or newly inserted)\n * @throws Error if the operation fails\n */\n const getOrInsert = async (\n table: string,\n where: Record<string, any>,\n data: Record<string, any>,\n ): Promise<Record<string, any>> => {\n const result = await get(table, where);\n if (result) return result;\n\n await insert(table, data);\n const inserted = await get(table, where);\n\n if (!inserted) {\n throw new DatabaseError('Failed to insert and retrieve record', {\n table,\n where,\n data,\n });\n }\n\n return inserted;\n };\n\n /**\n * Deletes records from a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records for deletion\n * @returns Promise resolving to operation result with count of deleted rows\n * @throws Error if the delete operation fails\n */\n const deleteRecords = async (\n table: string,\n where: Record<string, any>,\n ): Promise<QueryResult> => {\n validateTableName(table);\n\n // Enforce read-only mode\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot delete: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n\n const keys = Object.keys(where);\n if (keys.length === 0) {\n throw new DatabaseError(\n 'DELETE requires at least one WHERE condition to prevent accidental deletion of all records',\n { table },\n );\n }\n\n const { sql: whereClause, values } = buildWhere(where, 1, 'json');\n const sql = `DELETE FROM \"${table}\" ${whereClause}`;\n\n try {\n await connection.run(sql, values);\n\n // Handle write-back strategy\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n return { operation: 'delete', affected: 1 };\n } catch (e) {\n throw new DatabaseError('Failed to delete records from table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Counts records in a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (optional, counts all if omitted)\n * @returns Promise resolving to count of matching records\n * @throws Error if the count operation fails\n */\n const count = async (\n table: string,\n where?: Record<string, any>,\n ): Promise<number> => {\n validateTableName(table);\n\n try {\n if (!where || Object.keys(where).length === 0) {\n // Count all records\n const result = await connection.runAndReadAll(\n `SELECT COUNT(*) as count FROM \"${table}\"`,\n );\n const rows = result.getRowObjects();\n return Number(rows[0]?.count) || 0;\n }\n\n // Count with conditions\n const { sql: whereClause, values } = buildWhere(where, 1, 'json');\n const sql = `SELECT COUNT(*) as count FROM \"${table}\" ${whereClause}`;\n\n const result = await connection.runAndReadAll(sql, values);\n const rows = result.getRowObjects();\n\n return Number(rows[0]?.count) || 0;\n } catch (e) {\n throw new DatabaseError('Failed to count records in table', {\n table,\n where,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Checks if a table exists in the database\n *\n * @param tableName - Name of the table to check\n * @returns Promise resolving to boolean indicating if the table exists\n */\n const tableExists = async (tableName: string): Promise<boolean> => {\n // The name is interpolated into a statement, and this engine executes\n // every statement in the string it is given, so an unvalidated name here\n // is a DDL/DML sink rather than a read oracle — the `catch` below\n // swallows the error, not the execution. Validation runs outside the try\n // for that reason: a rejected identifier must be reported, not reported\n // as `false`.\n validateTableName(tableName);\n try {\n // Try to query the table\n await connection.runAndReadAll(`SELECT * FROM ${tableName} LIMIT 1`);\n return true;\n } catch {\n return false;\n }\n };\n\n /**\n * Exports a table to JSON and schema files\n *\n * Exports two files:\n * 1. {table}.json - Table data with columns cast appropriately\n * 2. {table}.schema.sql - CREATE TABLE statement to preserve constraints\n *\n * Note: JSON columns are NOT cast to TEXT to preserve object structure.\n * Other columns are cast to TEXT to prevent DuckDB from converting UUIDs\n * and other TEXT values to hugeint representation in JSON output.\n *\n * @param connection - DuckDB connection\n * @param table - Table name\n * @param dataDir - Directory to write files\n */\n const exportTableToJSON = async (\n connection: any,\n table: string,\n dataDir: string,\n ): Promise<void> => {\n // Every caller-facing path into this function takes a table name, and it\n // is interpolated into a COPY statement's identifier and into the target\n // file path. Unvalidated, `exportTable('t\") TO $$/etc/x$$; DROP ...; --')`\n // both writes a file outside the data directory and runs the trailing\n // statements. Validate at this choke point so the internal callers that\n // pass catalog-derived names are covered too.\n validateTableName(table);\n\n const filePath = join(dataDir, `${table}.json`);\n const schemaPath = join(dataDir, `${table}.schema.sql`);\n\n // Export schema first using SHOW CREATE TABLE\n try {\n const schemaResult = await connection.runAndReadAll(\n `SELECT sql FROM sqlite_master WHERE type='table' AND name='${table}'`,\n );\n const rows = schemaResult.getRowObjects();\n if (rows.length > 0 && rows[0].sql) {\n const { writeFile } = await import('node:fs/promises');\n await writeFile(schemaPath, rows[0].sql, 'utf-8');\n }\n } catch (error) {\n console.warn(\n `[json-adapter] Could not export schema for ${table}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Get column names and types to handle JSON columns specially\n // JSON columns should NOT be cast to TEXT to preserve object structure (fixes #542)\n const columnsResult = await connection.runAndReadAll(\n `SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${table}' ORDER BY ordinal_position`,\n );\n const columnInfo = columnsResult.getRowObjects() as {\n column_name: string;\n data_type: string;\n }[];\n\n if (columnInfo.length === 0) {\n // Fallback to SELECT * if we can't get column names\n await connection.run(\n `COPY (SELECT * FROM \"${table}\") TO '${filePath}' (FORMAT JSON, ARRAY true)`,\n );\n return;\n }\n\n // Preserve type-appropriate columns, only cast text-based columns to TEXT\n // - JSON/STRUCT: preserve as-is for object structure (fixes #542)\n // - Numeric types: preserve as-is so numbers export as numbers (fixes #694)\n // - Boolean: preserve as-is\n // - Text-based: cast to TEXT to prevent DuckDB hugeint conversion for UUIDs\n const selectList = columnInfo\n .map(({ column_name, data_type }) => {\n const upperType = data_type.toUpperCase();\n // Preserve JSON/STRUCT as-is for object structure\n if (upperType === 'JSON' || upperType.startsWith('STRUCT')) {\n return `\"${column_name}\"`;\n }\n // Preserve numeric types as-is so they export as numbers, not strings\n if (\n [\n 'DOUBLE',\n 'REAL',\n 'FLOAT',\n 'INTEGER',\n 'BIGINT',\n 'SMALLINT',\n 'TINYINT',\n 'DECIMAL',\n 'NUMERIC',\n 'HUGEINT',\n 'UBIGINT',\n 'UINTEGER',\n 'USMALLINT',\n 'UTINYINT',\n ].some((t) => upperType.includes(t))\n ) {\n return `\"${column_name}\"`;\n }\n // Preserve boolean types as-is\n if (upperType === 'BOOLEAN' || upperType === 'BOOL') {\n return `\"${column_name}\"`;\n }\n // Cast text-based columns to TEXT to prevent hugeint conversion for UUIDs\n return `CAST(\"${column_name}\" AS TEXT) AS \"${column_name}\"`;\n })\n .join(', ');\n const sql = `COPY (SELECT ${selectList} FROM \"${table}\") TO '${filePath}' (FORMAT JSON, ARRAY true)`;\n\n await connection.run(sql);\n };\n\n const persistTableToJSON = (table: string): Promise<void> =>\n trackJSONSourceWrite(db, table, () =>\n exportTableToJSON(connection, table, url),\n );\n\n /**\n * Manual export method for 'manual' write strategy\n *\n * Allows explicit control over when tables are written back to JSON files.\n *\n * @param table - Table name to export\n * @returns Promise that resolves when export completes\n */\n const exportTable = async (table: string): Promise<void> => {\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot export table: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n await persistTableToJSON(table);\n };\n\n /**\n * Returns the table-load failures collected for this connection\n *\n * A JSON data file that parses but whose records cannot be inserted into a\n * table created for it (a renamed/dropped column, a NOT NULL / PRIMARY KEY\n * violation, or a file whose fields match no column) leaves that table\n * present-but-empty and is logged to stderr; calling code otherwise cannot\n * tell it apart from a legitimately empty one. This exposes those failures\n * so a caller can detect and react to them (issue #1139).\n *\n * Scope: this covers loads into a table created from a provided schema, a\n * `.schema.sql` file, or deferred `syncSchema()` / `execute()` DDL. A file\n * with no schema at all is loaded by DuckDB's own inference; if that fails\n * the table is simply not created, which already surfaces as an error when\n * you query it, so it is not reported here.\n *\n * The list accumulates across the connection's lifetime — covering both\n * connection-time and later deferred loads — and is append-only (a later\n * successful reload of the same table does not remove an earlier failure).\n * A fresh array is returned each call, so mutating it does not affect the\n * adapter's state. Because connections are cached per URL, callers sharing a\n * URL share this list.\n *\n * @returns Table-load failures, oldest first\n */\n const getTableLoadErrors = (): JSONTableLoadError[] => [...tableLoadErrors];\n\n /**\n * Creates a table by inferring schema from a JSON file\n *\n * This utility is for users who want automatic schema inference from JSON.\n * Note: Inferred schemas do NOT include UNIQUE constraints, so UPSERT\n * operations will fail. Use syncSchema() with explicit DDL for UPSERT support.\n *\n * @param tableName - Name of the table to create\n * @param jsonPath - Optional path to JSON file (defaults to {dataDir}/{tableName}.json)\n * @returns Promise that resolves when table is created\n * @throws Error if JSON file doesn't exist or table creation fails\n */\n const inferSchemaFromJSON = async (\n tableName: string,\n jsonPath?: string,\n ): Promise<void> => {\n validateTableName(tableName);\n\n const filePath = jsonPath || join(url, `${tableName}.json`);\n\n try {\n // Check if file exists\n const { access } = await import('node:fs/promises');\n const { constants } = await import('node:fs');\n await access(filePath, constants.F_OK);\n\n // Use DuckDB's read_json_auto to infer types and create table\n console.log(\n `[json-adapter] Inferring schema for ${tableName} from JSON file`,\n );\n await connection.run(\n `CREATE TABLE \"${tableName}\" AS SELECT * FROM read_json_auto('${escapeStringLiteral(filePath)}')`,\n );\n\n // Remove from pending files if it was deferred\n pendingJSONFiles.delete(tableName);\n\n console.log(\n `[json-adapter] Created ${tableName} with inferred schema (no UNIQUE constraints)`,\n );\n } catch (error) {\n throw new DatabaseError(\n `Failed to infer schema from JSON for table ${tableName}`,\n {\n tableName,\n jsonPath: filePath,\n originalError:\n error instanceof Error ? error.message : String(error),\n },\n );\n }\n };\n\n /**\n * Creates a table-specific interface for simplified table operations\n *\n * @param tableName - Table name\n * @returns TableInterface for the specified table\n */\n const table = (tableName: string): TableInterface => ({\n insert: (data) => insert(tableName, data),\n get: (where) => get(tableName, where),\n list: (where) => list(tableName, where),\n });\n\n /**\n * Parses a tagged template literal into a SQL query and values\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Object with SQL query and values array\n */\n const parseTemplate = (strings: TemplateStringsArray, ...vars: any[]) => {\n let sql = strings[0];\n const values = [];\n for (let i = 0; i < vars.length; i++) {\n values.push(vars[i]);\n sql += `$${i + 1}${strings[i + 1]}`;\n }\n return { sql, values };\n };\n\n /**\n * Executes a SQL query using template literals and returns multiple rows\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to array of result records\n * @throws Error if the query fails\n */\n const many = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<Record<string, any>[]> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const reader = await connection.runAndReadAll(sql, values);\n return convertBigInts(reader.getRowObjects());\n } catch (e) {\n throw new DatabaseError('Failed to execute many query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals and returns a single row\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single result record or null\n * @throws Error if the query fails\n */\n const single = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<Record<string, any> | null> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const reader = await connection.runAndReadAll(sql, values);\n const rows = reader.getRowObjects();\n return rows[0] ? convertBigInts(rows[0]) : null;\n } catch (e) {\n throw new DatabaseError('Failed to execute single query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals and returns a single value\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single value (first column of first row)\n * @throws Error if the query fails\n */\n const pluck = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<any> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const reader = await connection.runAndReadAll(sql, values);\n const rows = reader.getRowObjects();\n if (rows.length === 0) return null;\n const firstRow = rows[0];\n const firstKey = Object.keys(firstRow)[0];\n return convertBigInts(firstRow[firstKey]);\n } catch (e) {\n throw new DatabaseError('Failed to execute pluck query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals without returning results\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise that resolves when the query completes\n * @throws Error if the query fails\n */\n const execute = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<void> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n await connection.run(sql, values);\n\n // Detect CREATE TABLE statements\n const createTableMatch = sql.match(\n /CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?[\"']?(\\w+)[\"']?/i,\n );\n if (createTableMatch) {\n const tableName = createTableMatch[1];\n\n // Check if there's pending JSON data for this table (deferred loading)\n const pendingFile = pendingJSONFiles.get(tableName);\n if (pendingFile) {\n // Table was just created by DDL - now load the JSON data\n await loadJSONData(\n connection,\n tableName,\n pendingFile,\n reportLoadError,\n );\n pendingJSONFiles.delete(tableName);\n } else if (writeStrategy !== 'none') {\n // No pending data - export empty table (preserves SMRT system tables)\n try {\n const { access } = await import('node:fs/promises');\n const jsonFilePath = join(url, `${tableName}.json`);\n try {\n await access(jsonFilePath);\n // File exists, don't overwrite\n } catch {\n // File doesn't exist, export empty table\n await persistTableToJSON(tableName);\n }\n } catch (error) {\n console.warn(\n `[json-adapter] Could not export newly created table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n } catch (e) {\n throw new DatabaseError('Failed to execute query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a raw SQL query with parameterized values\n *\n * @param str - SQL query string\n * @param values - Variables to use as parameters\n * @returns Promise resolving to query result with rows and metadata\n * @throws Error if the query fails\n */\n const query = async (str: string, ...values: any[]) => {\n const sql = str;\n const args = Array.isArray(values[0]) ? values[0] : values;\n\n try {\n const reader = await connection.runAndReadAll(sql, args);\n const rows = convertBigInts(reader.getRowObjects());\n\n return {\n command: sql.split(' ')[0].toUpperCase(),\n rowCount: rows.length,\n oid: null,\n fields:\n rows.length > 0\n ? Object.keys(rows[0]).map((name) => ({\n name,\n tableID: 0,\n columnID: 0,\n dataTypeID: 0,\n dataTypeSize: -1,\n dataTypeModifier: -1,\n format: 'text',\n }))\n : [],\n rows,\n };\n } catch (e) {\n throw wrapDatabaseError('Failed to execute raw query', e, {\n sql,\n args,\n });\n }\n };\n\n // Shorthand aliases for query methods\n const oo = many; // (o)bjective-(o)bjects: returns multiple rows\n const oO = single; // (o)bjective-(O)bject: returns a single row\n const ox = pluck; // (o)bjective-(x): returns a single value\n const xx = execute; // e(x)ecute-e(x)ecute: executes without returning\n\n /**\n * Synchronizes database schema with provided SQL DDL\n *\n * Filters out CREATE TRIGGER statements since triggers are not supported\n * in the JSON adapter (timestamps managed at application level).\n *\n * @param schema - SQL schema definition with CREATE TABLE statements\n * @returns Promise that resolves when schema is synchronized\n */\n const syncSchema = async (schema: string): Promise<void> => {\n const statements = schema\n .trim()\n .split(';')\n .filter((stmt) => stmt.trim() !== '');\n\n const createTableStatements: string[] = [];\n const indexStatements: string[] = [];\n const otherStatements: string[] = [];\n\n // Separate CREATE TABLE, CREATE INDEX, and other statements\n for (const stmt of statements) {\n const trimmed = stmt.trim();\n if (!trimmed) continue;\n\n const upperTrimmed = trimmed.toUpperCase();\n\n // Skip trigger creation - not supported in JSON adapter\n if (upperTrimmed.startsWith('CREATE TRIGGER')) {\n console.warn(\n '[json-adapter] Skipping trigger creation - timestamps managed at application level',\n );\n continue;\n }\n\n if (\n upperTrimmed.startsWith('CREATE INDEX') ||\n upperTrimmed.startsWith('CREATE UNIQUE INDEX')\n ) {\n indexStatements.push(trimmed);\n } else if (upperTrimmed.startsWith('CREATE TABLE')) {\n createTableStatements.push(trimmed);\n } else {\n otherStatements.push(trimmed);\n }\n }\n\n // Process each CREATE TABLE statement\n for (const ddl of createTableStatements) {\n // Transform UNIQUE indexes to inline constraints for DuckDB compatibility\n const { ddl: transformedDDL, indexes: remainingIndexes } =\n convertUniqueIndexesToInlineConstraints(ddl, indexStatements);\n\n // Extract table name from DDL before executing\n const createTableMatch = transformedDDL.match(\n /CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?[\"']?(\\w+)[\"']?/i,\n );\n const tableName = createTableMatch ? createTableMatch[1] : null;\n\n // If table was created with inferred schema, drop it first\n // (proper schema with UNIQUE constraints takes precedence)\n if (tableName && inferredSchemaTables.has(tableName)) {\n try {\n await connection.run(`DROP TABLE IF EXISTS \"${tableName}\"`);\n inferredSchemaTables.delete(tableName);\n console.log(\n `[json-adapter] Dropped inferred-schema table ${tableName} for schema upgrade`,\n );\n } catch (error) {\n console.warn(\n `[json-adapter] Could not drop inferred-schema table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n // Execute transformed DDL\n try {\n await connection.run(transformedDDL);\n\n if (tableName) {\n // Check if there's pending JSON data for this table (deferred loading)\n const pendingFile = pendingJSONFiles.get(tableName);\n if (pendingFile) {\n // Table was just created by DDL - now load the JSON data.\n // loadJSONData logs an unloadable file rather than throwing, so a\n // single bad table does not abort the rest of this schema sync\n await loadJSONData(\n connection,\n tableName,\n pendingFile,\n reportLoadError,\n );\n pendingJSONFiles.delete(tableName);\n } else if (writeStrategy !== 'none') {\n // No pending data - export empty table (preserves SMRT system tables)\n try {\n // Only export if JSON file doesn't already exist\n // This prevents overwriting existing data when tables are manually created\n const { access } = await import('node:fs/promises');\n const jsonFilePath = join(url, `${tableName}.json`);\n try {\n await access(jsonFilePath);\n // File exists, don't overwrite\n } catch {\n // File doesn't exist, export empty table\n await persistTableToJSON(tableName);\n }\n } catch (error) {\n console.warn(\n `[json-adapter] Could not export newly created table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n } catch (e) {\n console.error('Schema sync error (CREATE TABLE):', e);\n }\n\n // Execute remaining (non-UNIQUE) indexes for this table\n for (const indexSQL of remainingIndexes) {\n try {\n await connection.run(indexSQL);\n } catch (e) {\n console.error('Schema sync error (CREATE INDEX):', e);\n }\n }\n }\n\n // Execute other statements\n for (const stmt of otherStatements) {\n try {\n await connection.run(stmt);\n } catch (e) {\n console.error('Schema sync error:', e);\n }\n }\n };\n\n /**\n * Initialize database schemas from JSON manifest\n * Supports dependency resolution and schema overrides\n *\n * @param options - Schema initialization options\n * @returns Promise that resolves when schemas are initialized\n */\n const initializeSchemas = async (\n options: SchemaInitializationOptions,\n ): Promise<void> => {\n const schemaManager = new DatabaseSchemaManager();\n const currentDb: DatabaseInterface = {\n url,\n client: connection,\n query,\n insert,\n update,\n upsert,\n get,\n list,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n tableExists,\n many,\n single,\n pluck,\n execute,\n oo,\n oO,\n ox,\n xx,\n syncSchema,\n };\n\n await schemaManager.initializeSchemas(currentDb, options);\n };\n\n /**\n * Executes a callback within a database transaction\n * Automatically commits on success or rolls back on error\n *\n * @param callback - Function to execute within transaction\n * @returns Promise resolving to callback result\n */\n const transaction = async <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ): Promise<T> =>\n // Held across the whole BEGIN … COMMIT/ROLLBACK span. Without it two\n // overlapping calls raced on the one connection: the second BEGIN threw,\n // its catch ran ROLLBACK, and that rollback ended the *first* transaction\n // — half of its writes durable, half lost, and its promise rejected, so\n // the caller was told nothing had happened.\n connectionLock.run(async () => {\n try {\n await connection.run('BEGIN TRANSACTION');\n\n const result = await callback(createTransactionScope());\n await connection.run('COMMIT');\n return result;\n } catch (error) {\n // A failing ROLLBACK must not replace the caller's error: DuckDB reports\n // \"cannot rollback - no transaction is active\" whenever the transaction\n // is already gone, which says nothing about what actually failed.\n try {\n await connection.run('ROLLBACK');\n } catch {\n // Nothing left to roll back.\n }\n throw error;\n }\n });\n\n /**\n * Builds the transaction-scoped interface handed to a transaction callback.\n *\n * The only difference from the top-level interface is `transaction`, which\n * refuses to nest. DuckDB has no SAVEPOINT, so there is no way to re-enter\n * the transaction already in progress; re-exposing the top-level\n * `transaction` (the previous behaviour) sent the nested call into a second\n * BEGIN on the same connection, which throws and then rolls back the\n * *enclosing* transaction, silently discarding its work.\n */\n const createTransactionScope = (): DatabaseInterface => {\n const txDb: DatabaseInterface = {\n url,\n client: connection,\n insert,\n get,\n list,\n update,\n upsert,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n transaction: async () => {\n throw new NestedTransactionError(\n 'Nested transactions are not supported by this adapter because its engine has no SAVEPOINT. Pass the existing transaction to the callee instead of opening a new one.',\n { adapter: 'json' },\n );\n },\n };\n\n return txDb;\n };\n\n /**\n * Begins a new transaction and returns a handle for manual control\n *\n * Unlike transaction(), this gives you explicit control over commit/rollback.\n * Ideal for test isolation where you want to rollback after each test.\n *\n * @returns Promise resolving to a TransactionHandle\n */\n const beginTransaction = async (): Promise<TransactionHandle> => {\n // The handle owns the connection until the caller ends it, so the lock is\n // held across the gap rather than around a single call. A handle that is\n // never committed or rolled back therefore blocks every later transaction\n // until the queue timeout reports it.\n const releaseConnection = await connectionLock.acquire();\n try {\n await connection.run('BEGIN TRANSACTION');\n } catch (error) {\n // No transaction was opened, so there is no handle to end it and nothing\n // left to release the connection.\n releaseConnection();\n throw error;\n }\n\n let active = true;\n\n // COMMIT and ROLLBACK can both throw, and the transaction is over either\n // way, so the connection goes back before the error is rethrown.\n const end = async (command: 'COMMIT' | 'ROLLBACK'): Promise<void> => {\n if (!active) {\n throw new DatabaseError('Transaction already ended', {});\n }\n try {\n await connection.run(command);\n } catch (error) {\n // COMMIT can fail and leave the transaction *open* — SQLite documents\n // exactly that for SQLITE_BUSY. Releasing the connection then would hand\n // the next queued caller a connection still inside a transaction: its\n // BEGIN would throw, and its catch would ROLLBACK, discarding this\n // transaction's work. So normalize before releasing, the way the pooled\n // adapter's discardTxClient does.\n try {\n await connection.run('ROLLBACK');\n } catch {\n // Already gone; nothing left to normalize.\n }\n throw error;\n } finally {\n active = false;\n releaseConnection();\n }\n };\n\n const commit = (): Promise<void> => end('COMMIT');\n\n const rollback = (): Promise<void> => end('ROLLBACK');\n\n const isActive = (): boolean => active;\n\n // Create a transaction-scoped database interface with commit/rollback.\n // The handle is inside a transaction for the same reason a callback\n // scope is, so it gets the same non-nesting `transaction`.\n const txHandle: TransactionHandle = {\n ...createTransactionScope(),\n commit,\n rollback,\n isActive,\n };\n\n return txHandle;\n };\n\n const closeResources = createDuckDBResourceCloser(connection, instance);\n db = {\n url,\n requiresSchemaCheck: true,\n client: connection,\n query,\n insert,\n update,\n upsert,\n get,\n list,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n tableExists,\n many,\n single,\n pluck,\n execute,\n oo,\n oO,\n ox,\n xx,\n syncSchema,\n initializeSchemas,\n transaction,\n beginTransaction,\n // JSON-specific methods\n exportTable,\n inferSchemaFromJSON,\n getTableLoadErrors,\n close: async () => {\n const state = getJSONSourceCacheState(db);\n if (state) {\n state.closing = true;\n await waitForJSONSourceWrites(state);\n }\n memoryConnectionCache.forget(db);\n await closeResources();\n },\n } as DatabaseInterface & {\n exportTable: (table: string) => Promise<void>;\n inferSchemaFromJSON: (\n tableName: string,\n jsonPath?: string,\n ) => Promise<void>;\n getTableLoadErrors: () => JSONTableLoadError[];\n };\n return db;\n })();\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport { webcrypto } from 'node:crypto';\nimport { DatabaseError } from '@happyvertical/utils';\nimport {\n DatabaseError as PgDatabaseError,\n type QueryResult as PgQueryResult,\n Pool,\n type PoolClient,\n} from 'pg';\nimport { DatabaseSchemaManager } from './schema-manager';\nimport {\n generateAddColumnStatement,\n generateCreateIndexStatement,\n quotePostgresColumnName,\n validateColumnName,\n validateColumnNames,\n validateIndexName,\n validateTableName,\n} from './shared/alter-utils';\nimport {\n ConnectionCache,\n validateDatabaseCacheOptions,\n} from './shared/connection-cache';\nimport type {\n QueryResult as BaseQueryResult,\n ColumnDefinition,\n ColumnDefinitionWithName,\n DatabaseCacheOptions,\n DatabaseInterface,\n IndexDefinition,\n SchemaInitializationOptions,\n SessionHandle,\n TableInterface,\n TableSchemaInfo,\n TransactionHandle,\n UpsertOptions,\n VectorCapabilities,\n VectorIndexOptions,\n VectorSearchOptions,\n VectorSearchResult,\n} from './shared/types';\nimport {\n buildWhere,\n formatDbError,\n resolveInsertColumns,\n wrapDatabaseError,\n} from './shared/utils';\n\nconst MAX_TIMER_DELAY_MILLIS = 2_147_483_647;\n\n/**\n * Configuration options for PostgreSQL database connections\n */\nexport interface PostgresOptions extends DatabaseCacheOptions {\n /**\n * Connection URL for PostgreSQL\n */\n url?: string;\n\n /**\n * Database name\n */\n database?: string;\n\n /**\n * Database server hostname\n */\n host?: string;\n\n /**\n * Username for authentication\n */\n user?: string;\n\n /**\n * Password for authentication\n */\n password?: string;\n\n /**\n * Port number for the PostgreSQL server\n */\n port?: number;\n\n /**\n * Opaque caller-owned cache identity for connection pooling.\n * If provided, the same dbid returns the same cached connection.\n * When omitted, a keyed digest is derived from effective connection and pool\n * options, including credentials, without retaining them in readable keys.\n */\n dbid?: string;\n\n /**\n * Maximum number of connections in the pool.\n * Defaults to 20. The pg library defaults to 10, which is too low\n * for applications with many SMRT collections.\n */\n max?: number;\n\n /**\n * Maximum time to wait for a pool connection, in milliseconds.\n * Must not exceed Node.js's maximum timer delay of 2,147,483,647.\n * Set to 0 to wait indefinitely. When omitted, pg's default is used.\n */\n connectionTimeoutMillis?: number;\n\n /**\n * Maximum time an idle client remains in the pool, in milliseconds.\n * Must not exceed Node.js's maximum timer delay of 2,147,483,647.\n * Set to 0 to retain idle clients. When omitted, pg's default is used.\n */\n idleTimeoutMillis?: number;\n\n /**\n * Schema definitions for tables.\n * Accepts a record or a lazy function (see SchemasOption).\n *\n * **Postgres ignores this option** — tables are managed by migrations.\n * The option exists so callers can pass schemas uniformly to all adapters\n * without knowing the adapter type. Only JSON/DuckDB adapters resolve it.\n */\n schemas?: import('./shared/types').SchemasOption;\n}\n\n/**\n * Module-level cache for PostgreSQL connections.\n * Keyed by dbid (or derived from connection URL).\n * Prevents creating multiple pg.Pool instances for the same database.\n */\ndeclare global {\n // A non-extractable process-local HMAC key keeps password-bearing cache\n // identities resistant to offline guessing even if cache keys are inspected.\n // eslint-disable-next-line no-var\n var __haveSqlPostgresCacheHmacKey: Promise<CryptoKey> | undefined;\n // eslint-disable-next-line no-var\n var __haveSqlPostgresConnectionCache:\n | ConnectionCache<DatabaseInterface>\n | undefined;\n}\n\nglobalThis.__haveSqlPostgresCacheHmacKey ??= webcrypto.subtle.generateKey(\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n) as Promise<CryptoKey>;\nglobalThis.__haveSqlPostgresConnectionCache ??=\n new ConnectionCache<DatabaseInterface>();\nconst connectionCache = globalThis.__haveSqlPostgresConnectionCache;\n\ntype PostgresQueryExecutor = {\n query: (\n sql: string,\n values?: any[],\n ) => Promise<{ rows: any[]; rowCount: number | null }>;\n};\n\n/**\n * Clears the PostgreSQL connection cache.\n * Useful for test isolation and reconnection scenarios.\n */\nexport async function clearPostgresConnectionCache(): Promise<void> {\n await connectionCache.clear(closePostgresDatabase);\n}\n\nasync function closePostgresDatabase(db: DatabaseInterface): Promise<void> {\n if (db.close) {\n await db.close();\n return;\n }\n await db.client.end();\n}\n\ntype EffectivePostgresConfig = {\n url?: string;\n database?: string;\n host?: string;\n user?: string;\n password?: string;\n port: number;\n max: number;\n connectionTimeoutMillis?: number;\n idleTimeoutMillis?: number;\n};\n\nfunction resolvePostgresConfig(\n options: PostgresOptions,\n): EffectivePostgresConfig {\n const preferred = <T>(option: T | undefined, environment: T | undefined) =>\n option !== undefined ? option : environment;\n const envPort = process.env.HAVE_SQL_PORT;\n const primary = {\n url: preferred(options.url, process.env.HAVE_SQL_URL),\n database: preferred(options.database, process.env.HAVE_SQL_DATABASE),\n host: preferred(options.host, process.env.HAVE_SQL_HOST),\n user: preferred(options.user, process.env.HAVE_SQL_USER),\n password: preferred(options.password, process.env.HAVE_SQL_PASSWORD),\n port: preferred(\n options.port,\n envPort === undefined ? undefined : Number(envPort),\n ),\n };\n const useLegacy =\n !primary.url && !primary.host && !primary.database && !primary.user;\n const legacy = <T>(value: T | undefined, environment: T | undefined) =>\n value !== undefined || !useLegacy ? value : environment;\n const legacyPort = process.env.SQLOO_PORT;\n\n const port =\n legacy(\n primary.port,\n legacyPort === undefined ? undefined : Number(legacyPort),\n ) ?? 5432;\n const max = options.max ?? 20;\n const { connectionTimeoutMillis, idleTimeoutMillis } = options;\n if (!Number.isInteger(port) || port < 1 || port > 65_535) {\n throw new Error('PostgreSQL port must be an integer between 1 and 65535');\n }\n if (!Number.isInteger(max) || max < 1) {\n throw new Error('PostgreSQL pool max must be a positive integer');\n }\n if (\n connectionTimeoutMillis !== undefined &&\n (!Number.isInteger(connectionTimeoutMillis) ||\n connectionTimeoutMillis < 0 ||\n connectionTimeoutMillis > MAX_TIMER_DELAY_MILLIS)\n ) {\n throw new Error(\n `PostgreSQL pool connectionTimeoutMillis must be an integer between 0 and ${MAX_TIMER_DELAY_MILLIS}`,\n );\n }\n if (\n idleTimeoutMillis !== undefined &&\n (!Number.isInteger(idleTimeoutMillis) ||\n idleTimeoutMillis < 0 ||\n idleTimeoutMillis > MAX_TIMER_DELAY_MILLIS)\n ) {\n throw new Error(\n `PostgreSQL pool idleTimeoutMillis must be an integer between 0 and ${MAX_TIMER_DELAY_MILLIS}`,\n );\n }\n\n return {\n url: legacy(primary.url, process.env.SQLOO_URL),\n database: legacy(primary.database, process.env.SQLOO_DATABASE),\n host: legacy(primary.host, process.env.SQLOO_HOST),\n user: legacy(primary.user, process.env.SQLOO_USER),\n password: legacy(primary.password, process.env.SQLOO_PASSWORD),\n port,\n max,\n connectionTimeoutMillis,\n idleTimeoutMillis,\n };\n}\n\nasync function derivePostgresConnectionCacheKey(\n options: PostgresOptions,\n effective: EffectivePostgresConfig,\n): Promise<string | undefined> {\n if (options.dbid) {\n return options.dbid;\n }\n\n const identity = JSON.stringify({\n url: effective.url ?? null,\n host: effective.host ?? 'localhost',\n port: effective.port,\n database: effective.database ?? null,\n user: effective.user ?? null,\n password: effective.password ?? null,\n max: effective.max,\n connectionTimeoutMillis: effective.connectionTimeoutMillis ?? null,\n idleTimeoutMillis: effective.idleTimeoutMillis ?? null,\n });\n const hmacKey = await globalThis.__haveSqlPostgresCacheHmacKey;\n if (!hmacKey) {\n throw new Error('PostgreSQL cache identity key was not initialized');\n }\n const signature = await webcrypto.subtle.sign(\n 'HMAC',\n hmacKey,\n new TextEncoder().encode(\n `@happyvertical/sql:postgres-cache:v1\\0${identity}`,\n ),\n );\n return `pg:hmac-sha256:v1:${Buffer.from(signature).toString('hex')}`;\n}\n\n/** @internal Exported for adversarial identity tests. */\nexport async function getPostgresConnectionCacheKey(\n options: PostgresOptions,\n): Promise<string | undefined> {\n validateDatabaseCacheOptions(options);\n return derivePostgresConnectionCacheKey(\n options,\n resolvePostgresConfig(options),\n );\n}\n\n/**\n * Operator for the given distance metric in pgvector\n */\nfunction vectorOperator(metric: 'cosine' | 'l2' | 'ip'): string {\n switch (metric) {\n case 'cosine':\n return '<=>';\n case 'l2':\n return '<->';\n case 'ip':\n return '<#>';\n default:\n return '<=>';\n }\n}\n\nfunction getMaxPostgresParameterIndex(sql: string): number {\n let maxIndex = 0;\n for (const match of sql.matchAll(/\\$(\\d+)/g)) {\n const index = Number(match[1]);\n if (Number.isSafeInteger(index) && index > maxIndex) {\n maxIndex = index;\n }\n }\n return maxIndex;\n}\n\nfunction usesSingleArrayParameter(sql: string): boolean {\n const identifier = String.raw`(?:\"[^\"]+\"|[a-z_][\\w$]*)`;\n const qualifiedIdentifier = String.raw`${identifier}(?:\\s*\\.\\s*${identifier})?`;\n const arrayType = String.raw`${qualifiedIdentifier}(?:\\s*\\([^)]*\\))?\\s*\\[\\]`;\n\n return (\n new RegExp(String.raw`\\$1\\s*::\\s*${arrayType}`, 'i').test(sql) ||\n new RegExp(\n String.raw`\\bCAST\\s*\\(\\s*\\$1\\s+AS\\s+${arrayType}\\s*\\)`,\n 'i',\n ).test(sql) ||\n /\\b(?:ANY|ALL|SOME)\\s*\\(\\s*\\$1\\s*\\)/i.test(sql)\n );\n}\n\nfunction normalizeRawQueryValues(sql: string, values: any[]): any[] {\n if (values.length !== 1 || !Array.isArray(values[0])) {\n return values;\n }\n\n const valuesArray = values[0];\n const maxParameterIndex = getMaxPostgresParameterIndex(sql);\n\n if (maxParameterIndex === 1 && usesSingleArrayParameter(sql)) {\n return values;\n }\n\n if (maxParameterIndex === 0 || maxParameterIndex === valuesArray.length) {\n return valuesArray;\n }\n\n return values;\n}\n\nfunction rewriteQuestionMarkPlaceholders(sql: string): {\n sql: string;\n count: number;\n} {\n let output = '';\n let count = 0;\n let index = 0;\n\n const copyQuoted = (quote: \"'\" | '\"', escaped = false) => {\n output += quote;\n index += 1;\n\n while (index < sql.length) {\n const char = sql[index];\n output += char;\n index += 1;\n\n if (escaped && char === '\\\\' && index < sql.length) {\n output += sql[index];\n index += 1;\n continue;\n }\n\n if (char === quote) {\n if (sql[index] === quote) {\n output += sql[index];\n index += 1;\n continue;\n }\n break;\n }\n }\n };\n\n const copyUntil = (end: string) => {\n const endIndex = sql.indexOf(end, index + end.length);\n if (endIndex === -1) {\n output += sql.slice(index);\n index = sql.length;\n return;\n }\n\n output += sql.slice(index, endIndex + end.length);\n index = endIndex + end.length;\n };\n\n while (index < sql.length) {\n const char = sql[index];\n const next = sql[index + 1];\n\n if ((char === 'E' || char === 'e') && next === \"'\") {\n output += char;\n index += 1;\n copyQuoted(\"'\", true);\n continue;\n }\n\n if (char === \"'\") {\n copyQuoted(\"'\");\n continue;\n }\n\n if (char === '\"') {\n copyQuoted('\"');\n continue;\n }\n\n if (char === '-' && next === '-') {\n const endIndex = sql.indexOf('\\n', index + 2);\n if (endIndex === -1) {\n output += sql.slice(index);\n index = sql.length;\n } else {\n output += sql.slice(index, endIndex + 1);\n index = endIndex + 1;\n }\n continue;\n }\n\n if (char === '/' && next === '*') {\n copyUntil('*/');\n continue;\n }\n\n if (char === '$') {\n const tag = sql.slice(index).match(/^\\$[A-Za-z_][\\w$]*\\$|^\\$\\$/);\n if (tag) {\n copyUntil(tag[0]);\n continue;\n }\n }\n\n if (char === '?' && next !== '|' && next !== '&') {\n count += 1;\n output += `$${count}`;\n index += 1;\n continue;\n }\n\n output += char;\n index += 1;\n }\n\n return { sql: output, count };\n}\n\nfunction usesSingleQuestionArrayParameter(sql: string): boolean {\n const identifier = String.raw`(?:\"[^\"]+\"|[a-z_][\\w$]*)`;\n const qualifiedIdentifier = String.raw`${identifier}(?:\\s*\\.\\s*${identifier})?`;\n const arrayType = String.raw`${qualifiedIdentifier}(?:\\s*\\([^)]*\\))?\\s*\\[\\]`;\n\n return (\n new RegExp(String.raw`\\?\\s*::\\s*${arrayType}`, 'i').test(sql) ||\n new RegExp(String.raw`\\bCAST\\s*\\(\\s*\\?\\s+AS\\s+${arrayType}\\s*\\)`, 'i').test(\n sql,\n ) ||\n /\\b(?:ANY|ALL|SOME)\\s*\\(\\s*\\?\\s*\\)/i.test(sql)\n );\n}\n\nfunction normalizeQuestionMarkQueryValues(\n sql: string,\n values: any[],\n placeholderCount: number,\n): any[] {\n if (values.length !== 1 || !Array.isArray(values[0])) {\n return values;\n }\n\n if (placeholderCount === 1 && usesSingleQuestionArrayParameter(sql)) {\n return values;\n }\n\n if (placeholderCount === values[0].length) {\n return values[0];\n }\n\n return values;\n}\n\nfunction normalizePostgresRawQuery(\n sql: string,\n values: any[],\n): { sql: string; values: any[] } {\n if (getMaxPostgresParameterIndex(sql) > 0) {\n return { sql, values: normalizeRawQueryValues(sql, values) };\n }\n\n const rewritten = rewriteQuestionMarkPlaceholders(sql);\n const placeholderCount = rewritten.count;\n const queryValues = normalizeQuestionMarkQueryValues(\n sql,\n values,\n placeholderCount,\n );\n\n if (placeholderCount > 0 && placeholderCount === queryValues.length) {\n return {\n sql: rewritten.sql,\n values: queryValues,\n };\n }\n\n return { sql, values: normalizeRawQueryValues(sql, values) };\n}\n\n/**\n * pgvector index operator class for CREATE INDEX\n */\nfunction vectorOpsClass(metric: 'cosine' | 'l2' | 'ip'): string {\n switch (metric) {\n case 'cosine':\n return 'vector_cosine_ops';\n case 'l2':\n return 'vector_l2_ops';\n case 'ip':\n return 'vector_ip_ops';\n default:\n return 'vector_cosine_ops';\n }\n}\n\n/**\n * Convert a number[] to a pgvector literal string: '[0.1,0.2,0.3]'\n */\nfunction toVectorLiteral(embedding: number[]): string {\n if (embedding.length === 0) {\n throw new DatabaseError('Vector embedding must not be empty', {});\n }\n for (let i = 0; i < embedding.length; i++) {\n if (!Number.isFinite(embedding[i])) {\n throw new DatabaseError(\n 'Vector embedding contains invalid value (NaN or Infinity)',\n { index: i, value: embedding[i] },\n );\n }\n }\n return `[${embedding.join(',')}]`;\n}\n\n/**\n * Creates vector capabilities backed by pgvector for a PostgreSQL pool.\n *\n * @param pool - PostgreSQL connection pool\n * @returns VectorCapabilities implementation\n */\n/**\n * `type` is interpolated into `USING <type>`, which cannot be parameterized and\n * is not an identifier the quoting helpers cover. `VectorIndexOptions.type`\n * constrains it at compile time only, so anything reaching this at runtime —\n * a JavaScript caller, or a `filter as VectorIndexOptions` on parsed JSON — has\n * to be checked against the values pgvector actually offers.\n */\nconst VECTOR_INDEX_TYPES = new Set(['hnsw', 'ivfflat']);\n\n/**\n * `metric` needs the same treatment as `type`, for a less obvious reason.\n *\n * It never reaches SQL directly — `vectorOpsClass`/`vectorOperator` map it\n * through a closed switch with a safe default, so an unknown metric produces a\n * valid operator class. But it is also concatenated into the *generated index\n * name*, which is then interpolated into a quoted identifier, so a metric\n * carrying a `\"` still ends that identifier. The safe-default mapping is what\n * makes this easy to miss: the part of the metric that looks dangerous is\n * handled, and the part that is actually dangerous is somewhere else.\n */\nconst VECTOR_METRICS = new Set(['cosine', 'l2', 'ip']);\n\nfunction createVectorCapabilities(pool: Pool): VectorCapabilities {\n return {\n async ensureColumn(\n table: string,\n column: string,\n dimensions: number,\n ): Promise<void> {\n if (!Number.isInteger(dimensions) || dimensions <= 0) {\n throw new DatabaseError(\n 'Vector dimensions must be a positive integer',\n { dimensions },\n );\n }\n validateTableName(table);\n validateColumnName(column);\n try {\n await pool.query('CREATE EXTENSION IF NOT EXISTS vector');\n await pool.query(\n `ALTER TABLE \"${table}\" ADD COLUMN IF NOT EXISTS \"${column}\" vector(${dimensions})`,\n );\n } catch (e) {\n throw new DatabaseError('Failed to ensure vector column', {\n table,\n column,\n dimensions,\n originalError: formatDbError(e),\n });\n }\n },\n\n async ensureIndex(\n table: string,\n column: string,\n options?: VectorIndexOptions,\n ): Promise<void> {\n validateTableName(table);\n validateColumnName(column);\n const metric = options?.metric || 'cosine';\n const indexType = options?.type || 'hnsw';\n if (!VECTOR_INDEX_TYPES.has(indexType)) {\n throw new DatabaseError('Unsupported vector index type', {\n table,\n column,\n type: indexType,\n supported: [...VECTOR_INDEX_TYPES],\n });\n }\n if (!VECTOR_METRICS.has(metric)) {\n throw new DatabaseError('Unsupported vector metric', {\n table,\n column,\n metric,\n supported: [...VECTOR_METRICS],\n });\n }\n const opsClass = vectorOpsClass(metric);\n // Every part is now either a validated identifier or one of a closed set,\n // so the generated name cannot carry SQL of its own.\n const indexName = `idx_${table}_${column}_${metric}_${indexType}`;\n\n try {\n await pool.query(\n `CREATE INDEX IF NOT EXISTS \"${indexName}\" ON \"${table}\" USING ${indexType} (\"${column}\" ${opsClass})`,\n );\n } catch (e) {\n throw new DatabaseError('Failed to ensure vector index', {\n table,\n column,\n indexName,\n originalError: formatDbError(e),\n });\n }\n },\n\n async upsertVector(\n table: string,\n where: Record<string, any>,\n column: string,\n embedding: number[],\n ): Promise<void> {\n validateTableName(table);\n validateColumnName(column);\n const keys = Object.keys(where);\n validateColumnNames(keys);\n const values = Object.values(where);\n const conditions = keys\n .map((key, i) => `\"${key}\" IS NOT DISTINCT FROM $${i + 1}`)\n .join(' AND ');\n const vectorParam = `$${values.length + 1}`;\n\n try {\n await pool.query(\n `UPDATE \"${table}\" SET \"${column}\" = ${vectorParam}::vector WHERE ${conditions}`,\n [...values, toVectorLiteral(embedding)],\n );\n } catch (e) {\n throw new DatabaseError('Failed to upsert vector', {\n table,\n column,\n where,\n originalError: formatDbError(e),\n });\n }\n },\n\n async search(\n table: string,\n column: string,\n embedding: number[],\n options?: VectorSearchOptions,\n ): Promise<VectorSearchResult[]> {\n validateTableName(table);\n validateColumnName(column);\n const metric = options?.metric || 'cosine';\n const limit = options?.limit || 10;\n const op = vectorOperator(metric);\n\n // $1 is the query vector; user-provided WHERE params use $2, $3, etc.\n const queryParams: any[] = [toVectorLiteral(embedding)];\n\n let whereClause = `\"${column}\" IS NOT NULL`;\n if (options?.where) {\n // `options.where` is raw SQL by design, the same documented\n // developer-controlled boundary as `buildWhere`'s operator-suffix keys.\n // It is not validated here; use `options.params` for anything that came\n // from outside the process.\n whereClause += ` AND (${options.where})`;\n if (options.params) {\n queryParams.push(...options.params);\n }\n }\n\n const limitParam = `$${queryParams.length + 1}`;\n queryParams.push(limit);\n\n const sql = `SELECT *, (\"${column}\" ${op} $1::vector) AS distance FROM \"${table}\" WHERE ${whereClause} ORDER BY \"${column}\" ${op} $1::vector LIMIT ${limitParam}`;\n\n try {\n const result = await pool.query(sql, queryParams);\n\n return result.rows.map((row: Record<string, any>) => ({\n ...row,\n id: row.id as string,\n distance: Number.parseFloat(row.distance),\n }));\n } catch (e) {\n throw new DatabaseError('Failed to execute vector search', {\n table,\n column,\n metric,\n limit,\n originalError: formatDbError(e),\n });\n }\n },\n };\n}\n\n/**\n * Creates a PostgreSQL database adapter\n *\n * Loads configuration from environment variables with backward compatibility:\n * - First checks HAVE_SQL_* environment variables (new standard)\n * - Falls back to SQLOO_* environment variables (legacy)\n * - User-provided options always take precedence\n *\n * Environment variables:\n * - HAVE_SQL_URL / SQLOO_URL → Connection string (takes precedence)\n * - HAVE_SQL_DATABASE / SQLOO_DATABASE → Database name\n * - HAVE_SQL_HOST / SQLOO_HOST → Host (default: 'localhost')\n * - HAVE_SQL_USER / SQLOO_USER → Username\n * - HAVE_SQL_PASSWORD / SQLOO_PASSWORD → Password\n * - HAVE_SQL_PORT / SQLOO_PORT → Port (default: 5432)\n *\n * @param options - PostgreSQL connection options\n * @returns Database interface for PostgreSQL\n */\nexport async function getDatabase(\n options: PostgresOptions = {},\n): Promise<DatabaseInterface> {\n validateDatabaseCacheOptions(options);\n const effective = resolvePostgresConfig(options);\n const cacheKey = await derivePostgresConnectionCacheKey(options, effective);\n return connectionCache.getOrCreate(\n cacheKey,\n options,\n () => createDatabase(effective),\n closePostgresDatabase,\n );\n}\n\n/**\n * Internal: creates the actual database connection and caches it.\n */\nasync function createDatabase(\n config: EffectivePostgresConfig,\n): Promise<DatabaseInterface> {\n // Apply defaults\n const { database, host = 'localhost', user, password, port = 5432 } = config;\n\n // Construct url if not provided (for DatabaseInterface requirement)\n const url: string =\n (config.url as string) ||\n `postgresql://${user}${password ? `:${password}` : ''}@${host}:${port}/${database || 'postgres'}`;\n\n // Create a connection pool with explicit max to prevent exhaustion.\n // pg defaults to 10 which is too low for SMRT apps that sync 30+ table\n // schemas on startup. Default to 20 but allow callers to override.\n const { max: poolMax, connectionTimeoutMillis, idleTimeoutMillis } = config;\n const pool = new Pool(\n config.url\n ? {\n connectionString: config.url as string,\n max: poolMax,\n connectionTimeoutMillis,\n idleTimeoutMillis,\n }\n : {\n host: host as string,\n user: user as string,\n password: password as string,\n port: port as number,\n database: database as string,\n max: poolMax,\n connectionTimeoutMillis,\n idleTimeoutMillis,\n },\n );\n\n // An idle pooled client that loses its backend (restart, failover, proxy\n // idle timeout, TCP reset) emits 'error' on the pool. Without a listener Node\n // treats that as an unhandled 'error' event and terminates the process, so\n // routine database maintenance would crash every consumer of this adapter.\n // pg has already removed the client from the pool by this point; absorb the\n // event and let the pool carry on with its remaining connections.\n pool.on('error', (error) => {\n console.warn(\n 'Warning: PostgreSQL pool client error (connection discarded):',\n error instanceof Error ? error.message : String(error),\n );\n });\n\n // Wrap pool.end() to evict from cache so stale pools are never returned.\n // This fixes the \"Cannot use a pool after calling end on the pool\" error\n // when tests or shutdown code calls db.client.end() and subsequent calls\n // to getDatabase() would otherwise return the ended pool.\n const originalEnd = pool.end.bind(pool);\n let db: DatabaseInterface;\n let endPromise: Promise<void> | undefined;\n const evictFromCache = () => connectionCache.forget(db);\n (pool as any).end = (callback?: (error?: Error) => void) => {\n if (!endPromise) {\n evictFromCache();\n endPromise = originalEnd();\n }\n if (callback) {\n endPromise.then(\n () => callback(),\n (error) =>\n callback(error instanceof Error ? error : new Error(String(error))),\n );\n return;\n }\n return endPromise;\n };\n\n const client = pool;\n\n /**\n * Serializes a value for database storage\n * Converts objects and arrays to JSON strings\n * Converts Dates to ISO strings\n * Passes through primitives unchanged\n */\n const serializeValue = (value: any): any => {\n if (value === null || value === undefined) {\n return value;\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (Buffer.isBuffer(value) || ArrayBuffer.isView(value)) {\n return value;\n }\n if (typeof value === 'object') {\n return JSON.stringify(value);\n }\n return value;\n };\n\n /**\n * Serializes all values in an object for database storage\n */\n const serializeRecord = (\n record: Record<string, any>,\n ): Record<string, any> => {\n const serialized: Record<string, any> = {};\n for (const [key, value] of Object.entries(record)) {\n // Skip undefined values - they cannot be passed to the database\n if (value === undefined) {\n continue;\n }\n serialized[key] = serializeValue(value);\n }\n return serialized;\n };\n\n let serverVersionNumPromise: Promise<number> | undefined;\n const nullsNotDistinctIndexCache = new Map<string, Promise<boolean>>();\n\n const hasNullConflictValue = (\n conflictColumns: string[],\n data: Record<string, any>,\n ): boolean => conflictColumns.some((col) => data[col] === null);\n\n const validateUpsertConflictColumns = (\n table: string,\n conflictColumns: string[],\n serializedData: Record<string, any>,\n ): void => {\n const missingColumns = conflictColumns.filter(\n (col) => !(col in serializedData),\n );\n\n if (missingColumns.length > 0) {\n throw new DatabaseError('Conflict columns missing from data', {\n table,\n conflictColumns,\n missingColumns,\n availableColumns: Object.keys(serializedData),\n hint: 'All columns specified in ON CONFLICT must be present in the data being inserted. Undefined values should be replaced with null or an appropriate default.',\n });\n }\n };\n\n const getServerVersionNum = async (): Promise<number> => {\n serverVersionNumPromise ??= client\n .query('SHOW server_version_num')\n .then((result) => Number(result.rows[0]?.server_version_num) || 0);\n return serverVersionNumPromise;\n };\n\n const hasNativeNullsNotDistinctIndex = async (\n table: string,\n conflictColumns: string[],\n ): Promise<boolean> => {\n const versionNum = await getServerVersionNum();\n if (versionNum < 150000) return false;\n\n const cacheKey = `${table}:${conflictColumns.join(',')}`;\n let cached = nullsNotDistinctIndexCache.get(cacheKey);\n if (!cached) {\n cached = client\n .query(\n `\n SELECT 1\n FROM pg_index i\n WHERE i.indrelid = to_regclass($1)\n AND i.indisunique\n AND i.indnullsnotdistinct\n AND ARRAY(\n SELECT a.attname::text\n FROM unnest(i.indkey) WITH ORDINALITY AS cols(attnum, ord)\n JOIN pg_attribute a\n ON a.attrelid = i.indrelid\n AND a.attnum = cols.attnum\n ORDER BY cols.ord\n ) = $2::text[]\n LIMIT 1\n `,\n [table, conflictColumns],\n )\n .then((result) => result.rows.length > 0)\n .catch(() => false);\n nullsNotDistinctIndexCache.set(cacheKey, cached);\n }\n\n return cached;\n };\n\n const buildPostgresUpsertParts = (\n conflictColumns: string[],\n serializedData: Record<string, any>,\n ) => {\n const keys = Object.keys(serializedData);\n const columns = keys.map(quotePostgresColumnName);\n const values = Object.values(serializedData);\n const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');\n const updateSet = columns\n .map((column, i) => `${column} = $${i + 1}`)\n .join(', ');\n const conflict = conflictColumns.map(quotePostgresColumnName).join(', ');\n\n return { columns, values, placeholders, updateSet, conflict };\n };\n\n const executeStandardPostgresUpsert = async (\n executor: PostgresQueryExecutor,\n table: string,\n conflictColumns: string[],\n serializedData: Record<string, any>,\n ): Promise<BaseQueryResult> => {\n const { columns, values, placeholders, updateSet, conflict } =\n buildPostgresUpsertParts(conflictColumns, serializedData);\n const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders}) ON CONFLICT(${conflict}) DO UPDATE SET ${updateSet}`;\n const result = await executor.query(sql, values);\n return { operation: 'upsert', affected: result.rowCount ?? 0 };\n };\n\n const executeNullAwarePostgresUpsert = async (\n executor: PostgresQueryExecutor,\n table: string,\n conflictColumns: string[],\n serializedData: Record<string, any>,\n ): Promise<BaseQueryResult> => {\n const { columns, values, placeholders, updateSet } =\n buildPostgresUpsertParts(conflictColumns, serializedData);\n const lockKey = conflictColumns\n .map((col) => `${col}:${JSON.stringify(serializedData[col])}`)\n .join('|');\n\n await executor.query(\n 'SELECT pg_advisory_xact_lock(hashtext($1), hashtext($2))',\n [`@happyvertical/sql.upsert:${table}`, lockKey],\n );\n\n const whereStart = values.length + 1;\n const whereClause = conflictColumns\n .map(\n (col, i) =>\n `${quotePostgresColumnName(col)} IS NOT DISTINCT FROM $${whereStart + i}`,\n )\n .join(' AND ');\n const whereValues = conflictColumns.map((col) => serializedData[col]);\n const updateSql = `UPDATE ${table} SET ${updateSet} WHERE ${whereClause}`;\n const updateResult = await executor.query(updateSql, [\n ...values,\n ...whereValues,\n ]);\n\n if ((updateResult.rowCount ?? 0) > 0) {\n return { operation: 'upsert', affected: updateResult.rowCount ?? 0 };\n }\n\n const insertSql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders})`;\n const insertResult = await executor.query(insertSql, values);\n return { operation: 'upsert', affected: insertResult.rowCount ?? 0 };\n };\n\n /**\n * Absorbs 'error' events for the clients currently checked out for a\n * transaction, keyed by client so the listener can be removed on release.\n */\n const txErrorAbsorbers = new WeakMap<PoolClient, () => void>();\n const releasedTxClients = new WeakSet<PoolClient>();\n\n /**\n * Checks out a pooled client for a transaction.\n *\n * pg-pool drops its own idle 'error' listener while a client is checked out,\n * so a backend that dies mid-transaction would emit an unhandled 'error' and\n * terminate the process. Absorb it here — the failure still reaches the\n * caller as a rejected query — and drop the listener again on release so\n * repeated checkouts of the same client cannot accumulate them.\n */\n const acquireTxClient = async (): Promise<PoolClient> => {\n const txClient = await client.connect();\n // The pool hands back the same client objects, so the release guard has to\n // be reset per checkout — otherwise every reuse after the first would be\n // treated as already released and stay checked out forever.\n releasedTxClients.delete(txClient);\n const absorb = () => {};\n txClient.on('error', absorb);\n txErrorAbsorbers.set(txClient, absorb);\n return txClient;\n };\n\n /**\n * Returns a transaction client to the pool exactly once.\n *\n * Every teardown path must run this, including the ones reached by a throwing\n * COMMIT or ROLLBACK — a client that is never released stays checked out for\n * the life of the process, and enough of them exhaust the pool. Releasing\n * twice throws, so the released clients are tracked rather than assumed.\n *\n * @param txClient - The pooled client backing a transaction\n * @param destroy - Destroy the connection instead of reusing it, for when the\n * transaction state could not be cleaned up\n */\n const releaseTxClient = (txClient: PoolClient, destroy = false): void => {\n if (releasedTxClients.has(txClient)) return;\n releasedTxClients.add(txClient);\n const absorb = txErrorAbsorbers.get(txClient);\n if (absorb) {\n txClient.off('error', absorb);\n txErrorAbsorbers.delete(txClient);\n }\n txClient.release(destroy || undefined);\n };\n\n /**\n * Rolls back a failed transaction and returns its client to the pool.\n *\n * The connection may still be inside a transaction, so ROLLBACK is attempted\n * to normalize it. If that works the connection is clean and worth reusing;\n * if it does not, the state is unknown and the connection is destroyed rather\n * than handed to unrelated code later.\n *\n * @returns The rollback failure, if the connection could not be normalized\n */\n const discardTxClient = async (txClient: PoolClient): Promise<unknown> => {\n try {\n await txClient.query('ROLLBACK');\n releaseTxClient(txClient);\n return undefined;\n } catch (rollbackError) {\n releaseTxClient(txClient, true);\n return rollbackError;\n }\n };\n\n /**\n * Keeps the first PostgreSQL statement failure attached to a transaction.\n *\n * PostgreSQL reports every later statement in an aborted transaction as\n * 25P02. Replacing the first failure with that generic state error hides the\n * actionable code and diagnostics from callers. The scoped executor instead\n * rethrows the first failure whenever PostgreSQL reports 25P02, and exposes\n * the recorded failure so a swallowed statement error cannot make COMMIT's\n * implicit rollback look successful.\n */\n const createTransactionExecutor = (txClient: PoolClient) => {\n let firstError: unknown;\n let firstUnconfirmedError: unknown;\n const pending = new Set<Promise<unknown>>();\n\n const isRollbackToSavepoint = (query: unknown): boolean => {\n const sql =\n typeof query === 'string'\n ? query\n : typeof query === 'object' &&\n query !== null &&\n 'text' in query &&\n typeof query.text === 'string'\n ? query.text\n : undefined;\n if (sql === undefined) return false;\n\n let statement = sql.trimStart();\n while (statement.startsWith('--') || statement.startsWith('/*')) {\n if (statement.startsWith('--')) {\n const newline = statement.search(/[\\r\\n]/);\n if (newline === -1) return false;\n statement = statement.slice(newline + 1).trimStart();\n continue;\n }\n const commentEnd = statement.indexOf('*/', 2);\n if (commentEnd === -1) return false;\n statement = statement.slice(commentEnd + 2).trimStart();\n }\n return /^ROLLBACK\\s+TO(?:\\s+SAVEPOINT)?\\s+/i.test(statement);\n };\n\n const errorCode = (error: unknown): string | undefined => {\n if (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof error.code === 'string'\n ) {\n return error.code;\n }\n return undefined;\n };\n\n const preserveFirstError = (error: unknown): unknown => {\n if (errorCode(error) === '25P02' && firstError !== undefined) {\n return firstError;\n }\n if (errorCode(error) === '25P02' && firstUnconfirmedError !== undefined) {\n return firstUnconfirmedError;\n }\n if (error instanceof PgDatabaseError) {\n firstError ??= error;\n } else {\n firstUnconfirmedError ??= error;\n }\n return error;\n };\n\n const track = <T>(operation: Promise<T>, query: unknown): Promise<T> => {\n const tracked = operation\n .then((result) => {\n if (isRollbackToSavepoint(query)) firstError = undefined;\n firstUnconfirmedError = undefined;\n return result;\n })\n .catch((error) => {\n throw preserveFirstError(error);\n });\n pending.add(tracked);\n void tracked.finally(() => pending.delete(tracked)).catch(() => {});\n return tracked;\n };\n\n const executor: PostgresQueryExecutor = {\n query: (sql, values) => track(txClient.query(sql, values), sql),\n };\n\n const client = new Proxy(txClient, {\n get(target, property) {\n if (property === 'query') {\n return (...args: any[]) => {\n const query = args[0];\n const callbackIndex =\n typeof args.at(-1) === 'function' ? args.length - 1 : -1;\n const submittable =\n typeof query === 'object' &&\n query !== null &&\n 'submit' in query &&\n typeof query.submit === 'function';\n\n if (submittable) {\n if (\n !('handleError' in query) ||\n typeof query.handleError !== 'function' ||\n !('handleReadyForQuery' in query) ||\n typeof query.handleReadyForQuery !== 'function'\n ) {\n return Reflect.apply(target.query, target, args);\n }\n\n const handleError = query.handleError;\n const handleReadyForQuery = query.handleReadyForQuery;\n let complete!: () => void;\n const completion = new Promise<void>((resolve) => {\n complete = resolve;\n });\n let completed = false;\n const finish = () => {\n if (completed) return;\n completed = true;\n query.handleError = handleError;\n query.handleReadyForQuery = handleReadyForQuery;\n pending.delete(completion);\n complete();\n };\n pending.add(completion);\n query.handleError = function (...handlerArgs: unknown[]) {\n const error = preserveFirstError(handlerArgs[0]);\n try {\n return Reflect.apply(handleError, this, [\n error,\n ...handlerArgs.slice(1),\n ]);\n } finally {\n finish();\n }\n };\n query.handleReadyForQuery = function (...handlerArgs: unknown[]) {\n try {\n const result = Reflect.apply(\n handleReadyForQuery,\n this,\n handlerArgs,\n );\n if (isRollbackToSavepoint(query)) firstError = undefined;\n firstUnconfirmedError = undefined;\n return result;\n } finally {\n finish();\n }\n };\n try {\n return Reflect.apply(target.query, target, args);\n } catch (error) {\n finish();\n throw preserveFirstError(error);\n }\n }\n\n if (callbackIndex !== -1) {\n const callback = args[callbackIndex];\n let complete!: () => void;\n const completion = new Promise<void>((resolve) => {\n complete = resolve;\n });\n pending.add(completion);\n args[callbackIndex] = (error: unknown, result: unknown) => {\n const callbackError =\n error === null || error === undefined\n ? error\n : preserveFirstError(error);\n if (callbackError === null || callbackError === undefined) {\n if (isRollbackToSavepoint(query)) firstError = undefined;\n firstUnconfirmedError = undefined;\n }\n pending.delete(completion);\n complete();\n callback(callbackError, result);\n };\n try {\n return Reflect.apply(target.query, target, args);\n } catch (error) {\n pending.delete(completion);\n complete();\n throw preserveFirstError(error);\n }\n }\n\n const result = Reflect.apply(target.query, target, args);\n if (\n typeof result === 'object' &&\n result !== null &&\n 'then' in result &&\n typeof result.then === 'function'\n ) {\n return track(Promise.resolve(result), query);\n }\n if (\n typeof result === 'object' &&\n result !== null &&\n 'once' in result &&\n typeof result.once === 'function'\n ) {\n let complete!: () => void;\n const completion = new Promise<void>((resolve) => {\n complete = resolve;\n });\n const finish = () => {\n pending.delete(completion);\n complete();\n };\n pending.add(completion);\n result.once('error', (error: unknown) => {\n preserveFirstError(error);\n finish();\n });\n result.once('end', () => {\n if (isRollbackToSavepoint(query)) firstError = undefined;\n firstUnconfirmedError = undefined;\n finish();\n });\n result.once('close', finish);\n }\n return result;\n };\n }\n return Reflect.get(target, property, target);\n },\n });\n\n return {\n client,\n executor,\n clearError: () => {\n firstError = undefined;\n },\n drain: async () => {\n while (pending.size > 0) {\n await Promise.allSettled([...pending]);\n }\n },\n getError: () => firstError,\n getUnconfirmedError: () => firstUnconfirmedError,\n };\n };\n\n const assertCommitted = (\n result: PgQueryResult,\n transactionState?: ReturnType<typeof createTransactionExecutor>,\n ): void => {\n if (result.command === 'COMMIT') return;\n\n const queryError =\n transactionState?.getError() ?? transactionState?.getUnconfirmedError();\n if (queryError !== undefined) throw queryError;\n\n throw new DatabaseError(\n 'PostgreSQL rolled back the transaction at commit',\n {\n command: result.command,\n },\n );\n };\n\n const executeNullAwarePostgresUpsertInTransaction = async (\n table: string,\n conflictColumns: string[],\n serializedData: Record<string, any>,\n ): Promise<BaseQueryResult> => {\n const txClient = await acquireTxClient();\n\n try {\n await txClient.query('BEGIN');\n const result = await executeNullAwarePostgresUpsert(\n txClient,\n table,\n conflictColumns,\n serializedData,\n );\n const commitResult = await txClient.query('COMMIT');\n assertCommitted(commitResult);\n releaseTxClient(txClient);\n return result;\n } catch (error) {\n const rollbackError = await discardTxClient(txClient);\n if (rollbackError !== undefined && error instanceof Error) {\n error.cause ??= rollbackError;\n }\n throw error;\n }\n };\n\n const executePostgresUpsert = async (\n executor: PostgresQueryExecutor,\n table: string,\n conflictColumns: string[],\n serializedData: Record<string, any>,\n options: UpsertOptions | undefined,\n acquireTransaction: boolean,\n ): Promise<BaseQueryResult> => {\n validateUpsertConflictColumns(table, conflictColumns, serializedData);\n\n if (\n options?.nullsDistinct ||\n !hasNullConflictValue(conflictColumns, serializedData)\n ) {\n return executeStandardPostgresUpsert(\n executor,\n table,\n conflictColumns,\n serializedData,\n );\n }\n\n if (await hasNativeNullsNotDistinctIndex(table, conflictColumns)) {\n return executeStandardPostgresUpsert(\n executor,\n table,\n conflictColumns,\n serializedData,\n );\n }\n\n if (!acquireTransaction) {\n return executeNullAwarePostgresUpsert(\n executor,\n table,\n conflictColumns,\n serializedData,\n );\n }\n\n return executeNullAwarePostgresUpsertInTransaction(\n table,\n conflictColumns,\n serializedData,\n );\n };\n\n /**\n * Template and values extracted from a tagged template literal\n */\n interface SqlTemplate {\n /**\n * SQL query with parameter placeholders\n */\n sql: string;\n\n /**\n * Values to use as parameters\n */\n values: any[];\n }\n\n /**\n * Parses a tagged template literal into a SQL query and values\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Object with SQL query and values array\n */\n const parseTemplate = (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): SqlTemplate => {\n let sql = strings[0];\n const values = [];\n for (let i = 0; i < vars.length; i++) {\n values.push(vars[i]);\n sql += `$${i + 1}${strings[i + 1]}`;\n }\n return { sql, values };\n };\n\n /**\n * Builds the complete set of query methods bound to one PostgreSQL client.\n *\n * The pool-backed interface and every transaction-scoped interface are\n * instantiated from this one factory, so there is exactly one implementation\n * of each method. The transaction interfaces used to be hand-maintained\n * copies of these bodies, and both had drifted:\n *\n * - they called the client bare, so a failure inside a transaction threw the\n * raw `pg` error rather than `DatabaseError`, and `instanceof` answered\n * differently depending on whether the call was in a transaction. `pg`\n * exports its own class also named `DatabaseError`, so nothing a developer\n * would check by eye showed the difference (#1115);\n * - they re-exposed `tableExists` and `syncSchema` from the enclosing scope,\n * which close over the *pool*. Both therefore ran on a different connection\n * than the transaction, so `tx.tableExists()` could not see a table created\n * in the same transaction and `tx.syncSchema()` committed its DDL\n * immediately, surviving a rollback (#1111).\n *\n * Binding a client is the only difference between the two, so it is the only\n * thing this factory parameterizes.\n *\n * @param executor - Pool or checked-out client every statement runs on\n * @param inTransaction - Whether `executor` is already inside a transaction\n * @param serialize - Applied to each record before `insert`/`update` builds\n * its SQL\n * @param clearTransactionError - Clears a statement error after a savepoint\n * has successfully restored the transaction to a usable state\n */\n const createClientMethods = (\n executor: PostgresQueryExecutor,\n inTransaction: boolean,\n serialize: (record: Record<string, any>) => Record<string, any>,\n clearTransactionError: () => void = () => {},\n ) => {\n /**\n * Inserts one or more records into a table\n *\n * @param table - Table name\n * @param data - Single record or array of records to insert\n * @returns Promise resolving to operation result\n * @throws Error if the insert operation fails\n *\n * @example Single record insert:\n * ```typescript\n * await db.insert('users', {\n * name: 'John Doe',\n * email: 'john@example.com'\n * });\n * ```\n *\n * @example Multiple record insert:\n * ```typescript\n * await db.insert('users', [\n * { name: 'John', email: 'john@example.com' },\n * { name: 'Jane', email: 'jane@example.com' }\n * ]);\n * ```\n */\n const insert = async (\n table: string,\n data: Record<string, any> | Record<string, any>[],\n ): Promise<BaseQueryResult> => {\n validateTableName(table);\n // If data is an array, we need to handle multiple rows\n if (Array.isArray(data)) {\n if (data.length === 0) {\n return { operation: 'insert', affected: 0 };\n }\n // Serialize all records in the array\n const serializedRecords = data.map((record) => serialize(record));\n const keys = resolveInsertColumns(table, serializedRecords);\n validateColumnNames(keys);\n const placeholders = serializedRecords\n .map(\n (_, i) =>\n `(${keys.map((_, j) => `$${i * keys.length + j + 1}`).join(', ')})`,\n )\n .join(', ');\n const query = `INSERT INTO ${table} (${keys.join(\n ', ',\n )}) VALUES ${placeholders}`;\n // Project through `keys` rather than each record's own key order, so a\n // record whose keys were inserted in a different order still binds each\n // value to its own column.\n const values = serializedRecords.flatMap((row) =>\n keys.map((key) => row[key]),\n );\n const result = await executor.query(query, values);\n return { operation: 'insert', affected: result.rowCount ?? 0 };\n }\n // If data is an object, we handle a single row\n const serializedData = serialize(data);\n const keys = Object.keys(serializedData);\n validateColumnNames(keys);\n const values = Object.values(serializedData);\n const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');\n const query = `INSERT INTO ${table} (${keys.join(\n ', ',\n )}) VALUES (${placeholders})`;\n const result = await executor.query(query, values);\n return { operation: 'insert', affected: result.rowCount ?? 0 };\n };\n\n /**\n * Retrieves a single record matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records\n * @returns Promise resolving to query result\n */\n const get = async (\n table: string,\n where: Record<string, any>,\n ): Promise<Record<string, any> | null> => {\n validateTableName(table);\n const { sql: whereClause, values } = buildWhere(where, 1, 'postgres');\n if (!whereClause) {\n throw new DatabaseError(\n 'GET requires at least one WHERE condition to prevent returning an arbitrary record',\n { table },\n );\n }\n\n const query = `SELECT * FROM ${table} ${whereClause}`;\n try {\n const result = await executor.query(query, values);\n return result.rows[0] || null;\n } catch (e) {\n throw new DatabaseError('Failed to retrieve record from table', {\n table,\n sql: query,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Retrieves multiple records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records\n * @returns Promise resolving to array of records\n */\n const list = async (\n table: string,\n where: Record<string, any>,\n ): Promise<Record<string, any>[]> => {\n validateTableName(table);\n const { sql: whereClause, values } = buildWhere(where, 1, 'postgres');\n const query = `SELECT * FROM ${table} ${whereClause}`;\n try {\n const result = await executor.query(query, values);\n return result.rows;\n } catch (e) {\n throw new DatabaseError('Failed to list records from table', {\n table,\n sql: query,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Updates records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records to update\n * @param data - New data to set\n * @returns Promise resolving to operation result\n */\n const update = async (\n table: string,\n where: Record<string, any>,\n data: Record<string, any>,\n ): Promise<BaseQueryResult> => {\n validateTableName(table);\n // Serialize the data to update\n const serializedData = serialize(data);\n const keys = Object.keys(serializedData);\n validateColumnNames(keys);\n const values = Object.values(serializedData);\n const setClause = keys.map((key, i) => `${key} = $${i + 1}`).join(', ');\n const { sql: whereClause, values: whereValues } = buildWhere(\n where,\n values.length + 1,\n 'postgres',\n );\n if (!whereClause) {\n throw new DatabaseError(\n 'UPDATE requires at least one WHERE condition to prevent accidental update of all records',\n { table },\n );\n }\n\n const sql = `UPDATE ${table} SET ${setClause} ${whereClause}`;\n try {\n const result = await executor.query(sql, [...values, ...whereValues]);\n return { operation: 'update', affected: result.rowCount ?? 0 };\n } catch (e) {\n throw new DatabaseError('Failed to update records in table', {\n table,\n sql,\n values: [...values, ...whereValues],\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Inserts a record or updates it if it already exists (UPSERT)\n *\n * @param table - Table name\n * @param conflictColumns - Columns that define the uniqueness constraint\n * @param data - Data to insert or update\n * @returns Promise resolving to operation result\n * @throws Error if the upsert operation fails\n */\n const upsert = async (\n table: string,\n conflictColumns: string[],\n data: Record<string, any>,\n options?: UpsertOptions,\n ): Promise<BaseQueryResult> => {\n validateTableName(table);\n // Snapshot both identifier sources once and validate the snapshots — the\n // exact arrays the executors interpolate — outside the try. `data` is\n // serialized (a plain-object snapshot) and `conflictColumns` is copied,\n // so neither a hostile record nor a hostile conflict-column array can\n // present different identifiers to the check and the SQL renderer. An\n // invalid identifier surfaces by name rather than as the generic wrapper\n // below; PostgreSQL columns are lowercased and quoted after validation.\n const serializedData = serializeRecord(data);\n const conflictCols = [...conflictColumns];\n validateColumnNames([...conflictCols, ...Object.keys(serializedData)]);\n try {\n return await executePostgresUpsert(\n executor,\n table,\n conflictCols,\n serializedData,\n options,\n // A transaction-scoped upsert is already inside a transaction; opening\n // a second one on another pooled connection would deadlock against it.\n !inTransaction,\n );\n } catch (e) {\n if (\n e instanceof DatabaseError &&\n e.message === 'Conflict columns missing from data'\n ) {\n throw e;\n }\n\n throw new DatabaseError('Failed to upsert record into table', {\n table,\n values: data,\n conflictColumns,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Gets a record matching the where criteria or inserts it if not found\n *\n * @param table - Table name\n * @param where - Criteria to match existing record\n * @returns Promise resolving to the query result or insert result\n */\n const getOrInsert = async (\n table: string,\n where: Record<string, any>,\n data: Record<string, any>,\n ): Promise<Record<string, any>> => {\n validateTableName(table);\n const result = await get(table, where);\n if (result) return result;\n await insert(table, data);\n\n const inserted = await get(table, where);\n if (!inserted) {\n throw new DatabaseError('Failed to insert and retrieve record', {\n table,\n where,\n data,\n });\n }\n return inserted;\n };\n\n /**\n * Deletes records from a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records for deletion\n * @returns Promise resolving to operation result with count of deleted rows\n * @throws Error if the delete operation fails\n */\n const deleteRecords = async (\n table: string,\n where: Record<string, any>,\n ): Promise<BaseQueryResult> => {\n validateTableName(table);\n\n const keys = Object.keys(where);\n if (keys.length === 0) {\n throw new DatabaseError(\n 'DELETE requires at least one WHERE condition to prevent accidental deletion of all records',\n { table },\n );\n }\n\n const { sql: whereClause, values } = buildWhere(where, 1, 'postgres');\n\n try {\n const result = await executor.query(\n `DELETE FROM ${table} ${whereClause}`,\n values,\n );\n\n return { operation: 'delete', affected: result.rowCount ?? 0 };\n } catch (e) {\n throw new DatabaseError('Failed to delete records from table', {\n table,\n where,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Counts records in a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (optional, counts all if omitted)\n * @returns Promise resolving to count of matching records\n * @throws Error if the count operation fails\n */\n const count = async (\n table: string,\n where?: Record<string, any>,\n ): Promise<number> => {\n validateTableName(table);\n\n try {\n if (!where || Object.keys(where).length === 0) {\n // Count all records\n const result = await executor.query(\n `SELECT COUNT(*) as count FROM ${table}`,\n );\n return Number(result.rows[0]?.count) || 0;\n }\n\n // Count with conditions\n const { sql: whereClause, values } = buildWhere(where, 1, 'postgres');\n\n const result = await executor.query(\n `SELECT COUNT(*) as count FROM ${table} ${whereClause}`,\n values,\n );\n\n return Number(result.rows[0]?.count) || 0;\n } catch (e) {\n throw new DatabaseError('Failed to count records in table', {\n table,\n where,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Creates a table-specific interface for simplified table operations\n *\n * @param tableName - Table name\n * @returns TableMethods interface for the specified table\n */\n const table = (tableName: string): TableInterface => {\n return {\n insert: (data) => insert(tableName, data),\n get: (data) => get(tableName, data),\n list: (data) => list(tableName, data),\n };\n };\n\n /**\n * Executes a SQL query using template literals and returns a single value\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single value (first column of first row)\n */\n const pluck = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<any> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const result = await executor.query(sql, values);\n const firstRow = result.rows[0];\n if (!firstRow) return null;\n // Return the first column value from the first row\n return Object.values(firstRow)[0];\n } catch (e) {\n throw new DatabaseError('Failed to execute pluck query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals and returns a single row\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single result record or null\n */\n const single = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<Record<string, any> | null> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const result = await executor.query(sql, values);\n return result.rows[0] || null;\n } catch (e) {\n throw new DatabaseError('Failed to execute single query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals and returns multiple rows\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to array of result records\n */\n const many = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<Record<string, any>[]> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const result = await executor.query(sql, values);\n return result.rows;\n } catch (e) {\n throw new DatabaseError('Failed to execute many query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals without returning results\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise that resolves when the query completes\n */\n const execute = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<void> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n await executor.query(sql, values);\n } catch (e) {\n throw new DatabaseError('Failed to execute query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a raw SQL query with parameterized values\n *\n * Uses PostgreSQL-native placeholders ($1, $2, ...). Legacy ? placeholders\n * are converted only when the placeholder count matches the supplied values,\n * so Postgres operators such as JSONB ? remain intact.\n *\n * @param sql - SQL query string\n * @param values - Variables to use as parameters\n * @returns Promise resolving to query result with rows and count\n */\n const query = async (\n sql: string,\n ...values: any[]\n ): Promise<{ rows: Record<string, any>[]; rowCount: number }> => {\n const query = normalizePostgresRawQuery(sql, values);\n try {\n const result = await executor.query(query.sql, query.values);\n // A successful rollback to a caller-managed savepoint restores the\n // transaction to the state at that savepoint, so any recorded\n // statement failure has been contained and must no longer poison the\n // enclosing commit. This mirrors the adapter-managed savepoint paths.\n return {\n rows: result.rows,\n rowCount: result.rowCount ?? 0,\n };\n } catch (e) {\n throw wrapDatabaseError('Failed to execute raw query', e, {\n sql: query.sql,\n values: query.values,\n });\n }\n };\n\n /**\n * Checks if a table exists in the database\n *\n * @param tableName - Name of the table to check\n * @returns Promise resolving to boolean indicating if the table exists\n */\n const tableExists = async (tableName: string): Promise<boolean> => {\n const result = await executor.query(\n `SELECT EXISTS (\n SELECT 1\n FROM information_schema.tables\n WHERE table_name = $1\n AND table_schema = (\n SELECT namespace.nspname\n FROM pg_catalog.pg_class AS relation\n JOIN pg_catalog.pg_namespace AS namespace\n ON namespace.oid = relation.relnamespace\n WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident($1))\n )\n )`,\n [tableName],\n );\n return result.rows[0].exists;\n };\n\n /**\n * Synchronizes database schema with provided SQL DDL\n * Creates tables if they don't exist and adds missing columns\n * Also executes CREATE INDEX statements\n *\n * @param schema - SQL schema definition with CREATE TABLE and CREATE INDEX statements\n * @returns Promise that resolves when schema is synchronized\n */\n /**\n * Runs a step whose failure `syncSchema` deliberately tolerates.\n *\n * Logging and continuing is self-contained on the pool. Inside a\n * transaction it is not: a failed statement puts PostgreSQL in the aborted\n * state, and `COMMIT` on an aborted transaction *succeeds* — it reports a\n * ROLLBACK command tag and no error. The caller is told the transaction\n * committed while every write in it was discarded, which is the silent\n * data loss this whole epic is about, re-entering through the schema path.\n *\n * A savepoint scopes the failure to the step, which is what \"log it and\n * continue\" was always supposed to mean.\n */\n const runTolerated = async <T>(work: () => Promise<T>): Promise<T> => {\n if (!inTransaction) {\n return work();\n }\n savepointSequence += 1;\n const name = `hv_sync_${savepointSequence}`;\n await executor.query(`SAVEPOINT ${name}`);\n try {\n const result = await work();\n await executor.query(`RELEASE SAVEPOINT ${name}`);\n return result;\n } catch (error) {\n // ROLLBACK TO leaves the savepoint defined, so release it too.\n await executor.query(`ROLLBACK TO SAVEPOINT ${name}`);\n clearTransactionError();\n await executor.query(`RELEASE SAVEPOINT ${name}`);\n throw error;\n }\n };\n\n const syncSchema = async (schema: string): Promise<void> => {\n const commands = schema\n .trim()\n .split(';')\n .filter((command) => command.trim() !== '');\n\n // Match CREATE INDEX statements (Issues #867 and #1040).\n // Named groups keep optional clauses from shifting identifier captures.\n const createIndexRegex =\n /CREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+(?:CONCURRENTLY\\s+)?(?:IF\\s+NOT\\s+EXISTS\\s+)?\"?(?<indexName>\\w+)\"?\\s+ON\\s+\"?(?<tableName>\\w+)\"?\\s*(?:USING\\s+\"?\\w+\"?\\s*)?\\([^)]+\\)/i;\n const parseCreateIndex = (\n command: string,\n ): { indexName: string; tableName: string } | null => {\n const { indexName, tableName } =\n command.match(createIndexRegex)?.groups ?? {};\n return indexName && tableName ? { indexName, tableName } : null;\n };\n\n // Pre-scan commands to collect all index names for batch existence check (Issue #798)\n const indexTargets: Array<{ indexName: string; tableName: string }> = [];\n for (const command of commands) {\n const indexMatch = parseCreateIndex(command.trim());\n if (indexMatch) {\n indexTargets.push(indexMatch);\n }\n }\n\n // Resolve each table through the executor's search_path. Index names are\n // only unique within a schema, so a name-only public lookup can suppress\n // a needed index when another schema has the same name.\n const existingIndexes = new Set<string>();\n if (indexTargets.length > 0) {\n const result = await executor.query(\n `SELECT target.table_name, target.index_name\n FROM unnest($1::text[], $2::text[])\n AS target(table_name, index_name)\n JOIN pg_catalog.pg_class AS relation\n ON relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(target.table_name))\n JOIN pg_catalog.pg_namespace AS namespace\n ON namespace.oid = relation.relnamespace\n JOIN pg_catalog.pg_indexes AS existing\n ON existing.schemaname = namespace.nspname\n AND existing.tablename = target.table_name\n AND existing.indexname = target.index_name`,\n [\n indexTargets.map((target) => target.tableName),\n indexTargets.map((target) => target.indexName),\n ],\n );\n for (const row of result.rows) {\n existingIndexes.add(`${row.table_name}\\u0000${row.index_name}`);\n }\n }\n\n for (const command of commands) {\n const trimmedCommand = command.trim();\n\n // Match CREATE TABLE with optional quotes around table name\n // Supports: CREATE TABLE foo, CREATE TABLE \"foo\", CREATE TABLE IF NOT EXISTS \"foo\"\n const createTableRegex =\n /CREATE TABLE (IF NOT EXISTS )?\"?(\\w+)\"? \\(([\\s\\S]+)\\)/i;\n const tableMatch = trimmedCommand.match(createTableRegex);\n\n if (tableMatch) {\n const tableName = tableMatch[2];\n const columns = tableMatch[3].trim().split(',\\n');\n\n // Check if table exists\n const exists = await tableExists(tableName);\n\n if (!exists) {\n // Table doesn't exist, create it\n await executor.query(trimmedCommand);\n } else {\n // Table exists, check for missing columns\n for (const column of columns) {\n const columnDef = column.trim();\n // Table constraints are not column definitions. In particular,\n // a quoted constraint name would otherwise let the unanchored\n // column matcher start at that name and generate an invalid\n // ADD COLUMN statement.\n if (\n /^(?:CONSTRAINT\\s+(?:\"(?:[^\"]|\"\")*\"|\\w+)\\s+)?(?:(?:PRIMARY\\s+KEY|FOREIGN\\s+KEY|UNIQUE|CHECK)\\b|EXCLUDE(?=\\s+(?:USING\\b|\\()))/i.test(\n columnDef,\n )\n ) {\n continue;\n }\n // Match column name with optional quotes: \"id\" or id\n const columnMatch = columnDef.match(\n /^(\"(?:[^\"]|\"\")*\"|\\w+)\\s+\\w+[^,]*/,\n );\n\n if (columnMatch) {\n const identifier = columnMatch[1];\n const columnName = identifier.startsWith('\"')\n ? identifier.slice(1, -1).replace(/\"\"/g, '\"')\n : identifier;\n\n // Skip constraint definitions\n if (\n columnName.toUpperCase() === 'PRIMARY' ||\n columnName.toUpperCase() === 'FOREIGN' ||\n columnName.toUpperCase() === 'UNIQUE' ||\n columnName.toUpperCase() === 'CHECK' ||\n columnName.toUpperCase() === 'CONSTRAINT'\n ) {\n continue;\n }\n\n try {\n await runTolerated(async () => {\n // Check if column exists\n const columnExists = await executor.query(\n `SELECT EXISTS (\n SELECT 1 FROM information_schema.columns\n WHERE table_name = $1\n AND column_name = $2\n AND table_schema = (\n SELECT namespace.nspname\n FROM pg_catalog.pg_class AS relation\n JOIN pg_catalog.pg_namespace AS namespace\n ON namespace.oid = relation.relnamespace\n WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident($1))\n )\n )`,\n [tableName, columnName],\n );\n\n if (!columnExists.rows[0].exists) {\n // Column doesn't exist, add it\n // Quote the table name for safety\n const alterCommand = `ALTER TABLE \"${tableName}\" ADD COLUMN ${columnDef}`;\n await executor.query(alterCommand);\n }\n });\n } catch (error) {\n // If there's an error checking/adding the column, log it but continue\n console.error(\n `Error adding column ${columnName} to ${tableName}:`,\n error,\n );\n }\n }\n }\n }\n continue;\n }\n\n const indexMatch = parseCreateIndex(trimmedCommand);\n\n if (indexMatch) {\n const { indexName, tableName: indexTableName } = indexMatch;\n\n // Use pre-fetched batch result instead of per-index query\n const indexKey = `${indexTableName}\\u0000${indexName}`;\n if (!existingIndexes.has(indexKey)) {\n try {\n await runTolerated(() => executor.query(trimmedCommand));\n // Track newly created index for idempotency within this call\n existingIndexes.add(indexKey);\n } catch (error) {\n // Log error but continue - index creation failures shouldn't block schema sync\n console.warn(\n `Warning: Failed to create index ${indexName} on ${indexTableName}:`,\n error instanceof Error ? error.message : String(error),\n );\n }\n }\n continue;\n }\n\n // For any other DDL statements (like other CREATE commands), try to execute them\n // This provides forward compatibility for future DDL types\n if (\n trimmedCommand.toUpperCase().startsWith('CREATE ') &&\n !trimmedCommand.toUpperCase().includes('CREATE TABLE')\n ) {\n try {\n await runTolerated(() => executor.query(trimmedCommand));\n } catch (error) {\n // Log but don't fail - the statement may have already been executed\n console.warn(\n `Warning: DDL statement may have failed:`,\n error instanceof Error ? error.message : String(error),\n );\n }\n }\n }\n };\n\n // Shorthand aliases for query methods\n const oo = many; // (o)bjective-(o)bjects: returns multiple rows\n const oO = single; // (o)bjective-(O)bject: returns a single row\n const ox = pluck; // (o)bjective-(x): returns a single value\n const xx = execute; // (x)ecute-(x)ecute: executes without returning\n\n return {\n insert,\n get,\n list,\n update,\n upsert,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n };\n };\n\n const {\n insert,\n get,\n list,\n update,\n upsert,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n } = createClientMethods(client, false, serializeRecord);\n\n /**\n * Acquire a pinned connection from the pool for session-scoped state such as\n * `pg_advisory_lock`. Every query on the returned handle runs on the same\n * client until {@link SessionHandle.release} drops the connection (which\n * frees any session locks it held). The connection is held for the handle's\n * lifetime, so hold one session per process, not per operation.\n */\n const acquireSession = async (): Promise<SessionHandle> => {\n const sessionClient = await pool.connect();\n let released = false;\n let lost = false;\n\n // pg-pool removes its idle 'error' listener when a client is checked out.\n // A session is pinned for the process lifetime and mostly idle between\n // queries, so a backend disconnect/failover would emit 'error' on a\n // listener-less client and crash the process. Absorb it and mark the\n // session dead instead; the next query()/isActive() surfaces it.\n sessionClient.on('error', () => {\n lost = true;\n });\n\n return {\n query: async (sql: string, ...values: any[]) => {\n if (released) {\n throw new DatabaseError('Session has been released', {});\n }\n if (lost) {\n throw new DatabaseError('Session connection was lost', {});\n }\n const normalized = normalizePostgresRawQuery(sql, values);\n try {\n const result = await sessionClient.query(\n normalized.sql,\n normalized.values,\n );\n return {\n rows: result.rows,\n rowCount: result.rowCount ?? 0,\n };\n } catch (e) {\n throw wrapDatabaseError('Failed to execute session query', e, {\n sql: normalized.sql,\n values: normalized.values,\n });\n }\n },\n isActive: () => !released && !lost,\n release: async () => {\n if (released) return;\n released = true;\n // Free session advisory locks before tearing down so the contract\n // \"after release() the locks are gone\" holds deterministically — the\n // destroy below is async (TCP teardown) and resolves later. Skip on a\n // lost connection (nothing to talk to; backend exit frees them anyway).\n if (!lost) {\n try {\n // A caller that left the connection in an aborted transaction\n // (BEGIN, a failed statement, then release) would make\n // pg_advisory_unlock_all() error with \"current transaction is\n // aborted\". Roll back first (a no-op when no transaction is open)\n // so the unlock runs and the deterministic-release contract holds.\n await sessionClient.query('ROLLBACK');\n } catch {\n // No open transaction, or already gone — proceed to unlock.\n }\n try {\n await sessionClient.query('SELECT pg_advisory_unlock_all()');\n } catch {\n // Best-effort; the destroy below frees locks at backend exit.\n }\n }\n // Destroy the physical connection rather than returning it to the pool.\n // Returning it would NOT clear residual session state, which could then\n // leak onto a connection later handed to unrelated code.\n sessionClient.release(true);\n },\n };\n };\n\n // Names nested savepoints. Only ever appended to an identifier the adapter\n // generates, never caller input.\n let savepointSequence = 0;\n\n /**\n * Builds the `transaction` exposed on a transaction-scoped interface.\n *\n * Re-entering runs the callback under a SAVEPOINT on the *same* connection,\n * which is what callers assume nesting already did. Re-exposing the top-level\n * `transaction` here (the previous behaviour) checked out a second pooled\n * connection and began an independent transaction, so the nested work could\n * not see the enclosing transaction's uncommitted rows, and if the enclosing\n * transaction held a lock the nested one needed, the two deadlocked in a way\n * PostgreSQL cannot detect: the outer connection is blocked on a promise\n * rather than on a lock, so `deadlock_timeout` never fires and the process\n * hangs until some unrelated timeout.\n *\n * @param executor - Error-preserving executor bound to the enclosing client\n * @param scopeFor - Builds the interface handed to the nested callback\n */\n const createNestedTransaction = (\n executor: PostgresQueryExecutor,\n scopeFor: () => DatabaseInterface,\n clearTransactionError: () => void,\n ) => {\n // PostgreSQL permits nested savepoints, but a sibling savepoint cannot be\n // released while a later sibling is still open: releasing the earlier one\n // implicitly discards the later savepoint. Each scope gets its own queue:\n // child scopes are queued behind their parent, while work inside a child\n // gets a fresh queue for its own children.\n interface NestedScope {\n tail: Promise<void>;\n }\n type NestedTransaction = (<T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ) => Promise<T>) & {\n drain: () => Promise<void>;\n };\n const nestedScope = new AsyncLocalStorage<NestedScope>();\n const rootScope: NestedScope = { tail: Promise.resolve() };\n\n const runScope = async <T>(\n scope: NestedScope,\n callback: (tx: DatabaseInterface) => Promise<T>,\n ): Promise<T> => {\n savepointSequence += 1;\n const name = `hv_sp_${savepointSequence}`;\n await executor.query(`SAVEPOINT ${name}`);\n try {\n const result = await callback(scopeFor());\n // A callback may start nested work without awaiting it. Its savepoint\n // must remain open until that work is finished.\n await scope.tail;\n await executor.query(`RELEASE SAVEPOINT ${name}`);\n return result;\n } catch (error) {\n // Promise.all rejects as soon as one sibling fails, but queued siblings\n // are still tied to this savepoint. Let them finish before unwinding.\n await scope.tail;\n try {\n // ROLLBACK TO leaves the savepoint defined, so release it too or it\n // accumulates for the life of the transaction.\n await executor.query(`ROLLBACK TO SAVEPOINT ${name}`);\n clearTransactionError();\n await executor.query(`RELEASE SAVEPOINT ${name}`);\n } catch {\n // The enclosing transaction is already unwinding; its own teardown\n // owns the connection from here.\n }\n throw error;\n }\n };\n\n const transaction: NestedTransaction = async <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ): Promise<T> => {\n const parentScope = nestedScope.getStore() ?? rootScope;\n const scope: NestedScope = { tail: Promise.resolve() };\n const current = parentScope.tail.then(() =>\n nestedScope.run(scope, () => runScope(scope, callback)),\n );\n parentScope.tail = current.then(\n () => undefined,\n () => undefined,\n );\n return current;\n };\n transaction.drain = () => rootScope.tail;\n return transaction;\n };\n\n /**\n * Executes a callback within a database transaction\n * Automatically commits on success or rolls back on error\n *\n * @param callback - Function to execute within transaction\n * @returns Promise resolving to callback result\n */\n const transaction = async <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ): Promise<T> => {\n // Get a client from the pool for the transaction\n const txClient = await acquireTxClient();\n\n let nestedTransaction:\n | ReturnType<typeof createNestedTransaction>\n | undefined;\n const transactionState = createTransactionExecutor(txClient);\n try {\n await txClient.query('BEGIN');\n\n // Create a transaction-scoped database interface\n let txDb!: DatabaseInterface;\n nestedTransaction = createNestedTransaction(\n transactionState.executor,\n () => txDb,\n transactionState.clearError,\n );\n txDb = {\n url,\n client: transactionState.client,\n ...createClientMethods(\n transactionState.executor,\n true,\n serializeRecord,\n transactionState.clearError,\n ),\n transaction: nestedTransaction,\n };\n\n const result = await callback(txDb);\n await nestedTransaction.drain();\n await transactionState.drain();\n const statementError = transactionState.getError();\n if (statementError !== undefined) {\n throw statementError;\n }\n const commitResult = await txClient.query('COMMIT');\n assertCommitted(commitResult, transactionState);\n releaseTxClient(txClient);\n return result;\n } catch (error) {\n // Promise.all may reject before queued nested scopes finish. Do not\n // release this client until their savepoint work has drained.\n await nestedTransaction?.drain();\n await transactionState.drain();\n // Never let a failing ROLLBACK replace the caller's error. On a dead\n // connection the rollback throws \"Connection terminated unexpectedly\",\n // which would otherwise be the only thing the caller ever sees.\n const rollbackError = await discardTxClient(txClient);\n if (rollbackError !== undefined && error instanceof Error) {\n error.cause ??= rollbackError;\n }\n throw error;\n }\n };\n\n /**\n * Begins a new transaction and returns a handle for manual control\n *\n * Unlike transaction(), this gives you explicit control over commit/rollback.\n * Ideal for test isolation where you want to rollback after each test.\n *\n * @returns Promise resolving to a TransactionHandle\n */\n const beginTransaction = async (): Promise<TransactionHandle> => {\n // Get a client from the pool for the transaction\n const txClient = await acquireTxClient();\n try {\n await txClient.query('BEGIN');\n } catch (error) {\n // BEGIN failed, so there is no transaction for the caller to end and no\n // handle to release the client — return it here or it is stranded.\n releaseTxClient(txClient, true);\n throw error;\n }\n\n let active = true;\n let nestedTransaction:\n | ReturnType<typeof createNestedTransaction>\n | undefined;\n const transactionState = createTransactionExecutor(txClient);\n\n // COMMIT and ROLLBACK both throw in ordinary operation — deferred\n // constraint violations, serialization failures, a connection lost at\n // commit time. The transaction is over either way, so end it and return the\n // client before rethrowing; leaving it checked out would strand a pooled\n // connection permanently and eventually deadlock the pool.\n const end = async (command: 'COMMIT' | 'ROLLBACK'): Promise<void> => {\n if (!active) {\n throw new DatabaseError('Transaction already ended', {});\n }\n try {\n await nestedTransaction?.drain();\n await transactionState.drain();\n const statementError = transactionState.getError();\n if (command === 'COMMIT' && statementError !== undefined) {\n throw statementError;\n }\n const result = await txClient.query(command);\n if (command === 'COMMIT') assertCommitted(result, transactionState);\n active = false;\n releaseTxClient(txClient);\n } catch (error) {\n active = false;\n const rollbackError = await discardTxClient(txClient);\n if (rollbackError !== undefined && error instanceof Error) {\n error.cause ??= rollbackError;\n }\n throw error;\n }\n };\n\n const commit = (): Promise<void> => end('COMMIT');\n\n const rollback = (): Promise<void> => end('ROLLBACK');\n\n const isActive = (): boolean => active;\n\n // Create a transaction-scoped database interface with commit/rollback\n let txHandle!: TransactionHandle;\n nestedTransaction = createNestedTransaction(\n transactionState.executor,\n () => txHandle,\n transactionState.clearError,\n );\n txHandle = {\n url,\n client: transactionState.client,\n ...createClientMethods(\n transactionState.executor,\n true,\n serializeRecord,\n transactionState.clearError,\n ),\n transaction: nestedTransaction,\n commit,\n rollback,\n isActive,\n };\n\n return txHandle;\n };\n\n /**\n * Initialize database schemas from JSON manifest\n * Supports dependency resolution and schema overrides\n *\n * @param options - Schema initialization options\n * @returns Promise that resolves when schemas are initialized\n */\n const initializeSchemas = async (\n options: SchemaInitializationOptions,\n ): Promise<void> => {\n const schemaManager = new DatabaseSchemaManager();\n const currentDb: DatabaseInterface = {\n url,\n client,\n insert,\n update,\n upsert,\n get,\n getOrInsert,\n delete: deleteRecords,\n count,\n list,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n transaction,\n };\n\n await schemaManager.initializeSchemas(currentDb, options);\n };\n\n /**\n * Retrieves the schema information for a table\n *\n * @param table - Table name\n * @returns Promise resolving to table schema info or null if table doesn't exist\n * @throws Error if the query fails\n */\n const getTableSchema = async (\n table: string,\n ): Promise<TableSchemaInfo | null> => {\n validateTableName(table);\n\n try {\n // Check if table exists\n const exists = await tableExists(table);\n if (!exists) {\n return null;\n }\n\n // Get column information from information_schema\n const columnRows = await many`\n SELECT\n column_name,\n data_type,\n is_nullable,\n column_default\n FROM information_schema.columns\n WHERE table_name = ${table}\n AND table_schema = (\n SELECT namespace.nspname\n FROM pg_catalog.pg_class AS relation\n JOIN pg_catalog.pg_namespace AS namespace\n ON namespace.oid = relation.relnamespace\n WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(${table}))\n )\n ORDER BY ordinal_position\n `;\n\n // Get primary key columns\n const pkRows = await many`\n SELECT kcu.column_name\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n WHERE tc.table_name = ${table}\n AND tc.table_schema = (\n SELECT namespace.nspname\n FROM pg_catalog.pg_class AS relation\n JOIN pg_catalog.pg_namespace AS namespace\n ON namespace.oid = relation.relnamespace\n WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(${table}))\n )\n AND tc.constraint_type = 'PRIMARY KEY'\n `;\n\n const pkColumns = new Set(pkRows.map((row) => row.column_name));\n\n const columns: Record<string, ColumnDefinition> = {};\n for (const row of columnRows) {\n const colName = row.column_name as string;\n columns[colName] = {\n type: row.data_type as string,\n primaryKey: pkColumns.has(colName),\n notNull: row.is_nullable === 'NO',\n defaultValue: row.column_default,\n };\n }\n\n // Get index information from pg_indexes\n const indexRows = await many`\n SELECT\n indexname,\n indexdef\n FROM pg_indexes\n WHERE tablename = ${table}\n AND schemaname = (\n SELECT namespace.nspname\n FROM pg_catalog.pg_class AS relation\n JOIN pg_catalog.pg_namespace AS namespace\n ON namespace.oid = relation.relnamespace\n WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(${table}))\n )\n AND indexname NOT LIKE '%_pkey'\n `;\n\n const indexes: IndexDefinition[] = [];\n for (const row of indexRows) {\n const indexName = row.indexname as string;\n const indexDef = row.indexdef as string;\n\n // Parse column names from index definition\n // Example: CREATE INDEX idx_name ON table (col1, col2)\n const colMatch = indexDef.match(/\\(([^)]+)\\)/);\n const indexColumns = colMatch\n ? colMatch[1].split(',').map((col) => col.trim())\n : [];\n\n const isUnique = indexDef.toUpperCase().includes('UNIQUE');\n\n indexes.push({\n name: indexName,\n columns: indexColumns,\n unique: isUnique,\n });\n }\n\n // Get foreign key information\n const fkRows = await many`\n SELECT\n kcu.column_name,\n ccu.table_name AS foreign_table_name,\n ccu.column_name AS foreign_column_name,\n rc.delete_rule,\n rc.update_rule\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n JOIN information_schema.referential_constraints AS rc\n ON rc.constraint_name = tc.constraint_name\n AND rc.constraint_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY'\n AND tc.table_name = ${table}\n AND tc.table_schema = (\n SELECT namespace.nspname\n FROM pg_catalog.pg_class AS relation\n JOIN pg_catalog.pg_namespace AS namespace\n ON namespace.oid = relation.relnamespace\n WHERE relation.oid = pg_catalog.to_regclass(pg_catalog.quote_ident(${table}))\n )\n `;\n\n const foreignKeys: Array<{\n column: string;\n referencesTable: string;\n referencesColumn: string;\n onDelete?: string;\n onUpdate?: string;\n }> = [];\n\n for (const fkRow of fkRows) {\n foreignKeys.push({\n column: fkRow.column_name as string,\n referencesTable: fkRow.foreign_table_name as string,\n referencesColumn: fkRow.foreign_column_name as string,\n onDelete: fkRow.delete_rule as string | undefined,\n onUpdate: fkRow.update_rule as string | undefined,\n });\n }\n\n return {\n tableName: table,\n columns,\n indexes,\n foreignKeys,\n };\n } catch (e) {\n throw new DatabaseError('Failed to retrieve table schema', {\n table,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * ALTER TABLE operations for schema evolution\n */\n const alterTable = {\n /**\n * Adds a new column to an existing table\n *\n * @param table - Table name\n * @param column - Column definition with name\n * @returns Promise that resolves when column is added\n * @throws Error if the alter operation fails\n */\n addColumn: async (\n table: string,\n column: ColumnDefinitionWithName,\n ): Promise<void> => {\n validateTableName(table);\n validateColumnName(column.name);\n\n let sql: string | undefined;\n try {\n sql = generateAddColumnStatement(table, column, 'postgres');\n await client.query(sql);\n } catch (e) {\n throw wrapDatabaseError('Failed to add column to table', e, {\n table,\n column: column.name,\n ...(sql ? { sql } : {}),\n });\n }\n },\n\n /**\n * Adds a new index to an existing table\n *\n * @param table - Table name\n * @param index - Index definition\n * @returns Promise that resolves when index is created\n * @throws Error if the create index operation fails\n */\n addIndex: async (table: string, index: IndexDefinition): Promise<void> => {\n validateTableName(table);\n validateIndexName(index.name);\n\n for (const col of index.columns) {\n validateColumnName(col);\n }\n\n let sql: string | undefined;\n try {\n sql = generateCreateIndexStatement(table, index);\n await client.query(sql);\n } catch (e) {\n throw wrapDatabaseError('Failed to create index on table', e, {\n table,\n index: index.name,\n ...(sql ? { sql } : {}),\n });\n }\n },\n };\n\n db = {\n url,\n client,\n insert,\n update,\n upsert,\n get,\n getOrInsert,\n delete: deleteRecords,\n count,\n list,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n initializeSchemas,\n transaction,\n beginTransaction,\n acquireSession,\n getTableSchema,\n alterTable,\n vector: createVectorCapabilities(pool),\n close: async () => {\n await pool.end();\n },\n };\n\n return db;\n}\n","import { loadEnvConfig } from '@happyvertical/utils';\nimport type { PostgresOptions } from './postgres';\nimport type {\n DatabaseInterface,\n DuckDBOptions,\n JSONOptions,\n} from './shared/types';\nimport type { SqliteOptions } from './sqlite';\n\n/**\n * Union type of options for creating different database types\n */\nexport type GetDatabaseOptions =\n | (PostgresOptions & { type?: 'postgres' })\n | (SqliteOptions & { type?: 'sqlite' })\n | (DuckDBOptions & { type?: 'duckdb' })\n | JSONOptions;\n\n/**\n * Checks if the provided value is a database instance rather than configuration options\n *\n * @param value - Value to check\n * @returns True if the value appears to be a DatabaseInterface instance\n */\nfunction isDatabaseInstance(value: any): value is DatabaseInterface {\n return (\n value &&\n typeof value === 'object' &&\n typeof value.client !== 'undefined' &&\n typeof value.insert === 'function' &&\n typeof value.get === 'function' &&\n typeof value.query === 'function'\n );\n}\n\n/**\n * Creates a database connection based on the provided options, or returns an existing database instance\n *\n * Loads configuration from environment variables using the HAVE_SQL_* pattern:\n * - HAVE_SQL_TYPE → type ('sqlite' | 'postgres' | 'duckdb' | 'json')\n * - HAVE_SQL_URL → url (connection string)\n * - HAVE_SQL_HOST → host (database server hostname)\n * - HAVE_SQL_PORT → port (database server port number)\n * - HAVE_SQL_DATABASE → database (database name)\n * - HAVE_SQL_USER → user (authentication username)\n * - HAVE_SQL_PASSWORD → password (authentication password)\n *\n * User-provided options always take precedence over environment variables.\n *\n * ## Lazy schemas\n *\n * The `schemas` option accepts either an eagerly-built record or a lazy\n * function (`() => Record<string, SchemaProvider>`). Adapters that manage\n * tables via migrations (Postgres, SQLite) never resolve the function, so\n * there is zero cost for callers that pass schemas uniformly regardless of\n * adapter type. Only JSON and DuckDB adapters call the function.\n *\n * ```typescript\n * // Caller doesn't need to know the adapter type:\n * await getDatabase({\n * ...config,\n * schemas: () => ObjectRegistry.getAllSchemas(),\n * });\n * ```\n *\n * @param options - Configuration options for the database connection or an existing database instance\n * @returns Promise resolving to a DatabaseInterface implementation\n * @throws Error if the database type is invalid\n */\nexport async function getDatabase(\n options: GetDatabaseOptions | DatabaseInterface = {},\n): Promise<DatabaseInterface> {\n // If a database instance is passed, return it directly\n if (isDatabaseInstance(options)) {\n return options;\n }\n\n // Load HAVE_SQL_* environment variables and merge into options\n // This ensures options object is mutated (needed for dbid propagation)\n Object.assign(\n options,\n loadEnvConfig(options as any, {\n packageName: 'sql',\n schema: {\n type: 'string',\n url: 'string',\n host: 'string',\n port: 'number',\n database: 'string',\n user: 'string',\n password: 'string',\n },\n }) as Partial<GetDatabaseOptions>,\n );\n\n // if no type but url starts with file:, set to sqlite\n if (\n !options.type &&\n (options.url?.startsWith('file:') || options.url === ':memory:')\n ) {\n options.type = 'sqlite';\n }\n\n if (options.type === 'postgres') {\n const postgres = await import('./postgres.js');\n return postgres.getDatabase(options as PostgresOptions);\n }\n if (options.type === 'sqlite') {\n const sqlite = await import('./sqlite.js');\n return sqlite.getDatabase(options as SqliteOptions);\n }\n if (options.type === 'duckdb') {\n const duckdb = await import('./duckdb.js');\n return duckdb.getDatabase(options as DuckDBOptions);\n }\n if (options.type === 'json') {\n const json = await import('./json.js');\n return json.getDatabase(options as JSONOptions);\n }\n throw new Error('Invalid database type');\n}\n\n/**\n * Validates if a table name consists only of alphanumeric characters and underscores\n *\n * @param name - Table name to validate\n * @returns Boolean indicating if the name is valid\n */\nfunction _isValidTableName(name: string): boolean {\n // Simple regex to allow only alphanumeric characters and underscores\n return /^[a-zA-Z0-9_]+$/.test(name);\n}\n\n/**\n * Synchronizes a SQL schema definition with a database\n * Creates tables if they don't exist and adds missing columns to existing tables\n *\n * @param options - Object containing database and schema\n * @param options.db - Database interface to use\n * @param options.schema - SQL schema definition\n * @throws Error if db or schema are missing or if the database doesn't support syncSchema\n */\nexport async function syncSchema(options: {\n db: DatabaseInterface;\n schema: string;\n}) {\n const { db, schema } = options;\n if (!db || !schema) {\n throw new Error('db and schema are required');\n }\n\n // Delegate to the database adapter's syncSchema implementation\n if (db.syncSchema) {\n await db.syncSchema(schema);\n } else {\n throw new Error('Database adapter does not support schema synchronization');\n }\n}\n\n/**\n * Checks if a table exists in the database\n *\n * @param db - Database interface to use\n * @param tableName - Name of the table to check\n * @returns Promise resolving to boolean indicating if the table exists\n */\nexport async function tableExists(db: DatabaseInterface, tableName: string) {\n return db.tableExists(tableName);\n}\n\n/**\n * Escapes and formats a value for use in SQL queries\n *\n * @param value - Value to escape\n * @returns String representation of the value safe for SQL use\n */\nexport function escapeSqlValue(value: any): string {\n if (value === null) {\n return 'NULL';\n }\n if (value instanceof Date) {\n return `'${value.toISOString()}'`;\n }\n if (typeof value === 'number') {\n return value.toString();\n }\n if (typeof value === 'boolean') {\n return value ? '1' : '0';\n }\n // Escape single quotes and wrap in quotes\n return `'${String(value).replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Validates a column name for use in SQL queries\n *\n * @param column - Column name to validate\n * @returns The validated column name\n * @throws Error if the column name contains invalid characters\n */\nexport function validateColumnName(column: string): string {\n // Reject non-strings before the regex. `test` coerces via `toString`, so an\n // object whose `toString` differs between this read and a later interpolation\n // read would pass validation and then reach SQL as a different identifier.\n // Only allow alphanumeric characters, underscores, and dots (for table.column notation)\n if (typeof column !== 'string' || !/^[a-zA-Z0-9_.]+$/.test(column)) {\n throw new Error(`Invalid column name: ${column}`);\n }\n return column;\n}\n\n// Import utilities from shared utils\nimport { buildWhere, formatDbError, raw } from './shared/utils';\n\nexport {\n type AggregateBuildResult,\n type AggregateFunction,\n type AggregateSelectExpr,\n type AggregateSpec,\n type AggregateTimeBucketUnit,\n bucketExpr,\n buildAggregate,\n} from './aggregate.js';\nexport type { RawSqlKey, SqlAdapterType } from './shared/utils';\nexport { buildWhere, formatDbError, raw };\n\n// Import DuckDB schema transformation utilities\nimport { convertUniqueIndexesToInlineConstraints } from './shared/duckdb-schema-utils';\n\n// Generic database integrity-check framework\nexport {\n checkExpectedTables,\n checkRelationship,\n checkUniqueColumn,\n type DoctorCheck,\n type DoctorContext,\n type DoctorIssue,\n type DoctorLevel,\n type DoctorResult,\n type RelationshipSpec,\n type RunDoctorOptions,\n runDoctor,\n type UniqueColumnSpec,\n} from './doctor';\n\n// Export adapter cache utilities (for testing)\nexport { clearConnectionCache } from './json.js';\nexport { clearPostgresConnectionCache } from './postgres.js';\n// Postgres CLI shell-outs and URL helpers\nexport {\n assertCanExportDatabase,\n assertCanImportDatabase,\n type CreateDropOptions,\n createPostgresDatabase,\n type DumpOptions,\n databaseNameFromUrl,\n dropPostgresDatabase,\n dumpPostgresDatabase,\n isLocalDatabaseUrl,\n type PostgresImportLocalityOptions,\n type PostgresLocalityOptions,\n postgresEnvFromUrl,\n type RestoreOptions,\n redactDatabaseUrl,\n restorePostgresDatabase,\n} from './postgres-cli';\nexport type { SchemaInitializationResult } from './schema-manager';\n// Export schema management\nexport { DatabaseSchemaManager } from './schema-manager';\nexport * from './shared/types';\nexport type { SecureSqliteFileOptions, SqliteOptions } from './sqlite.js';\nexport { convertUniqueIndexesToInlineConstraints };\n\nexport default {\n getDatabase,\n syncSchema,\n tableExists,\n buildWhere,\n raw,\n};\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/aggregate.ts","../src/doctor.ts","../src/shared/types.ts","../src/json.ts","../src/postgres.ts","../src/index.ts"],"sourcesContent":["import type { WhereClause } from './shared/types.js';\nimport {\n buildWhere,\n parseConditionKey,\n raw,\n type SqlAdapterType,\n unwrapRawKey,\n} from './shared/utils.js';\n\nexport type AggregateTimeBucketUnit =\n | 'minute'\n | 'hour'\n | 'day'\n | 'week'\n | 'month'\n | 'quarter'\n | 'year';\n\nexport type AggregateFunction =\n | 'count'\n | 'sum'\n | 'avg'\n | 'min'\n | 'max'\n | (string & {});\n\nexport type AggregateSelectExpr =\n | { column: string; as?: string }\n | {\n fn: AggregateFunction;\n column?: string;\n as: string;\n distinct?: boolean;\n }\n | {\n bucket: AggregateTimeBucketUnit;\n column: string;\n as: string;\n };\n\nexport interface AggregateSpec {\n from: string;\n select: AggregateSelectExpr[];\n where?: WhereClause;\n groupBy?: string[];\n having?: WhereClause;\n orderBy?: string | string[];\n limit?: number;\n offset?: number;\n}\n\nexport interface AggregateBuildResult {\n sql: string;\n values: any[];\n}\n\nconst VALID_DIRECTIONS = new Set(['ASC', 'DESC']);\nconst VALID_BUCKET_UNITS = new Set<AggregateTimeBucketUnit>([\n 'minute',\n 'hour',\n 'day',\n 'week',\n 'month',\n 'quarter',\n 'year',\n]);\n\nfunction validateSqlIdentifier(identifier: string): string {\n // Reject non-strings before the regex. `test` coerces via `toString`, so a\n // value whose `toString` differs across reads could pass here and interpolate\n // a different identifier into the aggregate SQL than the one validated.\n if (typeof identifier !== 'string' || !/^[a-zA-Z0-9_.]+$/.test(identifier)) {\n throw new Error(`Invalid SQL identifier: ${identifier}`);\n }\n return identifier;\n}\n\nfunction normalizeAdapter(adapterType?: SqlAdapterType): SqlAdapterType {\n return adapterType ?? 'sqlite';\n}\n\nfunction placeholder(index: number): string {\n return `$${index}`;\n}\n\nexport function bucketExpr(\n adapterType: SqlAdapterType | undefined,\n unit: AggregateTimeBucketUnit,\n column: string,\n): string {\n if (!VALID_BUCKET_UNITS.has(unit)) {\n throw new Error(`Invalid aggregate bucket unit: ${unit}`);\n }\n\n const col = validateSqlIdentifier(column);\n const adapter = normalizeAdapter(adapterType);\n\n if (adapter === 'postgres' || adapter === 'duckdb' || adapter === 'json') {\n return `date_trunc('${unit}', ${col})`;\n }\n\n switch (unit) {\n case 'minute':\n return `strftime('%Y-%m-%d %H:%M:00', ${col})`;\n case 'hour':\n return `strftime('%Y-%m-%d %H:00:00', ${col})`;\n case 'day':\n return `strftime('%Y-%m-%d 00:00:00', ${col})`;\n case 'week':\n return `strftime('%Y-%m-%d 00:00:00', date(${col}, printf('-%d days', (CAST(strftime('%w', ${col}) AS INTEGER) + 6) % 7)))`;\n case 'month':\n return `strftime('%Y-%m-01 00:00:00', ${col})`;\n case 'quarter':\n return `strftime('%Y-%m-%d 00:00:00', date(strftime('%Y-', ${col}) || printf('%02d', ((CAST(strftime('%m', ${col}) AS INTEGER) - 1) / 3) * 3 + 1) || '-01'))`;\n case 'year':\n return `strftime('%Y-01-01 00:00:00', ${col})`;\n }\n}\n\nfunction aggregateExpression(\n expr: Extract<AggregateSelectExpr, { fn: AggregateFunction }>,\n): string {\n const normalizedFn = String(expr.fn).toLowerCase();\n const fn = validateSqlIdentifier(String(expr.fn)).toUpperCase();\n if (normalizedFn === 'count' && !expr.column) {\n if (expr.distinct) {\n throw new Error(`Aggregate ${expr.fn} with distinct requires a column`);\n }\n return 'COUNT(*)';\n }\n if (!expr.column) {\n throw new Error(`Aggregate ${expr.fn} requires a column`);\n }\n const col = validateSqlIdentifier(expr.column);\n const distinct = expr.distinct ? 'DISTINCT ' : '';\n return `${fn}(${distinct}${col})`;\n}\n\nfunction selectExpression(\n expr: AggregateSelectExpr,\n adapterType: SqlAdapterType | undefined,\n): { expression: string; alias: string; groupingCandidate: boolean } {\n if ('column' in expr && !('fn' in expr) && !('bucket' in expr)) {\n const column = validateSqlIdentifier(expr.column);\n const alias = validateSqlIdentifier(expr.as ?? expr.column);\n return { expression: column, alias, groupingCandidate: true };\n }\n if ('bucket' in expr) {\n const expression = bucketExpr(adapterType, expr.bucket, expr.column);\n const alias = validateSqlIdentifier(expr.as);\n return { expression, alias, groupingCandidate: true };\n }\n\n const expression = aggregateExpression(expr);\n const alias = validateSqlIdentifier(expr.as);\n return { expression, alias, groupingCandidate: false };\n}\n\nfunction expandHavingKey(\n key: string,\n expressionByAlias: Map<string, string>,\n): string {\n // A key the caller already wrapped in raw() is used verbatim: raw() means\n // \"this exact SQL\", so it deliberately bypasses select-alias expansion.\n if (unwrapRawKey(key).isRaw) return key;\n\n const parsed = parseConditionKey(key);\n const expression =\n expressionByAlias.get(parsed.field) ?? validateSqlIdentifier(parsed.field);\n\n // The caller's key is validated above (as an alias or a plain identifier);\n // what comes out is a select expression such as `SUM(total) >`, which only\n // buildWhere's raw channel accepts.\n return raw(`${expression} ${parsed.operator}`);\n}\n\nfunction expandHaving(\n having: WhereClause,\n expressionByAlias: Map<string, string>,\n): WhereClause {\n const expandCondition = (condition: Record<string, any>) =>\n Object.fromEntries(\n Object.entries(condition).map(([key, value]) => [\n expandHavingKey(key, expressionByAlias),\n value,\n ]),\n );\n if (Array.isArray(having)) {\n return having.map((andGroup: Record<string, any>[]) =>\n andGroup.map((condition: Record<string, any>) =>\n expandCondition(condition),\n ),\n );\n }\n\n return expandCondition(having);\n}\n\nfunction buildHaving(\n having: AggregateSpec['having'],\n expressionByAlias: Map<string, string>,\n startIndex: number,\n adapterType: SqlAdapterType | undefined,\n): AggregateBuildResult {\n if (!having) return { sql: '', values: [] };\n const expanded = expandHaving(having, expressionByAlias);\n const built = buildWhere(expanded, startIndex, adapterType);\n\n return {\n sql: built.sql ? built.sql.replace(/^WHERE/, 'HAVING') : '',\n values: built.values,\n };\n}\n\nfunction buildOrderBy(orderBy: string | string[] | undefined): string {\n if (!orderBy) return '';\n const items = Array.isArray(orderBy) ? orderBy : [orderBy];\n const parts = items.map((item) => {\n const [field, direction = 'ASC'] = item.trim().split(/\\s+/);\n const normalizedDirection = direction.toUpperCase();\n if (!VALID_DIRECTIONS.has(normalizedDirection)) {\n throw new Error(`Invalid aggregate order direction: ${direction}`);\n }\n const orderExpr = validateSqlIdentifier(field);\n return `${orderExpr} ${normalizedDirection}`;\n });\n return `ORDER BY ${parts.join(', ')}`;\n}\n\nexport function buildAggregate(\n spec: AggregateSpec,\n startIndex = 1,\n adapterType?: SqlAdapterType,\n): AggregateBuildResult {\n const from = validateSqlIdentifier(spec.from);\n if (spec.select.length === 0) {\n throw new Error(\n 'AggregateSpec.select must contain at least one expression',\n );\n }\n\n const expressionByAlias = new Map<string, string>();\n const groupingAliases: string[] = [];\n const selectSql = spec.select.map((expr) => {\n const selected = selectExpression(expr, adapterType);\n expressionByAlias.set(selected.alias, selected.expression);\n if (selected.groupingCandidate) {\n groupingAliases.push(selected.alias);\n }\n return `${selected.expression} AS ${selected.alias}`;\n });\n\n const where = spec.where\n ? buildWhere(spec.where, startIndex, adapterType)\n : { sql: '', values: [] };\n let nextParamIndex = startIndex + where.values.length;\n\n const groupByItems = spec.groupBy ?? groupingAliases;\n const groupBySql =\n groupByItems.length > 0\n ? `GROUP BY ${groupByItems\n .map((item) =>\n expressionByAlias.has(item)\n ? expressionByAlias.get(item)\n : validateSqlIdentifier(item),\n )\n .join(', ')}`\n : '';\n\n const having = buildHaving(\n spec.having,\n expressionByAlias,\n nextParamIndex,\n adapterType,\n );\n nextParamIndex += having.values.length;\n\n const orderBySql = buildOrderBy(spec.orderBy);\n\n const pagingValues: any[] = [];\n const pagingSql: string[] = [];\n if (spec.limit !== undefined) {\n pagingSql.push(`LIMIT ${placeholder(nextParamIndex++)}`);\n pagingValues.push(spec.limit);\n } else if (\n spec.offset !== undefined &&\n normalizeAdapter(adapterType) === 'sqlite'\n ) {\n pagingSql.push('LIMIT -1');\n }\n if (spec.offset !== undefined) {\n pagingSql.push(`OFFSET ${placeholder(nextParamIndex++)}`);\n pagingValues.push(spec.offset);\n }\n\n const sql = [\n `SELECT ${selectSql.join(', ')}`,\n `FROM ${from}`,\n where.sql,\n groupBySql,\n having.sql,\n orderBySql,\n pagingSql.join(' '),\n ]\n .filter(Boolean)\n .join(' ');\n\n return {\n sql,\n values: [...where.values, ...having.values, ...pagingValues],\n };\n}\n","/**\n * Database integrity-check framework.\n *\n * The runner is a thin loop over user-supplied checks; the value here is\n * the reusable check builders (expected tables, unique columns, FK-like\n * relationships) that codify the common shapes of integrity bugs that\n * sneak past schema migrations.\n *\n * No SMRT awareness — checks operate on the `DatabaseInterface` from this\n * package and on plain table/column names.\n *\n * **Postgres-only today.** The bundled check builders and\n * `listPublicTables` use Postgres syntax (`pg_tables`, `::int`/`::text`\n * casts). The shape is portable in principle — `DoctorContext` carries a\n * generic `DatabaseInterface` so apps can add engine-aware checks — but\n * the out-of-the-box runner expects a Postgres connection. `runDoctor`\n * throws early if pointed at a non-Postgres URL rather than failing\n * mid-execution with a confusing `pg_tables`-doesn't-exist error.\n */\nimport { redactDatabaseUrl } from './postgres-cli';\nimport type { DatabaseInterface } from './shared/types';\n\nexport type DoctorLevel = 'fail' | 'warn';\n\nexport interface DoctorIssue {\n level: DoctorLevel;\n message: string;\n}\n\nexport interface DoctorContext {\n db: DatabaseInterface;\n tables: Set<string>;\n}\n\nexport type DoctorCheck = (ctx: DoctorContext) => Promise<DoctorIssue[]>;\n\nexport interface RunDoctorOptions {\n db: DatabaseInterface;\n checks: DoctorCheck[];\n}\n\nexport interface DoctorResult {\n failures: DoctorIssue[];\n warnings: DoctorIssue[];\n issues: DoctorIssue[];\n}\n\n/**\n * Execute every check against the public-schema tables of `db` and\n * partition the results by severity. Returns the aggregate; callers\n * format/exit as they see fit.\n *\n * The current table set is enumerated once and shared across checks so\n * each check can cheaply decide whether its target tables exist.\n */\nexport async function runDoctor(\n options: RunDoctorOptions,\n): Promise<DoctorResult> {\n assertPostgresAdapter(options.db);\n const tables = await listPublicTables(options.db);\n const issues: DoctorIssue[] = [];\n\n for (const check of options.checks) {\n const checkIssues = await check({ db: options.db, tables });\n issues.push(...checkIssues);\n }\n\n return {\n failures: issues.filter((issue) => issue.level === 'fail'),\n warnings: issues.filter((issue) => issue.level === 'warn'),\n issues,\n };\n}\n\n/**\n * Throw early if the supplied database isn't Postgres. The check builders\n * and `listPublicTables` use Postgres syntax; pointing them at SQLite or\n * DuckDB would fail mid-execution with a confusing \"table pg_tables does\n * not exist\" error from the adapter. Fail fast with a useful message\n * instead.\n */\nfunction assertPostgresAdapter(db: DatabaseInterface): void {\n const url = String(db.url ?? '');\n if (!url.startsWith('postgres://') && !url.startsWith('postgresql://')) {\n throw new Error(\n `runDoctor requires a Postgres database connection. Got URL: ${url ? redactDatabaseUrl(url) : '(empty)'}. ` +\n 'The bundled check builders use Postgres-specific syntax (pg_tables, ::int/::text casts).',\n );\n }\n}\n\n/**\n * Emit a 'warn' for each table in `expected` that's missing.\n *\n * Marked as `warn` rather than `fail` because a missing table often\n * indicates a migration that hasn't been run yet, not active data\n * corruption — callers can promote to fail by mapping the issues.\n */\nexport function checkExpectedTables(expected: readonly string[]): DoctorCheck {\n return async ({ tables }) => {\n const issues: DoctorIssue[] = [];\n for (const table of expected) {\n if (!tables.has(table)) {\n issues.push({\n level: 'warn',\n message: `Missing expected table: ${table}`,\n });\n }\n }\n return issues;\n };\n}\n\nexport interface UniqueColumnSpec {\n table: string;\n column: string;\n /** Human-readable label used in the issue message. */\n label: string;\n}\n\n/**\n * Emit a 'fail' for each value that appears more than once in\n * `(table, column)` (ignoring NULL and empty-string values).\n *\n * Useful for catching slug/key collisions that the schema didn't\n * declare a UNIQUE constraint for (or where the constraint was added\n * after data already existed).\n */\nexport function checkUniqueColumn(spec: UniqueColumnSpec): DoctorCheck {\n return async ({ db, tables }) => {\n if (!tables.has(spec.table)) return [];\n const result = await db.query(`\n SELECT ${quoteIdentifier(spec.column)} AS value, count(*)::text AS count\n FROM ${quoteIdentifier(spec.table)}\n WHERE NULLIF(trim(${quoteIdentifier(spec.column)}::text), '') IS NOT NULL\n GROUP BY ${quoteIdentifier(spec.column)}\n HAVING count(*) > 1\n ORDER BY count DESC, ${quoteIdentifier(spec.column)}\n `);\n return result.rows.map((row) => ({\n level: 'fail' as const,\n message: `Duplicate ${spec.label} \"${String(row.value)}\" in ${spec.table} (${countValueFromRow(\n row.count,\n )} rows).`,\n }));\n };\n}\n\nexport interface RelationshipSpec {\n childTable: string;\n childColumn: string;\n parentTable: string;\n /** Defaults to 'id'. */\n parentColumn?: string;\n /** Human-readable label used in the issue message. */\n label: string;\n}\n\n/**\n * Emit a 'fail' counting orphan rows where the child column points at a\n * parent row that doesn't exist (FK-shaped check, but doesn't require an\n * actual FK constraint to be declared — useful for soft references like\n * tag-id-or-slug lookup columns).\n */\nexport function checkRelationship(spec: RelationshipSpec): DoctorCheck {\n return async ({ db, tables }) => {\n if (!tables.has(spec.childTable) || !tables.has(spec.parentTable))\n return [];\n const parentColumn = spec.parentColumn ?? 'id';\n const result = await db.query(`\n SELECT count(*)::text AS count\n FROM ${quoteIdentifier(spec.childTable)} child\n LEFT JOIN ${quoteIdentifier(spec.parentTable)} parent\n ON parent.${quoteIdentifier(parentColumn)} = child.${quoteIdentifier(spec.childColumn)}\n WHERE NULLIF(trim(child.${quoteIdentifier(spec.childColumn)}::text), '') IS NOT NULL\n AND parent.${quoteIdentifier(parentColumn)} IS NULL\n `);\n const orphanCount = countValueFromRow(result.rows[0]?.count);\n if (orphanCount === '0') return [];\n return [\n {\n level: 'fail',\n message: `${spec.label}: ${orphanCount} orphaned row${orphanCount === '1' ? '' : 's'}.`,\n },\n ];\n };\n}\n\nasync function listPublicTables(db: DatabaseInterface): Promise<Set<string>> {\n const result = await db.query(\n \"SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename\",\n );\n return new Set(result.rows.map((row) => String(row.tablename ?? '')));\n}\n\nfunction quoteIdentifier(value: string): string {\n return `\"${value.replace(/\"/gu, '\"\"')}\"`;\n}\n\nfunction countValueFromRow(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'bigint') {\n return String(value);\n }\n return '0';\n}\n","import { DatabaseError } from '@happyvertical/utils';\n\n/**\n * Error thrown when `transaction()` is called on a transaction-scoped interface\n * belonging to an adapter whose engine has no savepoints.\n *\n * DuckDB — and therefore the JSON adapter built on it — does not implement\n * `SAVEPOINT`, so there is no way to re-enter the current transaction. Adapters\n * that can re-enter (PostgreSQL, SQLite) do so under a savepoint and never\n * throw this.\n *\n * The alternative would be to open a second, independent transaction, which is\n * what these adapters used to do; on a shared connection that silently\n * destroyed the enclosing transaction's uncommitted work.\n *\n * @example Ensuring a helper works on every adapter\n * ```typescript\n * // Take the transaction as a parameter rather than opening a nested one.\n * async function recordClick(db: DatabaseInterface, id: string) {\n * await db.update('links', { id }, { clicks: 1 });\n * }\n * await db.transaction(async (tx) => recordClick(tx, id));\n * ```\n */\nexport class NestedTransactionError extends DatabaseError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, context);\n this.name = 'NestedTransactionError';\n }\n}\n\n/**\n * WHERE clause input for database queries\n *\n * Supports two formats:\n * 1. Object format (AND-only): `{ status: 'active', 'price >': 100 }`\n * - All conditions are AND-joined\n *\n * 2. 2D array format (OR/AND compound logic): `[[cond1, cond2], [cond3, cond4]]`\n * - Inner arrays are AND-joined: `(cond1 AND cond2)`\n * - Outer array is OR-joined: `(cond1 AND cond2) OR (cond3 AND cond4)`\n *\n * @example Object format (backward compatible)\n * ```typescript\n * // WHERE status = 'active' AND name contains the exact text '100% cotton'\n * const where = {\n * status: 'active',\n * 'name contains': '100% cotton',\n * };\n * ```\n *\n * @example 2D array format for OR/AND compound logic\n * ```typescript\n * // WHERE (status = 'active' AND price > 100) OR (status = 'pending' AND priority = 'high')\n * const where = [\n * [{ status: 'active' }, { 'price >': 100 }],\n * [{ status: 'pending' }, { priority: 'high' }]\n * ];\n * ```\n */\nexport type WhereClause = Record<string, any> | Record<string, any>[][];\n\n/**\n * Common database connection options\n */\nexport interface DatabaseCacheOptions {\n /**\n * Stable, caller-supplied cache identity. When omitted, adapters derive an\n * identity from their effective connection configuration.\n */\n dbid?: string;\n\n /**\n * Whether this request may reuse and retain a shared adapter.\n * Set to `false` for a distinct connection that is never inserted into the\n * shared cache. Existing cached connections are left untouched.\n *\n * @default true for adapters that support caching\n */\n cache?: boolean;\n\n /**\n * Evict and close the matching cached adapter before serving this request.\n * Eviction waits for both resource closure and any in-flight initializer.\n * The replacement is cached unless `cache` is also `false`.\n *\n * @default false\n */\n clearCache?: boolean;\n}\n\nexport interface DatabaseOptions extends DatabaseCacheOptions {\n /**\n * Database connection URL\n */\n url?: string;\n\n /**\n * Authentication token for the database connection\n */\n authToken?: string;\n\n /**\n * How long a queued transaction waits for the connection, in milliseconds.\n *\n * Applies to the single-connection adapters — SQLite (both the LibSQL and\n * native paths), DuckDB, JSON and PGlite. Those drive one connection, so transactions\n * run one at a time and an overlapping `transaction()` waits its turn.\n * PostgreSQL pools and ignores this.\n *\n * The clock starts when the call queues, not when the connection frees, so\n * this bounds the total wait rather than any single transaction: raise it for\n * workloads with long transactions *or* with sustained bursts on one\n * connection. Must be positive and finite.\n *\n * Read once, when the connection is created. Adapters that cache connections\n * hand a later `getDatabase()` for the same database the existing one, which\n * keeps the timeout the first caller asked for.\n *\n * @default 30000\n */\n transactionQueueTimeout?: number;\n}\n\n/**\n * DuckDB-specific connection options\n */\nexport interface DuckDBOptions extends DatabaseOptions {\n /**\n * Database type identifier\n */\n type?: 'duckdb';\n\n /**\n * Path to directory containing JSON files to auto-register as tables\n * @default './data'\n */\n dataDir?: string;\n\n /**\n * Automatically register JSON files in dataDir as queryable tables\n * @default true\n */\n autoRegisterJSON?: boolean;\n\n /**\n * Strategy for writing data back to JSON files\n * - 'immediate': Write changes to JSON files immediately\n * - 'manual': Require explicit export calls\n * - 'none': Read-only mode, no writes to JSON files\n * @default 'none'\n */\n writeStrategy?: 'immediate' | 'manual' | 'none';\n\n /**\n * Whether to use a persistent DuckDB file for caching/indexes\n * If true, uses url or defaults to ':memory:'\n * @default false\n */\n persistent?: boolean;\n\n /**\n * Accepted for uniform configuration. DuckDB creates a fresh adapter on\n * every call, so this option does not change its behavior.\n */\n cache?: boolean;\n\n /**\n * Accepted for uniform configuration. DuckDB has no shared cache to evict,\n * so this option does not change its behavior.\n */\n clearCache?: boolean;\n\n /**\n * Explicit schema definitions for tables\n * When provided, these schemas will be used for table creation\n */\n schemas?: SchemasOption;\n}\n\n/**\n * A JSON data file that parsed but whose records could not be loaded into the\n * table — e.g. a renamed or dropped column, a NOT NULL / PRIMARY KEY violation,\n * or a file whose fields match no column on the table.\n *\n * The JSON adapter keeps the table (empty) and continues loading every other\n * table, logging the failure to stderr. This record makes the same failure\n * observable to calling code so a table that silently stays empty can be\n * detected. See issue #1139.\n */\nexport interface JSONTableLoadError {\n /** Name of the table whose data file failed to load */\n table: string;\n /** Path to the JSON data file that could not be loaded */\n filePath: string;\n /**\n * The underlying failure. Typically a `DatabaseError` whose `context`\n * carries the engine-level cause (e.g. the DuckDB constraint message).\n */\n error: unknown;\n}\n\n/**\n * JSON database adapter options (DuckDB-backed)\n *\n * Uses DuckDB's in-memory engine to query JSON files directly.\n * No WAL files or persistent database files are created.\n */\nexport interface JSONOptions extends DatabaseOptions {\n /**\n * Database type identifier\n */\n type: 'json';\n\n /**\n * Path to directory containing JSON files (required)\n * JSON files in this directory will be loaded as queryable tables\n */\n url: string;\n\n /**\n * Automatically load all JSON files in dataDir as tables\n * @default true\n */\n autoRegister?: boolean;\n\n /**\n * Strategy for writing changes back to JSON files\n * - 'immediate': Auto-save after every insert/update (default)\n * - 'manual': Require explicit exportTable() calls\n * - 'none': Read-only mode, throws error on writes\n * @default 'immediate'\n */\n writeStrategy?: 'immediate' | 'manual' | 'none';\n\n /**\n * How long a queued transaction waits for the connection, in milliseconds.\n *\n * Applies to the single-connection adapters — SQLite (both the LibSQL and\n * native paths), DuckDB, JSON and PGlite. Those drive one connection, so transactions\n * run one at a time and an overlapping `transaction()` waits its turn.\n * PostgreSQL pools and ignores this.\n *\n * The clock starts when the call queues, not when the connection frees, so\n * this bounds the total wait rather than any single transaction: raise it for\n * workloads with long transactions *or* with sustained bursts on one\n * connection. Must be positive and finite.\n *\n * Read once, when the connection is created. Adapters that cache connections\n * hand a later `getDatabase()` for the same database the existing one, which\n * keeps the timeout the first caller asked for.\n *\n * @default 30000\n */\n transactionQueueTimeout?: number;\n\n /**\n * Explicit schema definitions for tables\n *\n * When provided, these schemas will be used instead of auto-detection.\n * This allows frameworks to provide explicit schemas via dependency injection.\n *\n * @example\n * ```typescript\n * const db = await getDatabase({\n * type: 'json',\n * url: './data',\n * schemas: {\n * users: {\n * tableName: 'users',\n * ddl: 'CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)',\n * indexes: ['CREATE INDEX idx_users_name ON users(name)']\n * }\n * }\n * });\n * ```\n */\n schemas?: SchemasOption;\n\n /**\n * Unique identifier for in-memory DuckDB instances to enable connection sharing\n * When multiple getDatabase() calls use the same dbid, they receive\n * the same database connection instance.\n *\n * Auto-generated if not provided, enabling connection sharing by default.\n * This solves the issue where multiple collections create separate in-memory\n * databases that can't see each other's tables.\n */\n dbid?: string;\n\n /**\n * Eagerly load all JSON files as tables at connection time\n *\n * When true (default), creates tables with inferred schemas for all JSON files\n * in the data directory, even those without explicit schemas or .schema.sql files.\n * This enables cross-table queries (JOINs, NOT EXISTS subqueries) before\n * syncSchema() is called.\n *\n * Note: Inferred schemas don't include UNIQUE constraints. UPSERT operations\n * require syncSchema() to be called first to establish proper constraints.\n * When syncSchema() is called, tables with inferred schemas are dropped and\n * recreated with the proper schema.\n *\n * @default true\n */\n eagerLoadTables?: boolean;\n\n /**\n * Evict and close the existing database connection before creating a fresh one\n *\n * The new adapter is cached unless `cache` is also false. Eviction is awaited.\n * When automatic file registration is enabled, JSON adapters detect changed,\n * added, or removed `.json` and companion `.schema.sql` files on a later\n * acquisition. Use this option when a caller needs an unconditional reload\n * even if those sources are unchanged. Existing references point to the\n * closed adapter after replacement.\n *\n * @default false\n *\n * @example\n * // Initial connection - cached\n * const db1 = await getDatabase({ type: 'json', url: './data' });\n *\n * // Force an unconditional replacement...\n *\n * // Force reload from disk\n * const db2 = await getDatabase({\n * type: 'json',\n * url: './data',\n * clearCache: true\n * });\n */\n clearCache?: boolean;\n\n /**\n * Called when a JSON data file parses but its records cannot be loaded into a\n * table that was created for it — from a provided schema, a `.schema.sql`\n * file, or deferred `syncSchema()` / `execute()` DDL — leaving that table\n * present but empty (renamed/dropped column, NOT NULL / PRIMARY KEY violation,\n * or a file whose fields match no column). Every other table still loads.\n *\n * This targets the *silent* failure: a present-but-empty table a query cannot\n * tell apart from a legitimately empty one. A file with no schema at all is\n * loaded by DuckDB's own inference; if that fails the table is simply not\n * created, which already surfaces as an error when you query it, so it is not\n * reported here.\n *\n * Fires for both connection-time and deferred (`syncSchema()` / `execute()`)\n * loads. Without it, such a failure is only visible as a `console.error` on\n * stderr. The same failures are also retrievable after the fact via the\n * adapter's `getTableLoadErrors()` method.\n *\n * Registered once, on the `getDatabase()` call that creates the connection.\n * The JSON adapter caches connections per URL, so a later `getDatabase()` for\n * the same directory returns the existing connection and does *not* re-wire a\n * different `onTableLoadError` — read `getTableLoadErrors()` on the returned\n * adapter instead, which reflects the shared connection's failures.\n *\n * A callback that throws is caught and ignored so it cannot break loading of\n * the remaining tables. See issue #1139.\n */\n onTableLoadError?: (info: JSONTableLoadError) => void;\n}\n\n/**\n * Result of a database operation that modifies data\n */\nexport interface QueryResult {\n /**\n * Type of operation performed (e.g., \"insert\", \"update\", \"delete\")\n */\n operation: string;\n\n /**\n * Number of rows affected by the operation\n */\n affected: number;\n}\n\n/**\n * Options for upsert operations.\n */\nexport interface UpsertOptions {\n /**\n * Preserve database-native NULL-distinct conflict behavior.\n *\n * By default, upsert treats NULL values in conflict columns as matching\n * existing NULL values. Set this to true to opt out and use the underlying\n * database's native ON CONFLICT behavior where NULLs are distinct.\n */\n nullsDistinct?: boolean;\n}\n\n/**\n * Schema definition that can be provided to database adapters\n *\n * Allows frameworks like SMRT to provide explicit schemas instead of\n * relying on auto-detection or circular dependencies.\n *\n * This is a simpler, DDL-based alternative to SchemaDefinition which is\n * used for structured JSON manifests.\n */\nexport interface SchemaProvider {\n /**\n * Table name in snake_case plural form\n */\n tableName: string;\n\n /**\n * DDL statement(s) to create the table\n * Can be a single CREATE TABLE statement or multiple statements\n */\n ddl: string;\n\n /**\n * Index creation statements (optional)\n * Each string should be a complete CREATE INDEX statement\n */\n indexes?: string[];\n\n /**\n * Trigger creation statements (optional, adapter-specific support)\n * Each string should be a complete CREATE TRIGGER statement\n */\n triggers?: string[];\n\n /**\n * Schema version for migration tracking (optional)\n */\n version?: string;\n\n /**\n * Field metadata (optional, for advanced use cases)\n * Can be used by frameworks for runtime type information and validation\n */\n fields?: Map<string, any>;\n}\n\n/**\n * Schema definitions for getDatabase(), either eagerly built or lazy.\n *\n * Callers can pass schemas as a lazy function so that building the schema\n * map (potentially 200+ objects from ObjectRegistry) is deferred until an\n * adapter actually needs it. Adapters that manage tables via migrations\n * (Postgres, SQLite) never call the function, making the cost zero.\n *\n * @example Lazy — only resolved by JSON/DuckDB adapters\n * ```typescript\n * await getDatabase({\n * type: 'postgres',\n * url: '...',\n * schemas: () => ObjectRegistry.getAllSchemas(), // never called\n * });\n * ```\n *\n * @example Eager — built upfront (fine for JSON/DuckDB)\n * ```typescript\n * await getDatabase({\n * type: 'json',\n * url: './data',\n * schemas: { users: { tableName: 'users', ddl: '...' } },\n * });\n * ```\n */\nexport type SchemasOption =\n | Record<string, SchemaProvider>\n | (() => Record<string, SchemaProvider>);\n\n/**\n * Resolves a SchemasOption to its concrete value.\n *\n * Adapters that need schemas (JSON, DuckDB) call this to unwrap the\n * lazy function. Adapters that don't (Postgres, SQLite) simply ignore\n * the `schemas` option without calling this.\n */\nexport function resolveSchemas(\n schemas: SchemasOption | undefined,\n): Record<string, SchemaProvider> | undefined {\n if (!schemas) return undefined;\n if (typeof schemas === 'function') return schemas();\n return schemas;\n}\n\n/**\n * Schema manifest structure for JSON-based schema management\n */\nexport interface SchemaManifest {\n version: string;\n timestamp: number;\n packageName: string;\n schemas: Record<string, SchemaDefinition>;\n dependencies: string[];\n}\n\n/**\n * Schema definition for individual tables\n */\nexport interface SchemaDefinition {\n tableName: string;\n columns: Record<string, ColumnDefinition>;\n indexes: IndexDefinition[];\n triggers: TriggerDefinition[];\n foreignKeys: ForeignKeyDefinition[];\n dependencies: string[];\n version: string;\n packageName: string;\n baseClass?: string;\n}\n\n/**\n * Column definition structure\n */\nexport interface ColumnDefinition {\n type: string;\n primaryKey?: boolean;\n unique?: boolean;\n notNull?: boolean;\n defaultValue?: any;\n check?: string;\n foreignKey?: {\n table: string;\n column: string;\n onDelete?: string;\n onUpdate?: string;\n };\n description?: string;\n}\n\n/**\n * Column definition with name for ALTER TABLE operations\n */\nexport interface ColumnDefinitionWithName extends ColumnDefinition {\n name: string;\n}\n\n/**\n * Index definition structure\n */\nexport interface IndexDefinition {\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n description?: string;\n}\n\n/**\n * Trigger definition structure\n */\nexport interface TriggerDefinition {\n name: string;\n when: string;\n event: string;\n table: string;\n condition?: string;\n body: string;\n description?: string;\n}\n\n/**\n * Foreign key constraint definition\n */\nexport interface ForeignKeyDefinition {\n column: string;\n referencesTable: string;\n referencesColumn: string;\n onDelete?: string;\n onUpdate?: string;\n}\n\n/**\n * Options for schema initialization and management\n */\nexport interface SchemaInitializationOptions {\n /** Schema manifest containing table definitions */\n manifest?: SchemaManifest;\n /** Raw SQL DDL schema string (legacy support) */\n schema?: string;\n /** Schema overrides for extending base schemas */\n overrides?: Record<string, SchemaDefinition>;\n /** Force recreation of tables */\n force?: boolean;\n /** Enable debug logging */\n debug?: boolean;\n}\n\n/**\n * Table schema information returned by getTableSchema()\n */\nexport interface TableSchemaInfo {\n /** Table name */\n tableName: string;\n /** Columns in the table */\n columns: Record<string, ColumnDefinition>;\n /** Indexes on the table */\n indexes: IndexDefinition[];\n /** Foreign key constraints */\n foreignKeys: ForeignKeyDefinition[];\n}\n\n/**\n * Options for vector similarity search\n */\nexport interface VectorSearchOptions {\n /** Maximum number of results to return */\n limit?: number;\n /** Distance metric for similarity comparison @default 'cosine' */\n metric?: 'cosine' | 'l2' | 'ip';\n /**\n * Additional SQL WHERE clause.\n * Use $2, $3, etc. for parameter placeholders ($1 is reserved for the query vector).\n * @example `category = $2`\n */\n where?: string;\n /** Parameters for the WHERE clause, bound to $2, $3, etc. */\n params?: any[];\n}\n\n/**\n * Result of a vector similarity search\n */\nexport interface VectorSearchResult {\n /** Row primary key */\n id: string;\n /**\n * Distance from query vector (lower = more similar).\n * Range varies by metric: cosine [0, 2], L2 [0, Infinity), ip (-Infinity, Infinity).\n */\n distance: number;\n /** Additional columns from the row */\n [key: string]: any;\n}\n\n/**\n * Options for creating a vector index\n */\nexport interface VectorIndexOptions {\n /** Number of dimensions (informational only, dimensions are set via ensureColumn) */\n dimensions?: number;\n /** Distance metric @default 'cosine' */\n metric?: 'cosine' | 'l2' | 'ip';\n /** Index type @default 'hnsw' */\n type?: 'hnsw' | 'ivfflat';\n}\n\n/**\n * Vector operations capability for database adapters\n *\n * Provides native vector similarity search, storage, and indexing.\n * Implemented by PostgreSQL via pgvector and optionally by SQLite via\n * sqlite-vector.\n */\nexport interface VectorCapabilities {\n /**\n * Search for similar vectors\n *\n * @param table - Table containing vector column\n * @param column - Name of the vector column\n * @param embedding - Query vector\n * @param options - Search options\n * @returns Ranked results by similarity\n */\n search(\n table: string,\n column: string,\n embedding: number[],\n options?: VectorSearchOptions,\n ): Promise<VectorSearchResult[]>;\n\n /**\n * Ensure a vector column exists on a table\n *\n * @param table - Table name\n * @param column - Column name\n * @param dimensions - Number of vector dimensions\n */\n ensureColumn(\n table: string,\n column: string,\n dimensions: number,\n ): Promise<void>;\n\n /**\n * Ensure a vector index exists on a column\n *\n * @param table - Table name\n * @param column - Column name\n * @param options - Index options\n */\n ensureIndex(\n table: string,\n column: string,\n options?: VectorIndexOptions,\n ): Promise<void>;\n\n /**\n * Store or update a vector in a specific row\n *\n * @param table - Table name\n * @param where - Conditions to identify the row\n * @param column - Vector column name\n * @param embedding - Vector data\n */\n upsertVector(\n table: string,\n where: Record<string, any>,\n column: string,\n embedding: number[],\n ): Promise<void>;\n}\n\n/**\n * SQLite notification delivered by optional notification capabilities.\n */\nexport interface DatabaseNotification {\n id: number;\n channel: string;\n payload: any;\n createdAt?: number | string | Date | null;\n}\n\n/**\n * Options for notification listeners.\n */\nexport interface NotificationListenOptions {\n /**\n * Fallback poll interval in milliseconds when the native watcher does not\n * receive an update signal.\n */\n fallbackPollMs?: number | null;\n}\n\n/**\n * Options for waiting on the next database update.\n */\nexport interface NotificationWaitOptions {\n /**\n * Maximum time to wait in milliseconds. When omitted, waits indefinitely.\n */\n timeoutMs?: number;\n\n /**\n * Optional abort signal to cancel the wait.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Options for pruning persisted notification rows.\n */\nexport interface NotificationPruneOptions {\n /** Delete notifications older than this many seconds. */\n olderThanS?: number | null;\n /** Keep at most this many most-recent notifications. */\n maxKeep?: number | null;\n}\n\n/**\n * Optional notification/pub-sub capabilities for database adapters.\n */\nexport interface NotificationCapabilities {\n notify(channel: string, payload: any): Promise<number>;\n listen(\n channel: string,\n options?: NotificationListenOptions,\n ): AsyncIterable<DatabaseNotification>;\n waitForUpdate(options?: NotificationWaitOptions): Promise<boolean>;\n prune(options?: NotificationPruneOptions): Promise<number>;\n}\n\n/**\n * Options for SQLite native notification support.\n */\nexport interface SqliteNotificationCapabilityOptions {\n watcherBackend?: 'polling' | 'kernel' | 'shm';\n maxReaders?: number;\n}\n\n/**\n * Options for SQLite native vector support.\n */\nexport interface SqliteVectorCapabilityOptions {\n preload?: boolean;\n quantization?: 'turbo4' | 'turbo3' | 'turbo2' | 'uint8' | 'int8' | '1bit';\n maxMemory?: string;\n}\n\n/**\n * Optional SQLite-only native capabilities.\n */\nexport interface SqliteCapabilitiesOptions {\n notifications?: boolean | SqliteNotificationCapabilityOptions;\n vector?: boolean | SqliteVectorCapabilityOptions;\n}\n\n/**\n * Common interface for database adapters\n * Provides a unified API for different database backends\n */\nexport interface DatabaseInterface {\n /**\n * Database location identifier\n * For file-based databases: file path\n * For directory-based databases (JSON/DuckDB with JSON): directory path\n * For in-memory databases: ':memory:'\n * For remote databases: connection URL\n */\n url: string;\n\n /**\n * Underlying database client instance\n */\n client: any;\n\n /**\n * Inserts one or more records into a table\n *\n * @param table - Table name\n * @param data - Single record or array of records to insert\n * @returns Promise resolving to operation result\n */\n insert: (\n table: string,\n data: Record<string, any> | Record<string, any>[],\n ) => Promise<QueryResult>;\n\n /**\n * Retrieves a single record matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to matching record or null if not found\n */\n get: (\n table: string,\n where: WhereClause,\n ) => Promise<Record<string, any> | null>;\n\n /**\n * Retrieves multiple records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to array of matching records\n */\n list: (table: string, where: WhereClause) => Promise<Record<string, any>[]>;\n\n /**\n * Updates records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records to update (object for AND-only, 2D array for OR/AND)\n * @param data - New data to set\n * @returns Promise resolving to operation result\n */\n update: (\n table: string,\n where: WhereClause,\n data: Record<string, any>,\n ) => Promise<QueryResult>;\n\n /**\n * Inserts a record or updates it if it already exists (UPSERT)\n * Uses database-specific ON CONFLICT / ON DUPLICATE KEY syntax\n *\n * @param table - Table name\n * @param conflictColumns - Columns that define the uniqueness constraint\n * @param data - Data to insert or update\n * @returns Promise resolving to operation result\n *\n * @example\n * ```typescript\n * // Upsert a user by email\n * await db.upsert('users', ['email'], {\n * email: 'user@example.com',\n * name: 'John Doe',\n * updated_at: new Date().toISOString()\n * });\n *\n * // Upsert with composite key\n * await db.upsert('settings', ['user_id', 'key'], {\n * user_id: '123',\n * key: 'theme',\n * value: 'dark'\n * });\n * ```\n */\n upsert: (\n table: string,\n conflictColumns: string[],\n data: Record<string, any>,\n options?: UpsertOptions,\n ) => Promise<QueryResult>;\n\n /**\n * Gets a record matching the where criteria or inserts it if not found\n *\n * @param table - Table name\n * @param where - Criteria to match existing record (object for AND-only, 2D array for OR/AND)\n * @param data - Data to insert if no record found\n * @returns Promise resolving to the record (either retrieved or newly inserted)\n */\n getOrInsert: (\n table: string,\n where: WhereClause,\n data: Record<string, any>,\n ) => Promise<Record<string, any>>;\n\n /**\n * Deletes records from a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records for deletion (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to operation result with count of deleted rows\n *\n * @example\n * ```typescript\n * // Delete by ID\n * await db.delete('products', { id: 'prod-123' });\n *\n * // Delete by criteria\n * await db.delete('orders', { status: 'cancelled' });\n *\n * // Delete with multiple conditions\n * await db.delete('sessions', {\n * user_id: 'user-123',\n * expired: true\n * });\n * ```\n */\n delete: (table: string, where: WhereClause) => Promise<QueryResult>;\n\n /**\n * Counts records in a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (optional, counts all if omitted; object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to count of matching records\n *\n * @example\n * ```typescript\n * // Count all records\n * const total = await db.count('products');\n *\n * // Count with single condition\n * const activeCount = await db.count('products', { active: true });\n *\n * // Count with multiple conditions\n * const filteredCount = await db.count('orders', {\n * status: 'pending',\n * priority: 'high'\n * });\n * ```\n */\n count: (table: string, where?: WhereClause) => Promise<number>;\n\n /**\n * Creates a table-specific interface for simplified table operations\n *\n * @param table - Table name\n * @returns TableInterface for the specified table\n */\n table: (table: string) => TableInterface;\n\n /**\n * Checks if a table exists in the database\n *\n * @param table - Table name\n * @returns Promise resolving to boolean indicating existence\n */\n tableExists: (table: string) => Promise<boolean>;\n\n /**\n * Whether this adapter requires schema existence checks at runtime.\n *\n * Set to `true` on adapters where tables may not exist yet and need\n * to be verified before use (e.g. JSON/DuckDB with auto-created tables).\n *\n * When absent or `false`, frameworks skip `tableExists()` calls during\n * collection initialization — tables are assumed to exist because\n * schema is managed by migrations (e.g. `smrt db:migrate`).\n *\n * - Postgres adapter: not set (migration-managed)\n * - SQLite adapter: not set (migration-managed)\n * - JSON/DuckDB adapter: `true` (tables may be auto-created)\n */\n requiresSchemaCheck?: boolean;\n\n /**\n * Executes a SQL query using template literals and returns multiple rows\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to array of result records\n */\n many: (\n strings: TemplateStringsArray,\n ...vars: any[]\n ) => Promise<Record<string, any>[]>;\n\n /**\n * Executes a SQL query using template literals and returns a single row\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single result record or null\n */\n single: (\n strings: TemplateStringsArray,\n ...vars: any[]\n ) => Promise<Record<string, any> | null>;\n\n /**\n * Executes a SQL query using template literals and returns a single value\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single value (first column of first row)\n */\n pluck: (strings: TemplateStringsArray, ...vars: any[]) => Promise<any>;\n\n /**\n * Executes a SQL query using template literals without returning results\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise that resolves when the query completes\n */\n execute: (strings: TemplateStringsArray, ...vars: any[]) => Promise<void>;\n\n /**\n * Alias for many() - Executes a SQL query and returns multiple rows\n */\n oo: (\n strings: TemplateStringsArray,\n ...vars: any[]\n ) => Promise<Record<string, any>[]>;\n\n /**\n * Alias for single() - Executes a SQL query and returns a single row\n */\n oO: (\n strings: TemplateStringsArray,\n ...vars: any[]\n ) => Promise<Record<string, any> | null>;\n\n /**\n * Alias for pluck() - Executes a SQL query and returns a single value\n */\n ox: (strings: TemplateStringsArray, ...vars: any[]) => Promise<any>;\n\n /**\n * Alias for execute() - Executes a SQL query without returning results\n */\n xx: (strings: TemplateStringsArray, ...vars: any[]) => Promise<void>;\n\n /**\n * Executes a raw SQL query with parameterized values\n *\n * Raw queries use the adapter's native placeholder syntax.\n * Parameters may be passed as rest arguments or as one values array.\n * For adapter-specific array parameter caveats, see the package docs.\n *\n * @param str - SQL query string\n * @param vars - Variables to use as parameters\n * @returns Promise resolving to query result with rows and count\n */\n query: (\n str: string,\n ...vars: any[]\n ) => Promise<{ rows: Record<string, any>[]; rowCount: number }>;\n\n /**\n * Synchronizes database schema with provided SQL DDL\n * Creates tables if they don't exist and adds missing columns\n *\n * @param schema - SQL schema definition with CREATE TABLE statements\n * @returns Promise that resolves when schema is synchronized\n */\n syncSchema?: (schema: string) => Promise<void>;\n\n /**\n * Initialize database schemas from JSON manifest\n * Supports dependency resolution and schema overrides\n *\n * @param options - Schema initialization options\n * @returns Promise that resolves when schemas are initialized\n */\n initializeSchemas?: (options: SchemaInitializationOptions) => Promise<void>;\n\n /**\n * Executes a callback within a database transaction\n * Automatically commits on success or rolls back on error\n *\n * Calling `transaction()` again on the `tx` handed to a callback is\n * adapter-specific — do not assume one behaviour across adapters:\n *\n * - **SQLite** (both the libsql and native-capabilities paths) — the nested\n * scope re-enters the transaction already in progress under a `SAVEPOINT`.\n * It reads the enclosing transaction's uncommitted rows, and if it throws,\n * only its own work is rolled back; the enclosing transaction stays usable.\n * - **DuckDB and JSON** — the engine has no `SAVEPOINT`, so nesting throws\n * {@link NestedTransactionError} without touching the connection. The\n * enclosing transaction is unaffected and can continue.\n * - **PostgreSQL** — the nested scope re-enters the enclosing transaction\n * under a `SAVEPOINT` on the same pooled connection, like SQLite. It used\n * to check out a second connection and begin an independent transaction,\n * which could not see the enclosing transaction's uncommitted rows and\n * could deadlock against it undetectably; that was fixed in #1108.\n *\n * Code that must run on every adapter should take the transaction as a\n * parameter rather than opening a nested one.\n *\n * **Transactions are serialized per connection on the single-connection\n * adapters** — SQLite (both paths), DuckDB, JSON and PGlite drive one\n * connection, and a connection can only be in one transaction at a time, so a concurrent\n * `transaction()` waits for the one in progress rather than corrupting it. A\n * queued call that waits longer than `transactionQueueTimeout` (30s by\n * default) rejects rather than stalling indefinitely. Re-entrant calls do not\n * queue — they take the savepoint or refusal path described above.\n * PostgreSQL pools, so top-level transactions run concurrently. Nested scopes\n * on one PostgreSQL transaction use a scope-local queue: concurrently started\n * siblings serialize, while each child gets its own queue for further nesting.\n *\n * @param callback - Function to execute within transaction\n * @returns Promise resolving to callback result\n * @throws {NestedTransactionError} When nesting on an adapter without\n * savepoint support\n *\n * @example Portable across all adapters\n * ```typescript\n * async function recordClick(db: DatabaseInterface, id: string) {\n * await db.update('links', { id }, { clicks: 1 });\n * }\n *\n * await db.transaction(async (tx) => {\n * await tx.query('SELECT * FROM links WHERE id = $1 FOR UPDATE', id);\n * await recordClick(tx, id); // reuses tx; never opens its own\n * });\n * ```\n */\n transaction?: <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ) => Promise<T>;\n\n /**\n * Begins a new transaction and returns a handle for manual control\n *\n * Unlike transaction(), this gives you explicit control over commit/rollback.\n * Ideal for test isolation where you want to rollback after each test.\n *\n * On the single-connection adapters — SQLite (both paths), DuckDB and JSON —\n * the handle owns the connection until {@link TransactionHandle.commit} or\n * {@link TransactionHandle.rollback} is called, so every other transaction on\n * that connection waits in the meantime. **A handle that is never ended holds\n * the connection for the life of the process**, and later transactions then\n * fail with the queue-timeout error instead of proceeding. End the handle in\n * a `finally`, and in per-test setup guard the teardown, because an\n * assertion that throws between `beginTransaction()` and the teardown would\n * otherwise strand it.\n *\n * @returns Promise resolving to a TransactionHandle\n *\n * @example Test isolation pattern\n * ```typescript\n * let tx: TransactionHandle | undefined;\n *\n * beforeEach(async () => {\n * tx = await db.beginTransaction();\n * });\n *\n * afterEach(async () => {\n * // Guarded: if beforeEach threw, there is no handle to end, and an\n * // unguarded rollback would mask that failure with its own.\n * await tx?.rollback(); // Discard all test changes\n * tx = undefined;\n * });\n *\n * it('creates user', async () => {\n * await tx.insert('users', { id: '1', name: 'Test' });\n * const user = await tx.get('users', { id: '1' });\n * expect(user).toBeDefined();\n * // Changes rolled back in afterEach\n * });\n * ```\n */\n beginTransaction?: () => Promise<TransactionHandle>;\n\n /**\n * Acquire a pinned single-connection session.\n *\n * Returns a {@link SessionHandle} whose queries all run on one underlying\n * connection, enabling session-scoped state (e.g. PostgreSQL session advisory\n * locks) that the pooled top-level {@link query} cannot hold reliably. The\n * caller must {@link SessionHandle.release} it.\n *\n * Only meaningful for engines with session-scoped state, so it is currently\n * implemented for PostgreSQL only; check for the method before calling\n * (`if (db.acquireSession)`). Single-connection engines (SQLite, DuckDB) have\n * no session-scoped locks to hold, so they leave it undefined.\n *\n * @returns Promise resolving to a SessionHandle\n *\n * @example Hold a Postgres session advisory lock\n * ```typescript\n * const session = await db.acquireSession();\n * try {\n * await session.query('SELECT pg_advisory_lock($1)', 42);\n * // ... lock is held for the life of this session ...\n * } finally {\n * await session.release(); // frees the lock and drops the connection\n * }\n * ```\n */\n acquireSession?: () => Promise<SessionHandle>;\n\n /**\n * Retrieves the schema information for a table\n *\n * @param table - Table name\n * @returns Promise resolving to table schema info or null if table doesn't exist\n */\n getTableSchema?: (table: string) => Promise<TableSchemaInfo | null>;\n\n /**\n * Vector operations for native similarity search\n *\n * When available, enables database-level vector operations (e.g., pgvector).\n * Consumers should check for this capability rather than checking the database type.\n */\n vector?: VectorCapabilities;\n\n /**\n * Optional notification/pub-sub operations.\n */\n notifications?: NotificationCapabilities;\n\n /**\n * Close the database adapter and release native resources when supported.\n */\n close?: () => Promise<void>;\n\n /**\n * ALTER TABLE operations for schema evolution\n */\n alterTable?: {\n /**\n * Adds a new column to an existing table\n *\n * @param table - Table name\n * @param column - Column definition with name\n * @returns Promise that resolves when column is added\n */\n addColumn: (\n table: string,\n column: ColumnDefinitionWithName,\n ) => Promise<void>;\n\n /**\n * Adds a new index to an existing table\n *\n * @param table - Table name\n * @param index - Index definition\n * @returns Promise that resolves when index is created\n */\n addIndex: (table: string, index: IndexDefinition) => Promise<void>;\n };\n}\n\n/**\n * Transaction handle for manual transaction control\n *\n * Extends DatabaseInterface with commit() and rollback() methods.\n * Use beginTransaction() to obtain a handle, then explicitly\n * commit or rollback when done.\n *\n * @example Test isolation with rollback\n * ```typescript\n * const tx = await db.beginTransaction();\n * try {\n * // All operations happen in the transaction\n * await tx.insert('users', { id: '1', name: 'Test' });\n * const user = await tx.get('users', { id: '1' });\n *\n * // Rollback discards all changes (useful for test isolation)\n * await tx.rollback();\n * } catch (error) {\n * await tx.rollback();\n * throw error;\n * }\n * ```\n *\n * @example Manual commit\n * ```typescript\n * const tx = await db.beginTransaction();\n * try {\n * await tx.insert('orders', { id: '1', total: 100 });\n * await tx.insert('order_items', { order_id: '1', product: 'Widget' });\n * await tx.commit(); // Persist changes\n * } catch (error) {\n * await tx.rollback();\n * throw error;\n * }\n * ```\n */\nexport interface TransactionHandle extends DatabaseInterface {\n /**\n * Commits the transaction, persisting all changes\n *\n * After commit, the transaction handle should not be used.\n */\n commit: () => Promise<void>;\n\n /**\n * Rolls back the transaction, discarding all changes\n *\n * After rollback, the transaction handle should not be used.\n * This is the key method for test isolation - rollback after each test.\n */\n rollback: () => Promise<void>;\n\n /**\n * Whether the transaction is still active (not committed or rolled back)\n */\n isActive: () => boolean;\n}\n\n/**\n * Pinned single-connection session handle.\n *\n * Obtained via {@link DatabaseInterface.acquireSession}. Unlike the pooled\n * top-level `query` (which may run each statement on a different connection),\n * every call on a session handle runs on the *same* underlying connection for\n * the handle's lifetime. This is required for session-scoped state such as\n * PostgreSQL session advisory locks (`pg_advisory_lock`), which must be\n * acquired, observed, and released on one connection — and which release\n * automatically if the connection drops (e.g. the process dies).\n *\n * A session holds one connection out of the pool (default max 20) for its\n * entire lifetime, so it is meant to be held one-per-process, not per-operation.\n * Always {@link SessionHandle.release} the handle when done, and release it\n * *before* calling `db.client.end()` — a pinned connection keeps `pool.end()`\n * from resolving.\n */\nexport interface SessionHandle {\n /**\n * Execute a raw query on this session's pinned connection.\n * Same placeholder/return contract as {@link DatabaseInterface.query}.\n * Throws if the session has been released or its connection was lost.\n */\n query: (\n sql: string,\n ...vars: any[]\n ) => Promise<{ rows: Record<string, any>[]; rowCount: number }>;\n\n /**\n * Whether the session is still usable — not released and its connection has\n * not errored/dropped. A long-lived lock holder can poll this without\n * issuing a query.\n */\n isActive: () => boolean;\n\n /**\n * Release the session. Idempotent. After release the handle must not be used.\n * Best-effort frees session-scoped locks, then drops the underlying\n * connection (it is NOT returned to the pool) so the database releases any\n * remaining session state — returning it to the pool would leak that state\n * onto a later, unrelated checkout.\n */\n release: () => Promise<void>;\n}\n\n/**\n * Simplified interface for table-specific operations\n */\nexport interface TableInterface {\n /**\n * Inserts one or more records into the table\n *\n * @param data - Single record or array of records to insert\n * @returns Promise resolving to operation result\n */\n insert: (\n data: Record<string, any> | Record<string, any>[],\n ) => Promise<QueryResult>;\n\n /**\n * Retrieves a single record from the table matching the where criteria\n *\n * @param where - Criteria to match records (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to matching record or null if not found\n */\n get: (where: WhereClause) => Promise<Record<string, any> | null>;\n\n /**\n * Retrieves multiple records from the table matching the where criteria\n *\n * @param where - Criteria to match records (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to array of matching records\n */\n list: (where: WhereClause) => Promise<Record<string, any>[]>;\n}\n","import { mkdir, readdir, stat } from 'node:fs/promises';\nimport { basename, extname, join } from 'node:path';\nimport { DatabaseError } from '@happyvertical/utils';\nimport { DatabaseSchemaManager } from './schema-manager';\nimport {\n escapeQuotedIdentifier,\n escapeStringLiteral,\n validateColumnNames,\n} from './shared/alter-utils';\nimport {\n ConnectionCache,\n enableCachedValidation,\n} from './shared/connection-cache';\nimport {\n createDuckDBResourceCloser,\n throwWithDuckDBCleanup,\n} from './shared/duckdb-resources';\nimport { convertUniqueIndexesToInlineConstraints } from './shared/duckdb-schema-utils';\nimport { redactDatabaseUrl } from './shared/redact-database-url';\nimport { createTransactionLock } from './shared/transaction-lock';\nimport type {\n DatabaseInterface,\n JSONOptions,\n JSONTableLoadError,\n QueryResult,\n SchemaInitializationOptions,\n TableInterface,\n TransactionHandle,\n UpsertOptions,\n} from './shared/types';\nimport { NestedTransactionError, resolveSchemas } from './shared/types';\nimport {\n buildWhere,\n formatDbError,\n resolveInsertColumns,\n wrapDatabaseError,\n} from './shared/utils';\n\n/**\n * Extend globalThis to include our connection cache properties.\n * Using globalThis ensures all module instances share the same cache,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * @see https://github.com/happyvertical/sdk/issues/678\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __haveSqlJSONConnectionCache:\n | ConnectionCache<DatabaseInterface>\n | undefined;\n}\n\n/**\n * Connection cache for in-memory DuckDB instances keyed by data directory URL\n * Enables sharing of in-memory databases across multiple getDatabase() calls\n * with the same data directory.\n *\n * Uses globalThis to ensure cache is shared across all module instances,\n * fixing the lost update bug in monorepos with workspace symlinks.\n *\n * @see https://github.com/happyvertical/sdk/issues/678\n */\nglobalThis.__haveSqlJSONConnectionCache ??=\n new ConnectionCache<DatabaseInterface>();\nconst memoryConnectionCache = globalThis.__haveSqlJSONConnectionCache;\nenableCachedValidation(memoryConnectionCache);\n\ntype JSONSourceCacheState = {\n activeWrites: Set<Promise<void>>;\n closing: boolean;\n dataDir: string;\n fingerprint: string | undefined;\n sourceWriteTail: Promise<void>;\n trackExternalChanges: boolean;\n};\n\nconst JSON_SOURCE_CACHE_STATE = Symbol.for(\n '@happyvertical/sql/json-source-cache-state/v1',\n);\n\ntype TrackedJSONDatabase = DatabaseInterface &\n Record<symbol, JSONSourceCacheState | undefined>;\n\nconst SOURCE_SNAPSHOT_ATTEMPTS = 3;\n\nfunction isMissingPathError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n error.code === 'ENOENT'\n );\n}\n\nasync function listJSONSourceFiles(dataDir: string): Promise<string[] | null> {\n try {\n return (await readdir(dataDir))\n .filter((file) => {\n const lower = file.toLowerCase();\n return lower.endsWith('.json') || lower.endsWith('.schema.sql');\n })\n .sort();\n } catch (error) {\n if (isMissingPathError(error)) return null;\n throw error;\n }\n}\n\n/**\n * Captures the JSON adapter's file-backed inputs without reading their contents.\n * Directory membership plus nanosecond timestamps, size and inode detect edits,\n * replacements, additions and removals while keeping cache hits inexpensive.\n */\nasync function getJSONSourceFingerprint(dataDir: string): Promise<string> {\n const statFiles = (files: string[]) =>\n Promise.all(\n files.map(async (file) => {\n const info = await stat(join(dataDir, file), { bigint: true });\n return [\n file,\n info.size.toString(),\n info.mtimeNs.toString(),\n info.ctimeNs.toString(),\n info.ino.toString(),\n ];\n }),\n );\n const sameFiles = (left: string[], right: string[] | null) =>\n right !== null &&\n left.length === right.length &&\n left.every((file, index) => file === right[index]);\n\n for (let attempt = 0; attempt < SOURCE_SNAPSHOT_ATTEMPTS; attempt += 1) {\n const filesBefore = await listJSONSourceFiles(dataDir);\n if (filesBefore === null) return 'directory:missing';\n\n try {\n const entriesBefore = await statFiles(filesBefore);\n const filesDuring = await listJSONSourceFiles(dataDir);\n if (!sameFiles(filesBefore, filesDuring)) continue;\n\n // Re-stat after the membership check. A same-file edit can otherwise\n // complete after that file's first stat while leaving the name list\n // unchanged, causing this acquisition to accept stale metadata.\n const entriesAfter = await statFiles(filesBefore);\n const filesAfter = await listJSONSourceFiles(dataDir);\n const fingerprintAfter = JSON.stringify(entriesAfter);\n if (\n sameFiles(filesBefore, filesAfter) &&\n JSON.stringify(entriesBefore) === fingerprintAfter\n ) {\n return fingerprintAfter;\n }\n } catch (error) {\n if (!isMissingPathError(error)) throw error;\n }\n }\n\n throw new DatabaseError(\n 'JSON source files changed while checking connection cache freshness',\n { dataDir },\n );\n}\n\nfunction getJSONSourceCacheState(\n db: DatabaseInterface,\n): JSONSourceCacheState | undefined {\n return (db as TrackedJSONDatabase)[JSON_SOURCE_CACHE_STATE];\n}\n\nfunction setJSONSourceCacheState(\n db: DatabaseInterface,\n state: JSONSourceCacheState,\n): void {\n Object.defineProperty(db, JSON_SOURCE_CACHE_STATE, {\n configurable: false,\n enumerable: false,\n value: state,\n writable: false,\n });\n}\n\nasync function waitForJSONSourceWrites(\n state: JSONSourceCacheState,\n): Promise<void> {\n while (state.activeWrites.size > 0) {\n await Promise.allSettled([...state.activeWrites]);\n }\n}\n\nasync function isCachedJSONDatabaseFresh(\n db: DatabaseInterface,\n): Promise<boolean> {\n const state = getJSONSourceCacheState(db);\n // A cached adapter created by an older module instance has no trustworthy\n // snapshot, so replace it once and establish the current tracking contract.\n if (!state) return false;\n if (!state.trackExternalChanges) return true;\n\n while (true) {\n await waitForJSONSourceWrites(state);\n const fingerprint = await getJSONSourceFingerprint(state.dataDir);\n if (state.activeWrites.size === 0) {\n return fingerprint === state.fingerprint;\n }\n }\n}\n\nasync function trackJSONSourceWrite<T>(\n db: DatabaseInterface,\n table: string,\n write: () => Promise<T>,\n): Promise<T> {\n const state = getJSONSourceCacheState(db);\n if (!state) return write();\n if (state.closing) {\n throw new DatabaseError('JSON database connection is closing');\n }\n\n const previousWrite = state.sourceWriteTail;\n let releaseWrite!: () => void;\n const currentWrite = new Promise<void>((resolve) => {\n releaseWrite = resolve;\n });\n state.sourceWriteTail = previousWrite.then(\n () => currentWrite,\n () => currentWrite,\n );\n\n const operation = (async () => {\n await previousWrite.catch(() => {});\n let succeeded = false;\n let fingerprintBeforeWrite: string | undefined;\n try {\n if (state.trackExternalChanges) {\n fingerprintBeforeWrite = await getJSONSourceFingerprint(state.dataDir);\n if (fingerprintBeforeWrite !== state.fingerprint) {\n state.fingerprint = undefined;\n throw new DatabaseError(\n 'JSON source files changed externally before the adapter could persist its write; acquire a fresh database and retry',\n { dataDir: state.dataDir, table },\n );\n }\n }\n const result = await write();\n succeeded = true;\n return result;\n } finally {\n if (succeeded && state.trackExternalChanges) {\n try {\n const fingerprintAfterWrite = await getJSONSourceFingerprint(\n state.dataDir,\n );\n const ownedFiles = new Set([`${table}.json`, `${table}.schema.sql`]);\n const excludeOwnedFiles = (fingerprint: string) =>\n JSON.stringify(\n (JSON.parse(fingerprint) as string[][]).filter(\n ([file]) => !ownedFiles.has(file),\n ),\n );\n\n // Only the table's own data/schema files may change during this\n // export. Preserve evidence of any unrelated external mutation so\n // the waiting cache acquisition replaces this adapter.\n state.fingerprint =\n fingerprintBeforeWrite !== undefined &&\n excludeOwnedFiles(fingerprintBeforeWrite) ===\n excludeOwnedFiles(fingerprintAfterWrite)\n ? fingerprintAfterWrite\n : undefined;\n } catch (error) {\n // The write itself succeeded. Mark the snapshot unknown so the next\n // acquisition replaces this adapter, but do not report a false write\n // failure after DuckDB has already persisted the data.\n state.fingerprint = undefined;\n console.warn(\n `[json-adapter] Could not refresh source fingerprint: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n } else if (!succeeded) {\n state.fingerprint = undefined;\n }\n releaseWrite();\n }\n })();\n const activeWrite = operation.then(\n () => undefined,\n () => undefined,\n );\n state.activeWrites.add(activeWrite);\n try {\n return await operation;\n } finally {\n state.activeWrites.delete(activeWrite);\n }\n}\n\n/**\n * Clears and closes all cached connections.\n * Callers must await completion before opening a replacement connection.\n */\nexport async function clearConnectionCache(): Promise<void> {\n await memoryConnectionCache.clear(closeJSONDatabase);\n}\n\nasync function closeJSONDatabase(db: DatabaseInterface): Promise<void> {\n await db.close?.();\n}\n\n/**\n * Creates a JSON database connection using DuckDB in-memory engine\n *\n * This adapter uses DuckDB as a query engine for JSON files, storing everything\n * in-memory and writing changes back to JSON files. No WAL files or persistent\n * database files are created.\n *\n * @param options - JSON database options\n * @returns Promise resolving to a DuckDB connection\n */\nasync function createJSONConnection(options: JSONOptions) {\n const { url, autoRegister = true, eagerLoadTables = true } = options;\n const schemas = resolveSchemas(options.schemas) ?? {};\n\n if (!url) {\n throw new DatabaseError('url is required for JSON adapter', {\n options,\n });\n }\n\n // Track JSON files pending deferred loading (tableName → filePath)\n const pendingJSONFiles = new Map<string, string>();\n\n // Track tables created with inferred schemas (need DROP before syncSchema())\n const inferredSchemaTables = new Set<string>();\n\n // Accumulate data-load failures so calling code can detect a table that was\n // created but left empty because its data could not be inserted (issue #1139)\n const tableLoadErrors: JSONTableLoadError[] = [];\n const reportLoadError = (info: JSONTableLoadError): void => {\n tableLoadErrors.push(info);\n // A caller callback must never break loading of the remaining tables\n try {\n options.onTableLoadError?.(info);\n } catch (callbackError) {\n console.error(\n '[json-adapter] onTableLoadError callback threw:',\n callbackError,\n );\n }\n };\n\n let instance: any;\n let connection: any;\n try {\n // Dynamic import to avoid bundling\n const duckdbModule = '@duckdb/node-api';\n const { DuckDBInstance } = await import(/* @vite-ignore */ duckdbModule);\n\n // Always use in-memory database - no persistent files\n instance = await DuckDBInstance.create(':memory:');\n connection = await instance.connect();\n\n // Ensure data directory exists\n try {\n await mkdir(url, { recursive: true });\n } catch (_error) {\n // Directory might already exist, that's okay\n }\n\n // Scan JSON files - tables with schemas are created now, others are deferred\n if (autoRegister) {\n await loadJSONTables(\n connection,\n url,\n schemas,\n pendingJSONFiles,\n eagerLoadTables,\n inferredSchemaTables,\n reportLoadError,\n );\n }\n\n return {\n instance,\n connection,\n pendingJSONFiles,\n inferredSchemaTables,\n tableLoadErrors,\n reportLoadError,\n };\n } catch (error) {\n const errorMessage = redactDatabaseUrl(\n error instanceof Error ? error.message : String(error),\n );\n const databaseError = new DatabaseError(\n `Failed to create JSON database connection: ${errorMessage}`,\n {\n url: redactDatabaseUrl(url),\n originalError: errorMessage,\n },\n );\n return throwWithDuckDBCleanup(\n databaseError,\n createDuckDBResourceCloser(connection, instance),\n );\n }\n}\n\n/**\n * Loads JSON data into an existing table\n *\n * This function only loads data - it assumes the table already exists with proper schema.\n * Used after DDL creates the table to load persisted JSON data.\n *\n * An unreadable or malformed file is tolerated with a warning - the table\n * structure is what matters and there is no data to lose.\n *\n * A failure to insert records that DID parse is logged loudly rather than\n * swallowed with the old dismissive \"that's okay\" warning: the resulting empty\n * table is a real surprise the caller should see (issue #1132). It is logged\n * rather than thrown so that one unloadable table cannot abort the load of every\n * other table sharing the same data directory or syncSchema() call - both of\n * those paths already load each table independently.\n *\n * @param connection - DuckDB connection\n * @param tableName - Name of the table to load data into\n * @param filePath - Path to the JSON file\n * @param reportLoadError - Sink invoked when parsed records fail to insert (issue #1139)\n */\nasync function loadJSONData(\n connection: any,\n tableName: string,\n filePath: string,\n reportLoadError?: (info: JSONTableLoadError) => void,\n): Promise<void> {\n let records: unknown;\n\n try {\n const { readFile } = await import('node:fs/promises');\n const jsonContent = await readFile(filePath, 'utf-8');\n records = JSON.parse(jsonContent);\n } catch (error) {\n // Missing, empty, or malformed file - table structure is what matters\n console.warn(\n `[json-adapter] Could not read data for ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n return;\n }\n\n if (!Array.isArray(records) || records.length === 0) {\n return;\n }\n\n try {\n const inserted = await insertRecordsWithCast(\n connection,\n tableName,\n records,\n );\n if (inserted > 0) {\n console.log(\n `[json-adapter] Loaded ${inserted} records into ${tableName} from JSON`,\n );\n }\n } catch (error) {\n // Pass the raw error as a second argument so its context (the underlying\n // DuckDB cause, e.g. a NOT NULL violation) is rendered, not just the wrapper\n // message - diagnosability is the point of surfacing this (issue #1132)\n console.error(\n `[json-adapter] Failed to load data for ${tableName}; the table will be empty:`,\n error,\n );\n // Also surface it to calling code, which cannot otherwise tell an empty\n // table apart from a table that failed to load (issue #1139)\n reportLoadError?.({ table: tableName, filePath, error });\n }\n}\n\n/**\n * Scans the data directory and registers JSON files for deferred loading\n *\n * Unlike the DuckDB adapter which creates views, this creates actual tables\n * so that indexes can be added.\n *\n * Schema resolution priority:\n * 1. Explicit schemas provided via options.schemas - table created immediately\n * 2. Schema file (.schema.sql) in data directory - table created with schema\n * 3. All other tables - DEFERRED until syncSchema() provides DDL\n *\n * IMPORTANT: Tables without explicit schemas are NOT auto-created from JSON.\n * This ensures UNIQUE constraints from syncSchema() are in place before data\n * is loaded, which is required for UPSERT operations to work correctly.\n *\n * For users who want auto-inference, use the inferSchemaFromJSON() method\n * after database creation.\n *\n * @param connection - DuckDB connection\n * @param dataDir - Directory containing JSON files\n * @param providedSchemas - Explicit schema definitions provided by caller (optional)\n * @param pendingJSONFiles - Map to store JSON files pending deferred loading\n * @param eagerLoadTables - Whether to eagerly create tables with inferred schemas\n * @param inferredSchemaTables - Set to track tables created with inferred schemas\n * @param reportLoadError - Sink for per-table data-load failures (issue #1139)\n */\nasync function loadJSONTables(\n connection: any,\n dataDir: string,\n providedSchemas: Record<string, import('./shared/types').SchemaProvider> = {},\n pendingJSONFiles: Map<string, string> = new Map(),\n eagerLoadTables: boolean = true,\n inferredSchemaTables: Set<string> = new Set(),\n reportLoadError?: (info: JSONTableLoadError) => void,\n) {\n try {\n const files = await readdir(dataDir);\n const jsonFiles = files.filter(\n (file) => extname(file).toLowerCase() === '.json',\n );\n\n for (const file of jsonFiles) {\n const filePath = join(dataDir, file);\n const tableName = basename(file, '.json');\n\n // Check for explicitly provided schema\n const schema = providedSchemas[tableName];\n\n if (schema) {\n // Create table with provided schema (proper typing)\n console.log(\n `[json-adapter] Creating ${tableName} with provided schema definition`,\n );\n await createTableFromSchema(connection, tableName, schema);\n\n // Load JSON data into properly-typed table\n await loadJSONData(connection, tableName, filePath, reportLoadError);\n } else {\n // Check for schema file first (preserves constraints like PRIMARY KEY, UNIQUE, etc.)\n const schemaPath = join(dataDir, `${tableName}.schema.sql`);\n const { readFile, access } = await import('node:fs/promises');\n const { constants } = await import('node:fs');\n\n let schemaExists = false;\n try {\n await access(schemaPath, constants.F_OK);\n schemaExists = true;\n } catch {\n schemaExists = false;\n }\n\n if (schemaExists) {\n // Schema file exists - create table and load data now\n try {\n const schemaDDL = await readFile(schemaPath, 'utf-8');\n await connection.run(schemaDDL);\n await loadJSONData(\n connection,\n tableName,\n filePath,\n reportLoadError,\n );\n } catch (error) {\n console.warn(\n `[json-adapter] Could not load ${tableName} with schema file: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n } else {\n // No schema file - either eager load with inferred schema or defer\n if (eagerLoadTables) {\n // Eager load: create table with inferred schema from JSON\n // This enables cross-table queries before syncSchema() is called\n // Note: Inferred schemas don't have UNIQUE constraints, so UPSERT won't work\n // until syncSchema() is called to upgrade the schema\n try {\n await connection.run(\n `CREATE TABLE IF NOT EXISTS \"${escapeQuotedIdentifier(tableName)}\" AS SELECT * FROM read_json_auto('${escapeStringLiteral(filePath)}')`,\n );\n inferredSchemaTables.add(tableName);\n console.log(\n `[json-adapter] Eagerly created ${tableName} with inferred schema (no UNIQUE constraints)`,\n );\n } catch (error) {\n // Deliberately NOT reported via reportLoadError (issue #1139):\n // CREATE TABLE AS SELECT is atomic, so any read_json_auto failure\n // leaves NO table — which surfaces loudly as a missing-table error\n // when queried, unlike the silent present-but-empty case that\n // getTableLoadErrors()/onTableLoadError target. If this branch ever\n // gains something like read_json(..., ignore_errors := true) that\n // could leave a partially-created/empty table, revisit that scope.\n console.warn(\n `[json-adapter] Could not eagerly load ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n } else {\n console.log(\n `[json-adapter] Deferring ${tableName} - will load after syncSchema() creates table`,\n );\n }\n // Always track for later schema upgrade and data reload\n pendingJSONFiles.set(tableName, filePath);\n }\n }\n }\n } catch (error) {\n // If directory doesn't exist or is empty, that's okay\n if ((error as any).code !== 'ENOENT') {\n // Log the error for debugging\n console.error('[json-adapter] Error loading JSON tables:', error);\n throw new DatabaseError(`Failed to load JSON tables from ${dataDir}`, {\n dataDir,\n originalError: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n }\n }\n}\n\n/**\n * Converts DuckDB-specific type representations to JavaScript types\n *\n * Handles three cases:\n * 1. JavaScript BigInt values → converted to numbers\n * 2. DuckDB hugeint objects ({ hugeint: number }) → converted to strings\n * 3. DuckDB timestamp objects ({ micros: number }) → converted to Date objects\n *\n * DuckDB represents HUGEINT (128-bit integers) as objects with a single\n * property called 'hugeint'. When UUID strings (stored as TEXT) are\n * exported to JSON via COPY TO, DuckDB sometimes converts them to hugeint\n * representation. This function converts them back to strings.\n *\n * DuckDB represents TIMESTAMP values as objects with a single property\n * called 'micros' containing microseconds since Unix epoch. This function\n * converts them to JavaScript Date objects for proper round-trip serialization.\n *\n * @param obj - Object that may contain BigInt, hugeint, or timestamp values\n * @returns Object with DuckDB types converted to JavaScript types\n */\nfunction convertBigInts(obj: any): any {\n if (obj === null || obj === undefined) return obj;\n if (typeof obj === 'bigint') return Number(obj);\n\n // Handle DuckDB hugeint objects: { hugeint: number }\n // Convert to string to preserve UUID/TEXT values\n if (\n typeof obj === 'object' &&\n !Array.isArray(obj) &&\n 'hugeint' in obj &&\n Object.keys(obj).length === 1\n ) {\n // Convert hugeint to string representation\n // For UUIDs and other TEXT values that DuckDB converted\n return String(obj.hugeint);\n }\n\n // Handle DuckDB timestamp objects: { micros: number | bigint }\n // Convert to JavaScript Date for proper round-trip serialization\n // Fixes issue #319: JSON adapter fails to serialize Date fields during UPSERT\n if (\n typeof obj === 'object' &&\n !Array.isArray(obj) &&\n 'micros' in obj &&\n Object.keys(obj).length === 1 &&\n (typeof obj.micros === 'number' || typeof obj.micros === 'bigint')\n ) {\n // Convert microseconds to milliseconds for Date constructor\n // DuckDB stores timestamps as microseconds since Unix epoch\n // Handle both number and bigint types (DuckDB can return either)\n const micros =\n typeof obj.micros === 'bigint' ? Number(obj.micros) : obj.micros;\n const milliseconds = micros / 1000;\n return new Date(milliseconds);\n }\n\n if (Array.isArray(obj)) return obj.map(convertBigInts);\n if (typeof obj === 'object') {\n const result: any = {};\n for (const key in obj) {\n result[key] = convertBigInts(obj[key]);\n }\n return result;\n }\n return obj;\n}\n\n/**\n * Creates a table from a schema definition with proper typing\n *\n * DuckDB has issues with type inference when DEFAULT values are empty strings.\n * This function explicitly casts all DEFAULT values to their column types to\n * prevent DuckDB from inferring ANY type.\n *\n * Additionally, DuckDB has a known limitation (issue #12684) where ON CONFLICT\n * clauses fail with UNIQUE INDEX but work with inline UNIQUE constraints.\n * This function converts UNIQUE indexes to inline constraints for compatibility.\n *\n * @param connection - DuckDB connection\n * @param tableName - Name of the table to create\n * @param schema - Schema definition with DDL and indexes\n */\nasync function createTableFromSchema(\n connection: any,\n tableName: string,\n schema: import('./shared/types').SchemaProvider,\n): Promise<void> {\n // Extract CREATE TABLE statement (without triggers which aren't supported)\n const ddlLines = schema.ddl.split('\\n');\n const createTableEnd = ddlLines.findIndex((line) => {\n const trimmed = line.trim();\n return trimmed === ')' || trimmed.startsWith(');');\n });\n\n if (createTableEnd === -1) {\n throw new DatabaseError('Invalid schema DDL - no closing parenthesis', {\n tableName,\n ddl: schema.ddl,\n });\n }\n\n // Get CREATE TABLE statement\n let createTableSQL = ddlLines.slice(0, createTableEnd + 1).join('\\n');\n\n // Fix DEFAULT values for DuckDB type inference\n // DuckDB infers ANY type when DEFAULT is an empty string without explicit cast\n // Replace patterns like: DEFAULT '' with DEFAULT CAST('' AS TEXT)\n createTableSQL = createTableSQL.replace(\n /\\b(\\w+)\\s+(TEXT|VARCHAR)\\s+DEFAULT\\s+''/g,\n \"$1 $2 DEFAULT CAST('' AS $2)\",\n );\n\n // Also handle DEFAULT NULL cases to ensure proper typing\n createTableSQL = createTableSQL.replace(\n /\\b(\\w+)\\s+(TEXT|VARCHAR)\\s+DEFAULT\\s+NULL/g,\n '$1 $2 DEFAULT CAST(NULL AS $2)',\n );\n\n // Convert UNIQUE indexes to inline UNIQUE constraints for DuckDB compatibility\n // DuckDB's ON CONFLICT requires inline constraints, not separate indexes\n const transformed = convertUniqueIndexesToInlineConstraints(\n createTableSQL,\n schema.indexes,\n );\n createTableSQL = transformed.ddl;\n const indexesToCreate = transformed.indexes;\n\n try {\n await connection.run(createTableSQL);\n\n // Get actual columns after table creation\n let existingColumns: Set<string> = new Set();\n try {\n const schemaInfo = await connection.runAndReadAll(\n `DESCRIBE ${tableName}`,\n );\n const columns = schemaInfo.getRowObjects();\n existingColumns = new Set(\n columns.map((col: any) => col.column_name.toLowerCase()),\n );\n\n console.log(`[json-adapter] Created ${tableName} with schema:`, {\n columns: columns.map((col: any) => ({\n name: col.column_name,\n type: col.column_type,\n null: col.null,\n })),\n });\n } catch (error) {\n // Schema verification failed, but table was created - log warning and continue\n console.warn(\n `[json-adapter] Could not verify schema for ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Add columns from fields that aren't in the DDL (important for STI subclass fields)\n if (schema.fields && existingColumns.size > 0) {\n // Handle both Map and Record formats\n const fieldEntries =\n schema.fields instanceof Map\n ? Array.from(schema.fields.entries())\n : Object.entries(schema.fields);\n\n for (const [fieldName, fieldDef] of fieldEntries) {\n const columnName = fieldName.toLowerCase();\n if (!existingColumns.has(columnName)) {\n // Determine SQL type from field definition\n const sqlType = fieldDef?.type || 'TEXT';\n try {\n await connection.run(\n `ALTER TABLE ${tableName} ADD COLUMN \"${fieldName}\" ${sqlType}`,\n );\n console.log(\n `[json-adapter] Added missing column '${fieldName}' (${sqlType}) to ${tableName}`,\n );\n } catch (alterError) {\n console.warn(\n `[json-adapter] Could not add column '${fieldName}' to ${tableName}: ${alterError instanceof Error ? alterError.message : String(alterError)}`,\n );\n }\n }\n }\n }\n\n // Create remaining indexes (non-UNIQUE indexes only)\n // UNIQUE indexes have been converted to inline constraints\n if (indexesToCreate && indexesToCreate.length > 0) {\n for (const indexSQL of indexesToCreate) {\n try {\n await connection.run(indexSQL);\n } catch (error) {\n // Index creation might fail if column doesn't exist or syntax incompatibility\n console.warn(\n `[json-adapter] Failed to create index: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n } catch (error) {\n throw new DatabaseError('Failed to create table from schema', {\n tableName,\n sql: createTableSQL,\n originalError: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Reads the column names of an existing table, keyed by lowercase name\n *\n * The table-name predicate is case-insensitive: DuckDB resolves identifiers\n * case-insensitively, so a data file named `Items.json` can back a table\n * created as `items`. Matching table_name exactly would find no columns and\n * make the loader wrongly report that every field is unmatched (issue #1132).\n *\n * @param connection - DuckDB connection\n * @param tableName - Name of the table to describe\n * @returns Map of lowercase column name to the column's declared name\n */\nasync function getTableColumns(\n connection: any,\n tableName: string,\n): Promise<Map<string, string>> {\n const reader = await connection.runAndReadAll(\n 'SELECT column_name FROM information_schema.columns WHERE LOWER(table_name) = LOWER($1) ORDER BY ordinal_position',\n [tableName],\n );\n\n return new Map(\n reader\n .getRowObjects()\n .map((row: any) => [\n String(row.column_name).toLowerCase(),\n String(row.column_name),\n ]),\n );\n}\n\n/**\n * Inserts records with proper CAST handling for empty strings\n *\n * This helper avoids using read_json() with auto_detect, which causes DuckDB\n * to re-infer column types from data (causing ANY type for empty strings).\n * Instead, it uses parameterized INSERT with CAST to maintain table schema.\n *\n * Used only for loading a JSON data file, where records having different key\n * sets is normal input rather than a caller error: a document with an optional\n * field omits that key entirely on the records that lack it. Columns are\n * therefore unioned across ALL records and any record missing a key binds NULL.\n * Deriving them from the first record alone bound `undefined` for a missing key\n * (which DuckDB rejects, losing every row) and dropped keys the first record\n * did not have (losing the column silently). See issue #1132.\n *\n * The caller-facing insert() API is a separate implementation (it resolves\n * columns through `resolveInsertColumns`) and is not affected by this\n * file-loading tolerance.\n *\n * @param connection - DuckDB connection\n * @param tableName - Name of the table to insert into\n * @param records - Array of records to insert\n * @returns Number of records inserted (0 if there was nothing loadable)\n * @throws DatabaseError if the resolved records cannot be inserted\n */\nasync function insertRecordsWithCast(\n connection: any,\n tableName: string,\n records: unknown[],\n): Promise<number> {\n if (records.length === 0) return 0;\n\n // Only object records contribute columns AND rows. A non-object element\n // (e.g. a `null` hole in the array) must be skipped entirely - iterating it\n // for values would emit a phantom all-NULL row.\n const objectRecords = records.filter(\n (record): record is Record<string, any> =>\n record != null && typeof record === 'object' && !Array.isArray(record),\n );\n\n if (objectRecords.length === 0) {\n return 0;\n }\n\n // Union keys across every record, preserving first-appearance order\n const unionedKeys: string[] = [];\n const seenKeys = new Set<string>();\n for (const record of objectRecords) {\n for (const key of Object.keys(record)) {\n if (!seenKeys.has(key)) {\n seenKeys.add(key);\n unionedKeys.push(key);\n }\n }\n }\n\n if (unionedKeys.length === 0) {\n console.warn(\n `[json-adapter] Skipping data load for ${tableName}: records have no fields`,\n );\n return 0;\n }\n\n // Resolve keys against the table's real columns. A JSON field with no\n // matching column cannot be inserted; report it instead of failing the whole\n // load, so one stray field does not cost every row. Column matching is\n // case-insensitive (DuckDB folds identifier case), so track which columns are\n // already claimed - two case-variant keys (`id` and `ID`) must not both emit\n // the same column, which would produce a duplicate-column INSERT.\n const tableColumns = await getTableColumns(connection, tableName);\n const keys: string[] = [];\n const columnNames: string[] = [];\n const unmatchedKeys: string[] = [];\n const claimedColumns = new Set<string>();\n\n for (const key of unionedKeys) {\n const column = tableColumns.get(key.toLowerCase());\n if (column === undefined || claimedColumns.has(column.toLowerCase())) {\n // No matching column, or a differently-cased key already claimed it\n unmatchedKeys.push(key);\n } else {\n claimedColumns.add(column.toLowerCase());\n keys.push(key);\n columnNames.push(column);\n }\n }\n\n if (unmatchedKeys.length > 0) {\n console.warn(\n `[json-adapter] Ignoring ${unmatchedKeys.length} field(s) in ${tableName} data with no matching column: ${unmatchedKeys.join(', ')}`,\n );\n }\n\n if (keys.length === 0) {\n throw new DatabaseError(\n `No fields in the ${tableName} data file match a column on the table`,\n {\n tableName,\n fields: unionedKeys,\n columns: [...tableColumns.values()],\n },\n );\n }\n\n // A record that carries none of the matched keys would insert a row of all\n // NULLs - which is not data the file meant to provide and can violate a NOT\n // NULL / PRIMARY KEY column, failing the whole batch. Skip such records so a\n // stray record, like a stray field, does not cost the rest of the load.\n // (keys is non-empty here, and every key came from some record, so at least\n // that record survives - insertableRecords cannot be empty.)\n const insertableRecords = objectRecords.filter((record) =>\n keys.some((key) => key in record),\n );\n\n // Build placeholders - schema has NOT NULL DEFAULT '' to prevent ANY type\n const values: any[] = [];\n let paramIdx = 1;\n\n const placeholders = insertableRecords\n .map((record) => {\n const rowPlaceholders = keys.map((key) => {\n const value = record[key];\n\n // `undefined` covers a key this record omits; binding it directly makes\n // DuckDB reject the whole INSERT (\"Cannot create values of type ANY\"),\n // so bind NULL for the missing field, exactly as duckdb.ts does.\n if (value === null || value === undefined) {\n return 'NULL';\n } else if (value === '' && typeof value === 'string') {\n // CAST empty strings to TEXT to prevent DuckDB ANY type inference on parameters\n values.push(value);\n return `CAST($${paramIdx++} AS TEXT)`;\n } else if (value instanceof Date) {\n // Convert Date objects to ISO strings for DuckDB\n // CAST to TIMESTAMP to prevent DuckDB ANY type inference (issue #540)\n values.push(value.toISOString());\n return `CAST($${paramIdx++} AS TIMESTAMP)`;\n } else if (Array.isArray(value)) {\n // CAST arrays to JSON to prevent DuckDB ANY type inference\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx++} AS JSON)`;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n // CAST plain objects to JSON to prevent DuckDB ANY type inference\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx++} AS JSON)`;\n } else {\n // Direct parameter binding for other values\n values.push(value);\n return `$${paramIdx++}`;\n }\n });\n return `(${rowPlaceholders.join(', ')})`;\n })\n .join(', ');\n\n // Use the table's own column names - they came from information_schema, so\n // no JSON-supplied identifier reaches the statement\n const columnList = columnNames.map((name) => `\"${name}\"`).join(', ');\n const sql = `INSERT INTO \"${tableName}\" (${columnList}) VALUES ${placeholders}`;\n\n try {\n await connection.run(sql, values);\n } catch (e) {\n throw new DatabaseError('Failed to load records into table', {\n tableName,\n recordCount: insertableRecords.length,\n columns: columnNames,\n originalError: formatDbError(e),\n });\n }\n\n return insertableRecords.length;\n}\n\n/**\n * Validates that a table name is valid (alphanumeric + underscores only)\n *\n * @param table - Table name to validate\n * @throws DatabaseError if table name is invalid\n */\nfunction validateTableName(table: string): void {\n // Reject non-strings before the regex: `test` coerces via `toString`, so an\n // object with a stateful `toString` could pass here and interpolate a\n // different name into SQL. See shared/alter-utils.ts:validateTableName.\n if (typeof table !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {\n throw new DatabaseError(`Invalid table name: ${table}`, { table });\n }\n}\n\n/**\n * Creates a JSON database adapter using DuckDB in-memory engine\n *\n * This adapter provides SQL query capabilities over JSON files without creating\n * persistent database files or WAL files. All data is stored in-memory during\n * runtime and written back to JSON files based on the write strategy.\n *\n * @param options - JSON database options\n * @returns Database interface for JSON files\n */\nexport async function getDatabase(\n options: JSONOptions,\n): Promise<DatabaseInterface> {\n // Generate cache key from URL and options that affect database behavior\n // FIX for issues #533/#534: Auto-generate dbid from URL when schemas provided\n // This ensures connection caching ALWAYS happens for the same URL, preventing\n // the lost update bug where multiple connections overwrite each other's data.\n //\n // Priority:\n // 1. Explicit dbid from caller\n // 2. Auto-generated from URL + writeStrategy (ensures caching)\n const cacheKey =\n options.dbid ||\n `json:${options.url}:${options.writeStrategy || 'immediate'}:${options.autoRegister !== false}`;\n return memoryConnectionCache.getOrCreate(\n cacheKey,\n options,\n () => createTrackedJSONDatabase(options),\n closeJSONDatabase,\n isCachedJSONDatabaseFresh,\n );\n}\n\nasync function createTrackedJSONDatabase(\n options: JSONOptions,\n): Promise<DatabaseInterface> {\n const trackExternalChanges = options.autoRegister !== false;\n const dataDir = options.url;\n\n if (!trackExternalChanges || !dataDir) {\n const db = await createJSONDatabase(options);\n setJSONSourceCacheState(db, {\n activeWrites: new Set(),\n closing: false,\n dataDir: dataDir ?? '',\n fingerprint: undefined,\n sourceWriteTail: Promise.resolve(),\n trackExternalChanges: false,\n });\n return db;\n }\n\n for (let attempt = 0; attempt < SOURCE_SNAPSHOT_ATTEMPTS; attempt += 1) {\n const before = await getJSONSourceFingerprint(dataDir);\n const db = await createJSONDatabase(options);\n try {\n const after = await getJSONSourceFingerprint(dataDir);\n if (before === after) {\n setJSONSourceCacheState(db, {\n activeWrites: new Set(),\n closing: false,\n dataDir,\n fingerprint: after,\n sourceWriteTail: Promise.resolve(),\n trackExternalChanges: true,\n });\n return db;\n }\n } catch (error) {\n await closeJSONDatabase(db);\n throw error;\n }\n await closeJSONDatabase(db);\n }\n\n throw new DatabaseError(\n 'JSON source files did not remain stable while creating the database',\n { dataDir },\n );\n}\n\nasync function createJSONDatabase(\n options: JSONOptions,\n): Promise<DatabaseInterface> {\n return (async () => {\n const {\n instance,\n connection,\n pendingJSONFiles,\n inferredSchemaTables,\n tableLoadErrors,\n reportLoadError,\n } = await createJSONConnection(options);\n const writeStrategy = options.writeStrategy || 'immediate';\n const { url } = options;\n let db!: DatabaseInterface;\n\n // One lock per connection. `getDatabase` hands cached callers this same\n // closure, so \"created here\" and \"per connection\" are the same scope —\n // including across duplicated module copies, since the connection cache\n // itself lives on globalThis.\n const connectionLock = createTransactionLock(\n 'json',\n options.transactionQueueTimeout,\n );\n\n /**\n * Inserts one or more records into a table\n *\n * @param table - Table name\n * @param data - Single record or array of records to insert\n * @returns Promise resolving to operation result\n * @throws Error if the insert operation fails\n */\n const insert = async (\n table: string,\n data: Record<string, any> | Record<string, any>[],\n ): Promise<QueryResult> => {\n validateTableName(table);\n\n // Enforce read-only mode\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot insert: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n\n const records = Array.isArray(data) ? data : [data];\n\n if (records.length === 0) {\n return { operation: 'insert', affected: 0 };\n }\n\n const keys = resolveInsertColumns(table, records);\n validateColumnNames(keys);\n\n // Build placeholders with CAST for empty strings\n const values: any[] = [];\n let paramIdx = 1;\n\n const placeholders = records\n .map((record) => {\n const rowPlaceholders = keys.map((key) => {\n const value = record[key];\n\n // DuckDB cannot bind `undefined`, so map it to NULL as duckdb.ts\n // does rather than failing the insert.\n if (value === null || value === undefined) {\n return 'NULL';\n } else if (value === '' && typeof value === 'string') {\n // CAST empty strings to TEXT to prevent DuckDB ANY type inference\n values.push(value);\n return `CAST($${paramIdx++} AS TEXT)`;\n } else if (value instanceof Date) {\n // Convert Date objects to ISO strings for DuckDB (issue #319)\n // CAST to TIMESTAMP to prevent DuckDB ANY type inference (issue #540)\n values.push(value.toISOString());\n return `CAST($${paramIdx++} AS TIMESTAMP)`;\n } else if (Array.isArray(value)) {\n // CAST arrays to JSON to prevent DuckDB ANY type inference\n // DuckDB cannot infer array element types from empty arrays or mixed types\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx++} AS JSON)`;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n // CAST plain objects to JSON to prevent DuckDB ANY type inference\n // Only applies to plain objects (not class instances)\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx++} AS JSON)`;\n } else {\n // Direct parameter binding for other values\n values.push(value);\n return `$${paramIdx++}`;\n }\n });\n return `(${rowPlaceholders.join(', ')})`;\n })\n .join(', ');\n\n const sql = `INSERT INTO \"${table}\" (${keys.join(', ')}) VALUES ${placeholders}`;\n\n try {\n await connection.run(sql, values);\n const affected = records.length;\n\n // Handle write-back strategy\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n return { operation: 'insert', affected };\n } catch (e) {\n throw new DatabaseError('Failed to insert records into table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Retrieves a single record matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records\n * @returns Promise resolving to matching record or null if not found\n * @throws Error if the query fails\n */\n const get = async (\n table: string,\n where: Record<string, any>,\n ): Promise<Record<string, any> | null> => {\n validateTableName(table);\n\n const { sql: whereClause, values } = buildWhere(where, 1, 'json');\n const sql = `SELECT * FROM \"${table}\" ${whereClause} LIMIT 1`;\n\n try {\n const reader = await connection.runAndReadAll(sql, values);\n const rows = reader.getRowObjects();\n return rows.length > 0 ? convertBigInts(rows[0]) : null;\n } catch (e) {\n throw new DatabaseError('Failed to retrieve record from table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Retrieves multiple records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records\n * @returns Promise resolving to array of matching records\n * @throws Error if the query fails\n */\n const list = async (\n table: string,\n where: Record<string, any>,\n ): Promise<Record<string, any>[]> => {\n validateTableName(table);\n\n const { sql: whereClause, values } = buildWhere(where, 1, 'json');\n const sql = `SELECT * FROM \"${table}\" ${whereClause}`;\n\n try {\n const reader = await connection.runAndReadAll(sql, values);\n return convertBigInts(reader.getRowObjects());\n } catch (e) {\n throw new DatabaseError('Failed to list records from table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Updates records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records to update\n * @param data - New data to set\n * @returns Promise resolving to operation result\n * @throws Error if the update operation fails\n */\n const update = async (\n table: string,\n where: Record<string, any>,\n data: Record<string, any>,\n ): Promise<QueryResult> => {\n validateTableName(table);\n\n // Enforce read-only mode\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot update: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n\n const keys = Object.keys(data);\n validateColumnNames(keys);\n const setClause = keys\n .map((key, idx) => `${key} = $${idx + 1}`)\n .join(', ');\n const { sql: whereClause, values: whereValues } = buildWhere(\n where,\n keys.length + 1,\n 'json',\n );\n\n // Read values through the validated key list rather than a second\n // enumeration of `data`. `Object.values` on a hostile Proxy could return\n // values in a different order (or for different keys) than the `keys`\n // that drive `setClause`, binding each value to the wrong column.\n // Convert Date objects to ISO strings for DuckDB (issue #319)\n const dataValues = keys.map((key) => {\n const value = data[key];\n return value instanceof Date ? value.toISOString() : value;\n });\n\n const sql = `UPDATE \"${table}\" SET ${setClause} ${whereClause}`;\n const values = [...dataValues, ...whereValues];\n\n try {\n await connection.run(sql, values);\n\n // Handle write-back strategy\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n // DuckDB doesn't return rowsAffected in the same way, estimate from where clause\n return { operation: 'update', affected: 1 };\n } catch (e) {\n throw new DatabaseError('Failed to update records in table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n const buildJSONValueExpression = (\n value: any,\n values: any[],\n paramIdx: { value: number },\n ): string => {\n if (value === null) {\n return 'NULL';\n }\n if (value === '' && typeof value === 'string') {\n values.push(value);\n return `CAST($${paramIdx.value++} AS TEXT)`;\n }\n if (value instanceof Date) {\n values.push(value.toISOString());\n return `CAST($${paramIdx.value++} AS TIMESTAMP)`;\n }\n if (Array.isArray(value)) {\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx.value++} AS JSON)`;\n }\n if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n values.push(JSON.stringify(value));\n return `CAST($${paramIdx.value++} AS JSON)`;\n }\n\n values.push(value);\n return `$${paramIdx.value++}`;\n };\n\n const executeNullAwareJSONUpsert = async (\n table: string,\n conflictColumns: string[],\n data: Record<string, any>,\n ): Promise<QueryResult> => {\n const conflictWhere = Object.fromEntries(\n conflictColumns.map((col) => [col, data[col]]),\n );\n const existing = await get(table, conflictWhere);\n\n if (!existing) {\n return insert(table, data);\n }\n\n const keys = Object.keys(data);\n const values: any[] = [];\n const paramIdx = { value: 1 };\n const assignments = keys.map((key) => {\n const valueExpr = buildJSONValueExpression(data[key], values, paramIdx);\n return `\"${escapeQuotedIdentifier(key)}\" = ${valueExpr}`;\n });\n const { sql: whereClause, values: whereValues } = buildWhere(\n conflictWhere,\n paramIdx.value,\n 'json',\n );\n const sql = `UPDATE \"${table}\" SET ${assignments.join(', ')} ${whereClause}`;\n\n try {\n await connection.run(sql, [...values, ...whereValues]);\n\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n return { operation: 'upsert', affected: 1 };\n } catch (e) {\n throw new DatabaseError('Failed to upsert record into table', {\n table,\n sql,\n values: [...values, ...whereValues],\n conflictColumns,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Inserts a record or updates it if it already exists (UPSERT)\n *\n * @param table - Table name\n * @param conflictColumns - Columns that define the uniqueness constraint\n * @param data - Data to insert or update\n * @returns Promise resolving to operation result\n * @throws Error if the upsert operation fails\n */\n const upsert = async (\n table: string,\n conflictColumns: string[],\n data: Record<string, any>,\n options?: UpsertOptions,\n ): Promise<QueryResult> => {\n validateTableName(table);\n\n // Enforce read-only mode\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot upsert: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n\n // Snapshot the record once. Every read below — the presence check, the\n // column list, the value list, and the null-aware branch — must see the\n // same keys and values, or a hostile object could present one shape to\n // the checks and another to the interpolated `keys`. A plain-object copy\n // cannot observe how many times it is read.\n const record = { ...data };\n // Coerce the conflict columns to primitive strings once, for the same\n // reason as `record`. These are quoted rather than validated (names that\n // need quotes keep working), and `escapeQuotedIdentifier` must run on a\n // primitive. Coercing here also pins the value the presence check sees to\n // the value the `ON CONFLICT` list escapes, so a crafted object cannot read\n // as `id` for the check and as unescaped SQL for the SQL.\n const conflictCols = conflictColumns.map((col) => String(col));\n // Conflict columns reach two very different positions. In the plain\n // `ON CONFLICT(...)` path below they are quoted, so a name that needs\n // quotes — `Full Name`, `user-id`, whatever the source JSON called its\n // keys — is ordinary and has always worked; escaping keeps it working\n // while making it impossible to leave the quotes. In the null-aware path\n // they become `buildWhere` keys instead, which requires a plain\n // identifier and rejects them there, as it did before this change.\n // Validate that all conflict columns are present in the data\n const missingColumns = conflictCols.filter((col) => !(col in record));\n\n if (missingColumns.length > 0) {\n throw new DatabaseError('Conflict columns missing from data', {\n table,\n conflictColumns: conflictCols,\n missingColumns,\n availableColumns: Object.keys(record),\n hint: 'All columns specified in ON CONFLICT must be present in the data being inserted. Undefined values should be replaced with null or an appropriate default.',\n });\n }\n\n if (\n !options?.nullsDistinct &&\n conflictCols.some((col) => record[col] === null)\n ) {\n return executeNullAwareJSONUpsert(table, conflictCols, record);\n }\n\n const keys = Object.keys(record);\n const dataValues = Object.values(record);\n\n // Build placeholders - schema has NOT NULL DEFAULT '' to prevent ANY type\n const placeholders: string[] = [];\n const values: any[] = [];\n let paramIdx = 1;\n\n for (const value of dataValues) {\n if (value === null) {\n placeholders.push('NULL');\n } else if (value === '' && typeof value === 'string') {\n // CAST empty strings to TEXT to prevent DuckDB ANY type inference on parameters\n placeholders.push(`CAST($${paramIdx} AS TEXT)`);\n values.push(value);\n paramIdx++;\n } else if (value instanceof Date) {\n // Convert Date objects to ISO strings for DuckDB\n // CAST to TIMESTAMP to prevent DuckDB ANY type inference (issue #540)\n placeholders.push(`CAST($${paramIdx} AS TIMESTAMP)`);\n values.push(value.toISOString());\n paramIdx++;\n } else if (Array.isArray(value)) {\n // CAST arrays to JSON to prevent DuckDB ANY type inference\n // DuckDB cannot infer array element types from empty arrays or mixed types\n placeholders.push(`CAST($${paramIdx} AS JSON)`);\n values.push(JSON.stringify(value));\n paramIdx++;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n // CAST plain objects to JSON to prevent DuckDB ANY type inference\n // Only applies to plain objects (not class instances)\n placeholders.push(`CAST($${paramIdx} AS JSON)`);\n values.push(JSON.stringify(value));\n paramIdx++;\n } else {\n // Direct parameter binding for other values\n placeholders.push(`$${paramIdx}`);\n values.push(value);\n paramIdx++;\n }\n }\n\n // Build UPDATE SET clause with same approach\n // DO NOT reset paramIdx - parameters must be unique across entire query\n const updateSetParts: string[] = [];\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i];\n const value = dataValues[i];\n\n if (value === null) {\n updateSetParts.push(`${key} = NULL`);\n } else if (value === '' && typeof value === 'string') {\n // CAST empty strings to TEXT to prevent DuckDB ANY type inference on parameters\n updateSetParts.push(`${key} = CAST($${paramIdx} AS TEXT)`);\n values.push(value);\n paramIdx++;\n } else if (value instanceof Date) {\n // Convert Date objects to ISO strings for DuckDB\n // CAST to TIMESTAMP to prevent DuckDB ANY type inference (issue #540)\n updateSetParts.push(`${key} = CAST($${paramIdx} AS TIMESTAMP)`);\n values.push(value.toISOString());\n paramIdx++;\n } else if (Array.isArray(value)) {\n // CAST arrays to JSON to prevent DuckDB ANY type inference\n updateSetParts.push(`${key} = CAST($${paramIdx} AS JSON)`);\n values.push(JSON.stringify(value));\n paramIdx++;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n Object.getPrototypeOf(value) === Object.prototype\n ) {\n // CAST plain objects to JSON to prevent DuckDB ANY type inference\n updateSetParts.push(`${key} = CAST($${paramIdx} AS JSON)`);\n values.push(JSON.stringify(value));\n paramIdx++;\n } else {\n // Direct parameter binding for other values\n updateSetParts.push(`${key} = $${paramIdx}`);\n values.push(value);\n paramIdx++;\n }\n }\n\n // Quote ALL column names to match DuckDB's schema generation\n // SchemaGenerator always quotes column names, so UPSERT must match\n const conflict = conflictCols\n .map((col) => `\"${escapeQuotedIdentifier(col)}\"`)\n .join(', ');\n const quotedKeys = keys\n .map((key) => `\"${escapeQuotedIdentifier(key)}\"`)\n .join(', ');\n\n // Quote column names in UPDATE SET clause to match schema and ON CONFLICT\n // Extract the value expression from each updateSetPart (everything after '=')\n const quotedUpdateSetParts = keys.map((key, i) => {\n const part = updateSetParts[i];\n const valueExpr = part.substring(part.indexOf('=') + 1).trim();\n return `\"${escapeQuotedIdentifier(key)}\" = ${valueExpr}`;\n });\n\n const sql = `INSERT INTO \"${table}\" (${quotedKeys}) VALUES (${placeholders.join(', ')}) ON CONFLICT(${conflict}) DO UPDATE SET ${quotedUpdateSetParts.join(', ')}`;\n\n try {\n await connection.run(sql, values);\n\n // Handle write-back strategy\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n return { operation: 'upsert', affected: 1 };\n } catch (e) {\n // Log detailed error information for debugging\n console.error('UPSERT failed:', {\n table,\n sql,\n values,\n valueTypes: values.map((v) => `${typeof v} (${v})`),\n conflictColumns: conflictCols,\n error: formatDbError(e),\n });\n throw new DatabaseError('Failed to upsert record into table', {\n table,\n sql,\n values,\n conflictColumns: conflictCols,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Gets a record matching the where criteria or inserts it if not found\n *\n * @param table - Table name\n * @param where - Criteria to match existing record\n * @param data - Data to insert if no record found\n * @returns Promise resolving to the record (either retrieved or newly inserted)\n * @throws Error if the operation fails\n */\n const getOrInsert = async (\n table: string,\n where: Record<string, any>,\n data: Record<string, any>,\n ): Promise<Record<string, any>> => {\n const result = await get(table, where);\n if (result) return result;\n\n await insert(table, data);\n const inserted = await get(table, where);\n\n if (!inserted) {\n throw new DatabaseError('Failed to insert and retrieve record', {\n table,\n where,\n data,\n });\n }\n\n return inserted;\n };\n\n /**\n * Deletes records from a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records for deletion\n * @returns Promise resolving to operation result with count of deleted rows\n * @throws Error if the delete operation fails\n */\n const deleteRecords = async (\n table: string,\n where: Record<string, any>,\n ): Promise<QueryResult> => {\n validateTableName(table);\n\n // Enforce read-only mode\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot delete: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n\n const keys = Object.keys(where);\n if (keys.length === 0) {\n throw new DatabaseError(\n 'DELETE requires at least one WHERE condition to prevent accidental deletion of all records',\n { table },\n );\n }\n\n const { sql: whereClause, values } = buildWhere(where, 1, 'json');\n const sql = `DELETE FROM \"${table}\" ${whereClause}`;\n\n try {\n await connection.run(sql, values);\n\n // Handle write-back strategy\n if (writeStrategy === 'immediate') {\n await persistTableToJSON(table);\n }\n\n return { operation: 'delete', affected: 1 };\n } catch (e) {\n throw new DatabaseError('Failed to delete records from table', {\n table,\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Counts records in a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (optional, counts all if omitted)\n * @returns Promise resolving to count of matching records\n * @throws Error if the count operation fails\n */\n const count = async (\n table: string,\n where?: Record<string, any>,\n ): Promise<number> => {\n validateTableName(table);\n\n try {\n if (!where || Object.keys(where).length === 0) {\n // Count all records\n const result = await connection.runAndReadAll(\n `SELECT COUNT(*) as count FROM \"${table}\"`,\n );\n const rows = result.getRowObjects();\n return Number(rows[0]?.count) || 0;\n }\n\n // Count with conditions\n const { sql: whereClause, values } = buildWhere(where, 1, 'json');\n const sql = `SELECT COUNT(*) as count FROM \"${table}\" ${whereClause}`;\n\n const result = await connection.runAndReadAll(sql, values);\n const rows = result.getRowObjects();\n\n return Number(rows[0]?.count) || 0;\n } catch (e) {\n throw new DatabaseError('Failed to count records in table', {\n table,\n where,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Checks if a table exists in the database\n *\n * @param tableName - Name of the table to check\n * @returns Promise resolving to boolean indicating if the table exists\n */\n const tableExists = async (tableName: string): Promise<boolean> => {\n // The name is interpolated into a statement, and this engine executes\n // every statement in the string it is given, so an unvalidated name here\n // is a DDL/DML sink rather than a read oracle — the `catch` below\n // swallows the error, not the execution. Validation runs outside the try\n // for that reason: a rejected identifier must be reported, not reported\n // as `false`.\n validateTableName(tableName);\n try {\n // Try to query the table\n await connection.runAndReadAll(`SELECT * FROM ${tableName} LIMIT 1`);\n return true;\n } catch {\n return false;\n }\n };\n\n /**\n * Exports a table to JSON and schema files\n *\n * Exports two files:\n * 1. {table}.json - Table data with columns cast appropriately\n * 2. {table}.schema.sql - CREATE TABLE statement to preserve constraints\n *\n * Note: JSON columns are NOT cast to TEXT to preserve object structure.\n * Other columns are cast to TEXT to prevent DuckDB from converting UUIDs\n * and other TEXT values to hugeint representation in JSON output.\n *\n * @param connection - DuckDB connection\n * @param table - Table name\n * @param dataDir - Directory to write files\n */\n const exportTableToJSON = async (\n connection: any,\n table: string,\n dataDir: string,\n ): Promise<void> => {\n // Every caller-facing path into this function takes a table name, and it\n // is interpolated into a COPY statement's identifier and into the target\n // file path. Unvalidated, `exportTable('t\") TO $$/etc/x$$; DROP ...; --')`\n // both writes a file outside the data directory and runs the trailing\n // statements. Validate at this choke point so the internal callers that\n // pass catalog-derived names are covered too.\n validateTableName(table);\n\n const filePath = join(dataDir, `${table}.json`);\n const schemaPath = join(dataDir, `${table}.schema.sql`);\n\n // Export schema first using SHOW CREATE TABLE\n try {\n const schemaResult = await connection.runAndReadAll(\n `SELECT sql FROM sqlite_master WHERE type='table' AND name='${table}'`,\n );\n const rows = schemaResult.getRowObjects();\n if (rows.length > 0 && rows[0].sql) {\n const { writeFile } = await import('node:fs/promises');\n await writeFile(schemaPath, rows[0].sql, 'utf-8');\n }\n } catch (error) {\n console.warn(\n `[json-adapter] Could not export schema for ${table}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n // Get column names and types to handle JSON columns specially\n // JSON columns should NOT be cast to TEXT to preserve object structure (fixes #542)\n const columnsResult = await connection.runAndReadAll(\n `SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${table}' ORDER BY ordinal_position`,\n );\n const columnInfo = columnsResult.getRowObjects() as {\n column_name: string;\n data_type: string;\n }[];\n\n if (columnInfo.length === 0) {\n // Fallback to SELECT * if we can't get column names\n await connection.run(\n `COPY (SELECT * FROM \"${table}\") TO '${filePath}' (FORMAT JSON, ARRAY true)`,\n );\n return;\n }\n\n // Preserve type-appropriate columns, only cast text-based columns to TEXT\n // - JSON/STRUCT: preserve as-is for object structure (fixes #542)\n // - Numeric types: preserve as-is so numbers export as numbers (fixes #694)\n // - Boolean: preserve as-is\n // - Text-based: cast to TEXT to prevent DuckDB hugeint conversion for UUIDs\n const selectList = columnInfo\n .map(({ column_name, data_type }) => {\n const upperType = data_type.toUpperCase();\n // Preserve JSON/STRUCT as-is for object structure\n if (upperType === 'JSON' || upperType.startsWith('STRUCT')) {\n return `\"${column_name}\"`;\n }\n // Preserve numeric types as-is so they export as numbers, not strings\n if (\n [\n 'DOUBLE',\n 'REAL',\n 'FLOAT',\n 'INTEGER',\n 'BIGINT',\n 'SMALLINT',\n 'TINYINT',\n 'DECIMAL',\n 'NUMERIC',\n 'HUGEINT',\n 'UBIGINT',\n 'UINTEGER',\n 'USMALLINT',\n 'UTINYINT',\n ].some((t) => upperType.includes(t))\n ) {\n return `\"${column_name}\"`;\n }\n // Preserve boolean types as-is\n if (upperType === 'BOOLEAN' || upperType === 'BOOL') {\n return `\"${column_name}\"`;\n }\n // Cast text-based columns to TEXT to prevent hugeint conversion for UUIDs\n return `CAST(\"${column_name}\" AS TEXT) AS \"${column_name}\"`;\n })\n .join(', ');\n const sql = `COPY (SELECT ${selectList} FROM \"${table}\") TO '${filePath}' (FORMAT JSON, ARRAY true)`;\n\n await connection.run(sql);\n };\n\n const persistTableToJSON = (table: string): Promise<void> =>\n trackJSONSourceWrite(db, table, () =>\n exportTableToJSON(connection, table, url),\n );\n\n /**\n * Manual export method for 'manual' write strategy\n *\n * Allows explicit control over when tables are written back to JSON files.\n *\n * @param table - Table name to export\n * @returns Promise that resolves when export completes\n */\n const exportTable = async (table: string): Promise<void> => {\n if (writeStrategy === 'none') {\n throw new DatabaseError(\n 'Cannot export table: write strategy is set to none (read-only mode)',\n { table, writeStrategy },\n );\n }\n await persistTableToJSON(table);\n };\n\n /**\n * Returns the table-load failures collected for this connection\n *\n * A JSON data file that parses but whose records cannot be inserted into a\n * table created for it (a renamed/dropped column, a NOT NULL / PRIMARY KEY\n * violation, or a file whose fields match no column) leaves that table\n * present-but-empty and is logged to stderr; calling code otherwise cannot\n * tell it apart from a legitimately empty one. This exposes those failures\n * so a caller can detect and react to them (issue #1139).\n *\n * Scope: this covers loads into a table created from a provided schema, a\n * `.schema.sql` file, or deferred `syncSchema()` / `execute()` DDL. A file\n * with no schema at all is loaded by DuckDB's own inference; if that fails\n * the table is simply not created, which already surfaces as an error when\n * you query it, so it is not reported here.\n *\n * The list accumulates across the connection's lifetime — covering both\n * connection-time and later deferred loads — and is append-only (a later\n * successful reload of the same table does not remove an earlier failure).\n * A fresh array is returned each call, so mutating it does not affect the\n * adapter's state. Because connections are cached per URL, callers sharing a\n * URL share this list.\n *\n * @returns Table-load failures, oldest first\n */\n const getTableLoadErrors = (): JSONTableLoadError[] => [...tableLoadErrors];\n\n /**\n * Creates a table by inferring schema from a JSON file\n *\n * This utility is for users who want automatic schema inference from JSON.\n * Note: Inferred schemas do NOT include UNIQUE constraints, so UPSERT\n * operations will fail. Use syncSchema() with explicit DDL for UPSERT support.\n *\n * @param tableName - Name of the table to create\n * @param jsonPath - Optional path to JSON file (defaults to {dataDir}/{tableName}.json)\n * @returns Promise that resolves when table is created\n * @throws Error if JSON file doesn't exist or table creation fails\n */\n const inferSchemaFromJSON = async (\n tableName: string,\n jsonPath?: string,\n ): Promise<void> => {\n validateTableName(tableName);\n\n const filePath = jsonPath || join(url, `${tableName}.json`);\n\n try {\n // Check if file exists\n const { access } = await import('node:fs/promises');\n const { constants } = await import('node:fs');\n await access(filePath, constants.F_OK);\n\n // Use DuckDB's read_json_auto to infer types and create table\n console.log(\n `[json-adapter] Inferring schema for ${tableName} from JSON file`,\n );\n await connection.run(\n `CREATE TABLE \"${tableName}\" AS SELECT * FROM read_json_auto('${escapeStringLiteral(filePath)}')`,\n );\n\n // Remove from pending files if it was deferred\n pendingJSONFiles.delete(tableName);\n\n console.log(\n `[json-adapter] Created ${tableName} with inferred schema (no UNIQUE constraints)`,\n );\n } catch (error) {\n throw new DatabaseError(\n `Failed to infer schema from JSON for table ${tableName}`,\n {\n tableName,\n jsonPath: filePath,\n originalError:\n error instanceof Error ? error.message : String(error),\n },\n );\n }\n };\n\n /**\n * Creates a table-specific interface for simplified table operations\n *\n * @param tableName - Table name\n * @returns TableInterface for the specified table\n */\n const table = (tableName: string): TableInterface => ({\n insert: (data) => insert(tableName, data),\n get: (where) => get(tableName, where),\n list: (where) => list(tableName, where),\n });\n\n /**\n * Parses a tagged template literal into a SQL query and values\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Object with SQL query and values array\n */\n const parseTemplate = (strings: TemplateStringsArray, ...vars: any[]) => {\n let sql = strings[0];\n const values = [];\n for (let i = 0; i < vars.length; i++) {\n values.push(vars[i]);\n sql += `$${i + 1}${strings[i + 1]}`;\n }\n return { sql, values };\n };\n\n /**\n * Executes a SQL query using template literals and returns multiple rows\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to array of result records\n * @throws Error if the query fails\n */\n const many = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<Record<string, any>[]> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const reader = await connection.runAndReadAll(sql, values);\n return convertBigInts(reader.getRowObjects());\n } catch (e) {\n throw new DatabaseError('Failed to execute many query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals and returns a single row\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single result record or null\n * @throws Error if the query fails\n */\n const single = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<Record<string, any> | null> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const reader = await connection.runAndReadAll(sql, values);\n const rows = reader.getRowObjects();\n return rows[0] ? convertBigInts(rows[0]) : null;\n } catch (e) {\n throw new DatabaseError('Failed to execute single query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals and returns a single value\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single value (first column of first row)\n * @throws Error if the query fails\n */\n const pluck = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<any> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n const reader = await connection.runAndReadAll(sql, values);\n const rows = reader.getRowObjects();\n if (rows.length === 0) return null;\n const firstRow = rows[0];\n const firstKey = Object.keys(firstRow)[0];\n return convertBigInts(firstRow[firstKey]);\n } catch (e) {\n throw new DatabaseError('Failed to execute pluck query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a SQL query using template literals without returning results\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise that resolves when the query completes\n * @throws Error if the query fails\n */\n const execute = async (\n strings: TemplateStringsArray,\n ...vars: any[]\n ): Promise<void> => {\n const { sql, values } = parseTemplate(strings, ...vars);\n try {\n await connection.run(sql, values);\n\n // Detect CREATE TABLE statements\n const createTableMatch = sql.match(\n /CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?[\"']?(\\w+)[\"']?/i,\n );\n if (createTableMatch) {\n const tableName = createTableMatch[1];\n\n // Check if there's pending JSON data for this table (deferred loading)\n const pendingFile = pendingJSONFiles.get(tableName);\n if (pendingFile) {\n // Table was just created by DDL - now load the JSON data\n await loadJSONData(\n connection,\n tableName,\n pendingFile,\n reportLoadError,\n );\n pendingJSONFiles.delete(tableName);\n } else if (writeStrategy !== 'none') {\n // No pending data - export empty table (preserves SMRT system tables)\n try {\n const { access } = await import('node:fs/promises');\n const jsonFilePath = join(url, `${tableName}.json`);\n try {\n await access(jsonFilePath);\n // File exists, don't overwrite\n } catch {\n // File doesn't exist, export empty table\n await persistTableToJSON(tableName);\n }\n } catch (error) {\n console.warn(\n `[json-adapter] Could not export newly created table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n } catch (e) {\n throw new DatabaseError('Failed to execute query', {\n sql,\n values,\n originalError: formatDbError(e),\n });\n }\n };\n\n /**\n * Executes a raw SQL query with parameterized values\n *\n * @param str - SQL query string\n * @param values - Variables to use as parameters\n * @returns Promise resolving to query result with rows and metadata\n * @throws Error if the query fails\n */\n const query = async (str: string, ...values: any[]) => {\n const sql = str;\n const args = Array.isArray(values[0]) ? values[0] : values;\n\n try {\n const reader = await connection.runAndReadAll(sql, args);\n const rows = convertBigInts(reader.getRowObjects());\n\n return {\n command: sql.split(' ')[0].toUpperCase(),\n rowCount: rows.length,\n oid: null,\n fields:\n rows.length > 0\n ? Object.keys(rows[0]).map((name) => ({\n name,\n tableID: 0,\n columnID: 0,\n dataTypeID: 0,\n dataTypeSize: -1,\n dataTypeModifier: -1,\n format: 'text',\n }))\n : [],\n rows,\n };\n } catch (e) {\n throw wrapDatabaseError('Failed to execute raw query', e, {\n sql,\n args,\n });\n }\n };\n\n // Shorthand aliases for query methods\n const oo = many; // (o)bjective-(o)bjects: returns multiple rows\n const oO = single; // (o)bjective-(O)bject: returns a single row\n const ox = pluck; // (o)bjective-(x): returns a single value\n const xx = execute; // e(x)ecute-e(x)ecute: executes without returning\n\n /**\n * Synchronizes database schema with provided SQL DDL\n *\n * Filters out CREATE TRIGGER statements since triggers are not supported\n * in the JSON adapter (timestamps managed at application level).\n *\n * @param schema - SQL schema definition with CREATE TABLE statements\n * @returns Promise that resolves when schema is synchronized\n */\n const syncSchema = async (schema: string): Promise<void> => {\n const statements = schema\n .trim()\n .split(';')\n .filter((stmt) => stmt.trim() !== '');\n\n const createTableStatements: string[] = [];\n const indexStatements: string[] = [];\n const otherStatements: string[] = [];\n\n // Separate CREATE TABLE, CREATE INDEX, and other statements\n for (const stmt of statements) {\n const trimmed = stmt.trim();\n if (!trimmed) continue;\n\n const upperTrimmed = trimmed.toUpperCase();\n\n // Skip trigger creation - not supported in JSON adapter\n if (upperTrimmed.startsWith('CREATE TRIGGER')) {\n console.warn(\n '[json-adapter] Skipping trigger creation - timestamps managed at application level',\n );\n continue;\n }\n\n if (\n upperTrimmed.startsWith('CREATE INDEX') ||\n upperTrimmed.startsWith('CREATE UNIQUE INDEX')\n ) {\n indexStatements.push(trimmed);\n } else if (upperTrimmed.startsWith('CREATE TABLE')) {\n createTableStatements.push(trimmed);\n } else {\n otherStatements.push(trimmed);\n }\n }\n\n // Process each CREATE TABLE statement\n for (const ddl of createTableStatements) {\n // Transform UNIQUE indexes to inline constraints for DuckDB compatibility\n const { ddl: transformedDDL, indexes: remainingIndexes } =\n convertUniqueIndexesToInlineConstraints(ddl, indexStatements);\n\n // Extract table name from DDL before executing\n const createTableMatch = transformedDDL.match(\n /CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?[\"']?(\\w+)[\"']?/i,\n );\n const tableName = createTableMatch ? createTableMatch[1] : null;\n\n // If table was created with inferred schema, drop it first\n // (proper schema with UNIQUE constraints takes precedence)\n if (tableName && inferredSchemaTables.has(tableName)) {\n try {\n await connection.run(`DROP TABLE IF EXISTS \"${tableName}\"`);\n inferredSchemaTables.delete(tableName);\n console.log(\n `[json-adapter] Dropped inferred-schema table ${tableName} for schema upgrade`,\n );\n } catch (error) {\n console.warn(\n `[json-adapter] Could not drop inferred-schema table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n // Execute transformed DDL\n try {\n await connection.run(transformedDDL);\n\n if (tableName) {\n // Check if there's pending JSON data for this table (deferred loading)\n const pendingFile = pendingJSONFiles.get(tableName);\n if (pendingFile) {\n // Table was just created by DDL - now load the JSON data.\n // loadJSONData logs an unloadable file rather than throwing, so a\n // single bad table does not abort the rest of this schema sync\n await loadJSONData(\n connection,\n tableName,\n pendingFile,\n reportLoadError,\n );\n pendingJSONFiles.delete(tableName);\n } else if (writeStrategy !== 'none') {\n // No pending data - export empty table (preserves SMRT system tables)\n try {\n // Only export if JSON file doesn't already exist\n // This prevents overwriting existing data when tables are manually created\n const { access } = await import('node:fs/promises');\n const jsonFilePath = join(url, `${tableName}.json`);\n try {\n await access(jsonFilePath);\n // File exists, don't overwrite\n } catch {\n // File doesn't exist, export empty table\n await persistTableToJSON(tableName);\n }\n } catch (error) {\n console.warn(\n `[json-adapter] Could not export newly created table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n } catch (e) {\n console.error('Schema sync error (CREATE TABLE):', e);\n }\n\n // Execute remaining (non-UNIQUE) indexes for this table\n for (const indexSQL of remainingIndexes) {\n try {\n await connection.run(indexSQL);\n } catch (e) {\n console.error('Schema sync error (CREATE INDEX):', e);\n }\n }\n }\n\n // Execute other statements\n for (const stmt of otherStatements) {\n try {\n await connection.run(stmt);\n } catch (e) {\n console.error('Schema sync error:', e);\n }\n }\n };\n\n /**\n * Initialize database schemas from JSON manifest\n * Supports dependency resolution and schema overrides\n *\n * @param options - Schema initialization options\n * @returns Promise that resolves when schemas are initialized\n */\n const initializeSchemas = async (\n options: SchemaInitializationOptions,\n ): Promise<void> => {\n const schemaManager = new DatabaseSchemaManager();\n const currentDb: DatabaseInterface = {\n url,\n client: connection,\n query,\n insert,\n update,\n upsert,\n get,\n list,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n tableExists,\n many,\n single,\n pluck,\n execute,\n oo,\n oO,\n ox,\n xx,\n syncSchema,\n };\n\n await schemaManager.initializeSchemas(currentDb, options);\n };\n\n /**\n * Executes a callback within a database transaction\n * Automatically commits on success or rolls back on error\n *\n * @param callback - Function to execute within transaction\n * @returns Promise resolving to callback result\n */\n const transaction = async <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ): Promise<T> =>\n // Held across the whole BEGIN … COMMIT/ROLLBACK span. Without it two\n // overlapping calls raced on the one connection: the second BEGIN threw,\n // its catch ran ROLLBACK, and that rollback ended the *first* transaction\n // — half of its writes durable, half lost, and its promise rejected, so\n // the caller was told nothing had happened.\n connectionLock.run(async () => {\n try {\n await connection.run('BEGIN TRANSACTION');\n\n const result = await callback(createTransactionScope());\n await connection.run('COMMIT');\n return result;\n } catch (error) {\n // A failing ROLLBACK must not replace the caller's error: DuckDB reports\n // \"cannot rollback - no transaction is active\" whenever the transaction\n // is already gone, which says nothing about what actually failed.\n try {\n await connection.run('ROLLBACK');\n } catch {\n // Nothing left to roll back.\n }\n throw error;\n }\n });\n\n /**\n * Builds the transaction-scoped interface handed to a transaction callback.\n *\n * The only difference from the top-level interface is `transaction`, which\n * refuses to nest. DuckDB has no SAVEPOINT, so there is no way to re-enter\n * the transaction already in progress; re-exposing the top-level\n * `transaction` (the previous behaviour) sent the nested call into a second\n * BEGIN on the same connection, which throws and then rolls back the\n * *enclosing* transaction, silently discarding its work.\n */\n const createTransactionScope = (): DatabaseInterface => {\n const txDb: DatabaseInterface = {\n url,\n client: connection,\n insert,\n get,\n list,\n update,\n upsert,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n transaction: async () => {\n throw new NestedTransactionError(\n 'Nested transactions are not supported by this adapter because its engine has no SAVEPOINT. Pass the existing transaction to the callee instead of opening a new one.',\n { adapter: 'json' },\n );\n },\n };\n\n return txDb;\n };\n\n /**\n * Begins a new transaction and returns a handle for manual control\n *\n * Unlike transaction(), this gives you explicit control over commit/rollback.\n * Ideal for test isolation where you want to rollback after each test.\n *\n * @returns Promise resolving to a TransactionHandle\n */\n const beginTransaction = async (): Promise<TransactionHandle> => {\n // The handle owns the connection until the caller ends it, so the lock is\n // held across the gap rather than around a single call. A handle that is\n // never committed or rolled back therefore blocks every later transaction\n // until the queue timeout reports it.\n const releaseConnection = await connectionLock.acquire();\n try {\n await connection.run('BEGIN TRANSACTION');\n } catch (error) {\n // No transaction was opened, so there is no handle to end it and nothing\n // left to release the connection.\n releaseConnection();\n throw error;\n }\n\n let active = true;\n\n // COMMIT and ROLLBACK can both throw, and the transaction is over either\n // way, so the connection goes back before the error is rethrown.\n const end = async (command: 'COMMIT' | 'ROLLBACK'): Promise<void> => {\n if (!active) {\n throw new DatabaseError('Transaction already ended', {});\n }\n try {\n await connection.run(command);\n } catch (error) {\n // COMMIT can fail and leave the transaction *open* — SQLite documents\n // exactly that for SQLITE_BUSY. Releasing the connection then would hand\n // the next queued caller a connection still inside a transaction: its\n // BEGIN would throw, and its catch would ROLLBACK, discarding this\n // transaction's work. So normalize before releasing, the way the pooled\n // adapter's discardTxClient does.\n try {\n await connection.run('ROLLBACK');\n } catch {\n // Already gone; nothing left to normalize.\n }\n throw error;\n } finally {\n active = false;\n releaseConnection();\n }\n };\n\n const commit = (): Promise<void> => end('COMMIT');\n\n const rollback = (): Promise<void> => end('ROLLBACK');\n\n const isActive = (): boolean => active;\n\n // Create a transaction-scoped database interface with commit/rollback.\n // The handle is inside a transaction for the same reason a callback\n // scope is, so it gets the same non-nesting `transaction`.\n const txHandle: TransactionHandle = {\n ...createTransactionScope(),\n commit,\n rollback,\n isActive,\n };\n\n return txHandle;\n };\n\n const closeResources = createDuckDBResourceCloser(connection, instance);\n db = {\n url,\n requiresSchemaCheck: true,\n client: connection,\n query,\n insert,\n update,\n upsert,\n get,\n list,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n tableExists,\n many,\n single,\n pluck,\n execute,\n oo,\n oO,\n ox,\n xx,\n syncSchema,\n initializeSchemas,\n transaction,\n beginTransaction,\n // JSON-specific methods\n exportTable,\n inferSchemaFromJSON,\n getTableLoadErrors,\n close: async () => {\n const state = getJSONSourceCacheState(db);\n if (state) {\n state.closing = true;\n await waitForJSONSourceWrites(state);\n }\n memoryConnectionCache.forget(db);\n await closeResources();\n },\n } as DatabaseInterface & {\n exportTable: (table: string) => Promise<void>;\n inferSchemaFromJSON: (\n tableName: string,\n jsonPath?: string,\n ) => Promise<void>;\n getTableLoadErrors: () => JSONTableLoadError[];\n };\n return db;\n })();\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport { webcrypto } from 'node:crypto';\nimport { DatabaseError } from '@happyvertical/utils';\nimport {\n DatabaseError as PgDatabaseError,\n type QueryResult as PgQueryResult,\n Pool,\n type PoolClient,\n} from 'pg';\nimport { DatabaseSchemaManager } from './schema-manager';\nimport {\n ConnectionCache,\n validateDatabaseCacheOptions,\n} from './shared/connection-cache';\nimport {\n createAlterTable,\n createGetTableSchema,\n createPostgresDialect,\n createTransactionErrorTracker,\n createVectorCapabilities,\n type NestedScope,\n normalizePostgresRawQuery,\n type PostgresQueryExecutor,\n} from './shared/postgres-dialect';\nimport type {\n DatabaseCacheOptions,\n DatabaseInterface,\n SchemaInitializationOptions,\n SessionHandle,\n TransactionHandle,\n} from './shared/types';\nimport { wrapDatabaseError } from './shared/utils';\n\nconst MAX_TIMER_DELAY_MILLIS = 2_147_483_647;\n\n/**\n * Configuration options for PostgreSQL database connections\n */\nexport interface PostgresOptions extends DatabaseCacheOptions {\n /**\n * Connection URL for PostgreSQL\n */\n url?: string;\n\n /**\n * Database name\n */\n database?: string;\n\n /**\n * Database server hostname\n */\n host?: string;\n\n /**\n * Username for authentication\n */\n user?: string;\n\n /**\n * Password for authentication\n */\n password?: string;\n\n /**\n * Port number for the PostgreSQL server\n */\n port?: number;\n\n /**\n * Opaque caller-owned cache identity for connection pooling.\n * If provided, the same dbid returns the same cached connection.\n * When omitted, a keyed digest is derived from effective connection and pool\n * options, including credentials, without retaining them in readable keys.\n */\n dbid?: string;\n\n /**\n * Maximum number of connections in the pool.\n * Defaults to 20. The pg library defaults to 10, which is too low\n * for applications with many SMRT collections.\n */\n max?: number;\n\n /**\n * Maximum time to wait for a pool connection, in milliseconds.\n * Must not exceed Node.js's maximum timer delay of 2,147,483,647.\n * Set to 0 to wait indefinitely. When omitted, pg's default is used.\n */\n connectionTimeoutMillis?: number;\n\n /**\n * Maximum time an idle client remains in the pool, in milliseconds.\n * Must not exceed Node.js's maximum timer delay of 2,147,483,647.\n * Set to 0 to retain idle clients. When omitted, pg's default is used.\n */\n idleTimeoutMillis?: number;\n\n /**\n * Schema definitions for tables.\n * Accepts a record or a lazy function (see SchemasOption).\n *\n * **Postgres ignores this option** — tables are managed by migrations.\n * The option exists so callers can pass schemas uniformly to all adapters\n * without knowing the adapter type. Only JSON/DuckDB adapters resolve it.\n */\n schemas?: import('./shared/types').SchemasOption;\n}\n\n/**\n * Module-level cache for PostgreSQL connections.\n * Keyed by dbid (or derived from connection URL).\n * Prevents creating multiple pg.Pool instances for the same database.\n */\ndeclare global {\n // A non-extractable process-local HMAC key keeps password-bearing cache\n // identities resistant to offline guessing even if cache keys are inspected.\n // eslint-disable-next-line no-var\n var __haveSqlPostgresCacheHmacKey: Promise<CryptoKey> | undefined;\n // eslint-disable-next-line no-var\n var __haveSqlPostgresConnectionCache:\n | ConnectionCache<DatabaseInterface>\n | undefined;\n}\n\nglobalThis.__haveSqlPostgresCacheHmacKey ??= webcrypto.subtle.generateKey(\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n) as Promise<CryptoKey>;\nglobalThis.__haveSqlPostgresConnectionCache ??=\n new ConnectionCache<DatabaseInterface>();\nconst connectionCache = globalThis.__haveSqlPostgresConnectionCache;\n\n/**\n * Clears the PostgreSQL connection cache.\n * Useful for test isolation and reconnection scenarios.\n */\nexport async function clearPostgresConnectionCache(): Promise<void> {\n await connectionCache.clear(closePostgresDatabase);\n}\n\nasync function closePostgresDatabase(db: DatabaseInterface): Promise<void> {\n if (db.close) {\n await db.close();\n return;\n }\n await db.client.end();\n}\n\ntype EffectivePostgresConfig = {\n url?: string;\n database?: string;\n host?: string;\n user?: string;\n password?: string;\n port: number;\n max: number;\n connectionTimeoutMillis?: number;\n idleTimeoutMillis?: number;\n};\n\nfunction resolvePostgresConfig(\n options: PostgresOptions,\n): EffectivePostgresConfig {\n const preferred = <T>(option: T | undefined, environment: T | undefined) =>\n option !== undefined ? option : environment;\n const envPort = process.env.HAVE_SQL_PORT;\n const primary = {\n url: preferred(options.url, process.env.HAVE_SQL_URL),\n database: preferred(options.database, process.env.HAVE_SQL_DATABASE),\n host: preferred(options.host, process.env.HAVE_SQL_HOST),\n user: preferred(options.user, process.env.HAVE_SQL_USER),\n password: preferred(options.password, process.env.HAVE_SQL_PASSWORD),\n port: preferred(\n options.port,\n envPort === undefined ? undefined : Number(envPort),\n ),\n };\n const useLegacy =\n !primary.url && !primary.host && !primary.database && !primary.user;\n const legacy = <T>(value: T | undefined, environment: T | undefined) =>\n value !== undefined || !useLegacy ? value : environment;\n const legacyPort = process.env.SQLOO_PORT;\n\n const port =\n legacy(\n primary.port,\n legacyPort === undefined ? undefined : Number(legacyPort),\n ) ?? 5432;\n const max = options.max ?? 20;\n const { connectionTimeoutMillis, idleTimeoutMillis } = options;\n if (!Number.isInteger(port) || port < 1 || port > 65_535) {\n throw new Error('PostgreSQL port must be an integer between 1 and 65535');\n }\n if (!Number.isInteger(max) || max < 1) {\n throw new Error('PostgreSQL pool max must be a positive integer');\n }\n if (\n connectionTimeoutMillis !== undefined &&\n (!Number.isInteger(connectionTimeoutMillis) ||\n connectionTimeoutMillis < 0 ||\n connectionTimeoutMillis > MAX_TIMER_DELAY_MILLIS)\n ) {\n throw new Error(\n `PostgreSQL pool connectionTimeoutMillis must be an integer between 0 and ${MAX_TIMER_DELAY_MILLIS}`,\n );\n }\n if (\n idleTimeoutMillis !== undefined &&\n (!Number.isInteger(idleTimeoutMillis) ||\n idleTimeoutMillis < 0 ||\n idleTimeoutMillis > MAX_TIMER_DELAY_MILLIS)\n ) {\n throw new Error(\n `PostgreSQL pool idleTimeoutMillis must be an integer between 0 and ${MAX_TIMER_DELAY_MILLIS}`,\n );\n }\n\n return {\n url: legacy(primary.url, process.env.SQLOO_URL),\n database: legacy(primary.database, process.env.SQLOO_DATABASE),\n host: legacy(primary.host, process.env.SQLOO_HOST),\n user: legacy(primary.user, process.env.SQLOO_USER),\n password: legacy(primary.password, process.env.SQLOO_PASSWORD),\n port,\n max,\n connectionTimeoutMillis,\n idleTimeoutMillis,\n };\n}\n\nasync function derivePostgresConnectionCacheKey(\n options: PostgresOptions,\n effective: EffectivePostgresConfig,\n): Promise<string | undefined> {\n if (options.dbid) {\n return options.dbid;\n }\n\n const identity = JSON.stringify({\n url: effective.url ?? null,\n host: effective.host ?? 'localhost',\n port: effective.port,\n database: effective.database ?? null,\n user: effective.user ?? null,\n password: effective.password ?? null,\n max: effective.max,\n connectionTimeoutMillis: effective.connectionTimeoutMillis ?? null,\n idleTimeoutMillis: effective.idleTimeoutMillis ?? null,\n });\n const hmacKey = await globalThis.__haveSqlPostgresCacheHmacKey;\n if (!hmacKey) {\n throw new Error('PostgreSQL cache identity key was not initialized');\n }\n const signature = await webcrypto.subtle.sign(\n 'HMAC',\n hmacKey,\n new TextEncoder().encode(\n `@happyvertical/sql:postgres-cache:v1\\0${identity}`,\n ),\n );\n return `pg:hmac-sha256:v1:${Buffer.from(signature).toString('hex')}`;\n}\n\n/** @internal Exported for adversarial identity tests. */\nexport async function getPostgresConnectionCacheKey(\n options: PostgresOptions,\n): Promise<string | undefined> {\n validateDatabaseCacheOptions(options);\n return derivePostgresConnectionCacheKey(\n options,\n resolvePostgresConfig(options),\n );\n}\n\n/**\n * Creates a PostgreSQL database adapter\n *\n * Loads configuration from environment variables with backward compatibility:\n * - First checks HAVE_SQL_* environment variables (new standard)\n * - Falls back to SQLOO_* environment variables (legacy)\n * - User-provided options always take precedence\n *\n * Environment variables:\n * - HAVE_SQL_URL / SQLOO_URL → Connection string (takes precedence)\n * - HAVE_SQL_DATABASE / SQLOO_DATABASE → Database name\n * - HAVE_SQL_HOST / SQLOO_HOST → Host (default: 'localhost')\n * - HAVE_SQL_USER / SQLOO_USER → Username\n * - HAVE_SQL_PASSWORD / SQLOO_PASSWORD → Password\n * - HAVE_SQL_PORT / SQLOO_PORT → Port (default: 5432)\n *\n * @param options - PostgreSQL connection options\n * @returns Database interface for PostgreSQL\n */\nexport async function getDatabase(\n options: PostgresOptions = {},\n): Promise<DatabaseInterface> {\n validateDatabaseCacheOptions(options);\n const effective = resolvePostgresConfig(options);\n const cacheKey = await derivePostgresConnectionCacheKey(options, effective);\n return connectionCache.getOrCreate(\n cacheKey,\n options,\n () => createDatabase(effective),\n closePostgresDatabase,\n );\n}\n\n/**\n * Internal: creates the actual database connection and caches it.\n */\nasync function createDatabase(\n config: EffectivePostgresConfig,\n): Promise<DatabaseInterface> {\n // Apply defaults\n const { database, host = 'localhost', user, password, port = 5432 } = config;\n\n // Construct url if not provided (for DatabaseInterface requirement)\n const url: string =\n (config.url as string) ||\n `postgresql://${user}${password ? `:${password}` : ''}@${host}:${port}/${database || 'postgres'}`;\n\n // Create a connection pool with explicit max to prevent exhaustion.\n // pg defaults to 10 which is too low for SMRT apps that sync 30+ table\n // schemas on startup. Default to 20 but allow callers to override.\n const { max: poolMax, connectionTimeoutMillis, idleTimeoutMillis } = config;\n const pool = new Pool(\n config.url\n ? {\n connectionString: config.url as string,\n max: poolMax,\n connectionTimeoutMillis,\n idleTimeoutMillis,\n }\n : {\n host: host as string,\n user: user as string,\n password: password as string,\n port: port as number,\n database: database as string,\n max: poolMax,\n connectionTimeoutMillis,\n idleTimeoutMillis,\n },\n );\n\n // An idle pooled client that loses its backend (restart, failover, proxy\n // idle timeout, TCP reset) emits 'error' on the pool. Without a listener Node\n // treats that as an unhandled 'error' event and terminates the process, so\n // routine database maintenance would crash every consumer of this adapter.\n // pg has already removed the client from the pool by this point; absorb the\n // event and let the pool carry on with its remaining connections.\n pool.on('error', (error) => {\n console.warn(\n 'Warning: PostgreSQL pool client error (connection discarded):',\n error instanceof Error ? error.message : String(error),\n );\n });\n\n // Wrap pool.end() to evict from cache so stale pools are never returned.\n // This fixes the \"Cannot use a pool after calling end on the pool\" error\n // when tests or shutdown code calls db.client.end() and subsequent calls\n // to getDatabase() would otherwise return the ended pool.\n const originalEnd = pool.end.bind(pool);\n let db: DatabaseInterface;\n let endPromise: Promise<void> | undefined;\n const evictFromCache = () => connectionCache.forget(db);\n (pool as any).end = (callback?: (error?: Error) => void) => {\n if (!endPromise) {\n evictFromCache();\n endPromise = originalEnd();\n }\n if (callback) {\n endPromise.then(\n () => callback(),\n (error) =>\n callback(error instanceof Error ? error : new Error(String(error))),\n );\n return;\n }\n return endPromise;\n };\n\n const client = pool;\n\n /**\n * Absorbs 'error' events for the clients currently checked out for a\n * transaction, keyed by client so the listener can be removed on release.\n */\n const txErrorAbsorbers = new WeakMap<PoolClient, () => void>();\n const releasedTxClients = new WeakSet<PoolClient>();\n\n /**\n * Checks out a pooled client for a transaction.\n *\n * pg-pool drops its own idle 'error' listener while a client is checked out,\n * so a backend that dies mid-transaction would emit an unhandled 'error' and\n * terminate the process. Absorb it here — the failure still reaches the\n * caller as a rejected query — and drop the listener again on release so\n * repeated checkouts of the same client cannot accumulate them.\n */\n const acquireTxClient = async (): Promise<PoolClient> => {\n const txClient = await client.connect();\n // The pool hands back the same client objects, so the release guard has to\n // be reset per checkout — otherwise every reuse after the first would be\n // treated as already released and stay checked out forever.\n releasedTxClients.delete(txClient);\n const absorb = () => {};\n txClient.on('error', absorb);\n txErrorAbsorbers.set(txClient, absorb);\n return txClient;\n };\n\n /**\n * Returns a transaction client to the pool exactly once.\n *\n * Every teardown path must run this, including the ones reached by a throwing\n * COMMIT or ROLLBACK — a client that is never released stays checked out for\n * the life of the process, and enough of them exhaust the pool. Releasing\n * twice throws, so the released clients are tracked rather than assumed.\n *\n * @param txClient - The pooled client backing a transaction\n * @param destroy - Destroy the connection instead of reusing it, for when the\n * transaction state could not be cleaned up\n */\n const releaseTxClient = (txClient: PoolClient, destroy = false): void => {\n if (releasedTxClients.has(txClient)) return;\n releasedTxClients.add(txClient);\n const absorb = txErrorAbsorbers.get(txClient);\n if (absorb) {\n txClient.off('error', absorb);\n txErrorAbsorbers.delete(txClient);\n }\n txClient.release(destroy || undefined);\n };\n\n /**\n * Rolls back a failed transaction and returns its client to the pool.\n *\n * The connection may still be inside a transaction, so ROLLBACK is attempted\n * to normalize it. If that works the connection is clean and worth reusing;\n * if it does not, the state is unknown and the connection is destroyed rather\n * than handed to unrelated code later.\n *\n * @returns The rollback failure, if the connection could not be normalized\n */\n const discardTxClient = async (txClient: PoolClient): Promise<unknown> => {\n try {\n await txClient.query('ROLLBACK');\n releaseTxClient(txClient);\n return undefined;\n } catch (rollbackError) {\n releaseTxClient(txClient, true);\n return rollbackError;\n }\n };\n\n /**\n * Keeps the first PostgreSQL statement failure attached to a transaction.\n *\n * PostgreSQL reports every later statement in an aborted transaction as\n * 25P02. Replacing the first failure with that generic state error hides the\n * actionable code and diagnostics from callers. The scoped executor instead\n * rethrows the first failure whenever PostgreSQL reports 25P02, and exposes\n * the recorded failure so a swallowed statement error cannot make COMMIT's\n * implicit rollback look successful.\n */\n const createTransactionExecutor = (txClient: PoolClient) => {\n const tracker = createTransactionErrorTracker(\n (error) => error instanceof PgDatabaseError,\n );\n const { pending, preserve: preserveFirstError } = tracker;\n\n const executor: PostgresQueryExecutor = {\n query: (sql, values) => tracker.track(txClient.query(sql, values), sql),\n };\n\n const client = new Proxy(txClient, {\n get(target, property) {\n if (property === 'query') {\n return (...args: any[]) => {\n const query = args[0];\n const callbackIndex =\n typeof args.at(-1) === 'function' ? args.length - 1 : -1;\n const submittable =\n typeof query === 'object' &&\n query !== null &&\n 'submit' in query &&\n typeof query.submit === 'function';\n\n if (submittable) {\n if (\n !('handleError' in query) ||\n typeof query.handleError !== 'function' ||\n !('handleReadyForQuery' in query) ||\n typeof query.handleReadyForQuery !== 'function'\n ) {\n return Reflect.apply(target.query, target, args);\n }\n\n const handleError = query.handleError;\n const handleReadyForQuery = query.handleReadyForQuery;\n let complete!: () => void;\n const completion = new Promise<void>((resolve) => {\n complete = resolve;\n });\n let completed = false;\n const finish = () => {\n if (completed) return;\n completed = true;\n query.handleError = handleError;\n query.handleReadyForQuery = handleReadyForQuery;\n pending.delete(completion);\n complete();\n };\n pending.add(completion);\n query.handleError = function (...handlerArgs: unknown[]) {\n const error = preserveFirstError(handlerArgs[0]);\n try {\n return Reflect.apply(handleError, this, [\n error,\n ...handlerArgs.slice(1),\n ]);\n } finally {\n finish();\n }\n };\n query.handleReadyForQuery = function (...handlerArgs: unknown[]) {\n try {\n const result = Reflect.apply(\n handleReadyForQuery,\n this,\n handlerArgs,\n );\n tracker.confirm(query);\n return result;\n } finally {\n finish();\n }\n };\n try {\n return Reflect.apply(target.query, target, args);\n } catch (error) {\n finish();\n throw preserveFirstError(error);\n }\n }\n\n if (callbackIndex !== -1) {\n const callback = args[callbackIndex];\n let complete!: () => void;\n const completion = new Promise<void>((resolve) => {\n complete = resolve;\n });\n pending.add(completion);\n args[callbackIndex] = (error: unknown, result: unknown) => {\n const callbackError =\n error === null || error === undefined\n ? error\n : preserveFirstError(error);\n if (callbackError === null || callbackError === undefined) {\n tracker.confirm(query);\n }\n pending.delete(completion);\n complete();\n callback(callbackError, result);\n };\n try {\n return Reflect.apply(target.query, target, args);\n } catch (error) {\n pending.delete(completion);\n complete();\n throw preserveFirstError(error);\n }\n }\n\n const result = Reflect.apply(target.query, target, args);\n if (\n typeof result === 'object' &&\n result !== null &&\n 'then' in result &&\n typeof result.then === 'function'\n ) {\n return tracker.track(Promise.resolve(result), query);\n }\n if (\n typeof result === 'object' &&\n result !== null &&\n 'once' in result &&\n typeof result.once === 'function'\n ) {\n let complete!: () => void;\n const completion = new Promise<void>((resolve) => {\n complete = resolve;\n });\n const finish = () => {\n pending.delete(completion);\n complete();\n };\n pending.add(completion);\n result.once('error', (error: unknown) => {\n preserveFirstError(error);\n finish();\n });\n result.once('end', () => {\n tracker.confirm(query);\n finish();\n });\n result.once('close', finish);\n }\n return result;\n };\n }\n return Reflect.get(target, property, target);\n },\n });\n\n return {\n client,\n executor,\n clearError: tracker.clearError,\n drain: tracker.drain,\n getError: tracker.getError,\n getUnconfirmedError: tracker.getUnconfirmedError,\n };\n };\n\n const assertCommitted = (\n result: PgQueryResult,\n transactionState?: ReturnType<typeof createTransactionExecutor>,\n ): void => {\n if (result.command === 'COMMIT') return;\n\n const queryError =\n transactionState?.getError() ?? transactionState?.getUnconfirmedError();\n if (queryError !== undefined) throw queryError;\n\n throw new DatabaseError(\n 'PostgreSQL rolled back the transaction at commit',\n {\n command: result.command,\n },\n );\n };\n\n const dialect = createPostgresDialect({\n client,\n // Pooled: a probe on its own connection cannot abort the caller's\n // transaction.\n probeExecutor: () => client,\n runInTransaction: async (work) => {\n const txClient = await acquireTxClient();\n\n try {\n await txClient.query('BEGIN');\n const result = await work(txClient);\n const commitResult = await txClient.query('COMMIT');\n assertCommitted(commitResult);\n releaseTxClient(txClient);\n return result;\n } catch (error) {\n const rollbackError = await discardTxClient(txClient);\n if (rollbackError !== undefined && error instanceof Error) {\n error.cause ??= rollbackError;\n }\n throw error;\n }\n },\n });\n const { serializeRecord, createClientMethods, createNestedTransaction } =\n dialect;\n\n const {\n insert,\n get,\n list,\n update,\n upsert,\n getOrInsert,\n delete: deleteRecords,\n count,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n } = createClientMethods(client, false, serializeRecord);\n const getTableSchema = createGetTableSchema({ many, tableExists });\n const alterTable = createAlterTable(client);\n\n /**\n * Acquire a pinned connection from the pool for session-scoped state such as\n * `pg_advisory_lock`. Every query on the returned handle runs on the same\n * client until {@link SessionHandle.release} drops the connection (which\n * frees any session locks it held). The connection is held for the handle's\n * lifetime, so hold one session per process, not per operation.\n */\n const acquireSession = async (): Promise<SessionHandle> => {\n const sessionClient = await pool.connect();\n let released = false;\n let lost = false;\n\n // pg-pool removes its idle 'error' listener when a client is checked out.\n // A session is pinned for the process lifetime and mostly idle between\n // queries, so a backend disconnect/failover would emit 'error' on a\n // listener-less client and crash the process. Absorb it and mark the\n // session dead instead; the next query()/isActive() surfaces it.\n sessionClient.on('error', () => {\n lost = true;\n });\n\n return {\n query: async (sql: string, ...values: any[]) => {\n if (released) {\n throw new DatabaseError('Session has been released', {});\n }\n if (lost) {\n throw new DatabaseError('Session connection was lost', {});\n }\n const normalized = normalizePostgresRawQuery(sql, values);\n try {\n const result = await sessionClient.query(\n normalized.sql,\n normalized.values,\n );\n return {\n rows: result.rows,\n rowCount: result.rowCount ?? 0,\n };\n } catch (e) {\n throw wrapDatabaseError('Failed to execute session query', e, {\n sql: normalized.sql,\n values: normalized.values,\n });\n }\n },\n isActive: () => !released && !lost,\n release: async () => {\n if (released) return;\n released = true;\n // Free session advisory locks before tearing down so the contract\n // \"after release() the locks are gone\" holds deterministically — the\n // destroy below is async (TCP teardown) and resolves later. Skip on a\n // lost connection (nothing to talk to; backend exit frees them anyway).\n if (!lost) {\n try {\n // A caller that left the connection in an aborted transaction\n // (BEGIN, a failed statement, then release) would make\n // pg_advisory_unlock_all() error with \"current transaction is\n // aborted\". Roll back first (a no-op when no transaction is open)\n // so the unlock runs and the deterministic-release contract holds.\n await sessionClient.query('ROLLBACK');\n } catch {\n // No open transaction, or already gone — proceed to unlock.\n }\n try {\n await sessionClient.query('SELECT pg_advisory_unlock_all()');\n } catch {\n // Best-effort; the destroy below frees locks at backend exit.\n }\n }\n // Destroy the physical connection rather than returning it to the pool.\n // Returning it would NOT clear residual session state, which could then\n // leak onto a connection later handed to unrelated code.\n sessionClient.release(true);\n },\n };\n };\n\n /**\n * Executes a callback within a database transaction\n * Automatically commits on success or rolls back on error\n *\n * @param callback - Function to execute within transaction\n * @returns Promise resolving to callback result\n */\n const transaction = async <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ): Promise<T> => {\n // Get a client from the pool for the transaction\n const txClient = await acquireTxClient();\n\n let nestedTransaction:\n | ReturnType<typeof createNestedTransaction>\n | undefined;\n const transactionState = createTransactionExecutor(txClient);\n try {\n await txClient.query('BEGIN');\n\n // Create a transaction-scoped database interface\n let txDb!: DatabaseInterface;\n nestedTransaction = createNestedTransaction(\n transactionState.executor,\n () => txDb,\n transactionState.clearError,\n new AsyncLocalStorage<NestedScope>(),\n );\n txDb = {\n url,\n client: transactionState.client,\n ...createClientMethods(\n transactionState.executor,\n true,\n serializeRecord,\n transactionState.clearError,\n ),\n transaction: nestedTransaction,\n };\n\n const result = await callback(txDb);\n await nestedTransaction.drain();\n await transactionState.drain();\n const statementError = transactionState.getError();\n if (statementError !== undefined) {\n throw statementError;\n }\n const commitResult = await txClient.query('COMMIT');\n assertCommitted(commitResult, transactionState);\n releaseTxClient(txClient);\n return result;\n } catch (error) {\n // Promise.all may reject before queued nested scopes finish. Do not\n // release this client until their savepoint work has drained.\n await nestedTransaction?.drain();\n await transactionState.drain();\n // Never let a failing ROLLBACK replace the caller's error. On a dead\n // connection the rollback throws \"Connection terminated unexpectedly\",\n // which would otherwise be the only thing the caller ever sees.\n const rollbackError = await discardTxClient(txClient);\n if (rollbackError !== undefined && error instanceof Error) {\n error.cause ??= rollbackError;\n }\n throw error;\n }\n };\n\n /**\n * Begins a new transaction and returns a handle for manual control\n *\n * Unlike transaction(), this gives you explicit control over commit/rollback.\n * Ideal for test isolation where you want to rollback after each test.\n *\n * @returns Promise resolving to a TransactionHandle\n */\n const beginTransaction = async (): Promise<TransactionHandle> => {\n // Get a client from the pool for the transaction\n const txClient = await acquireTxClient();\n try {\n await txClient.query('BEGIN');\n } catch (error) {\n // BEGIN failed, so there is no transaction for the caller to end and no\n // handle to release the client — return it here or it is stranded.\n releaseTxClient(txClient, true);\n throw error;\n }\n\n let active = true;\n let nestedTransaction:\n | ReturnType<typeof createNestedTransaction>\n | undefined;\n const transactionState = createTransactionExecutor(txClient);\n\n // COMMIT and ROLLBACK both throw in ordinary operation — deferred\n // constraint violations, serialization failures, a connection lost at\n // commit time. The transaction is over either way, so end it and return the\n // client before rethrowing; leaving it checked out would strand a pooled\n // connection permanently and eventually deadlock the pool.\n const end = async (command: 'COMMIT' | 'ROLLBACK'): Promise<void> => {\n if (!active) {\n throw new DatabaseError('Transaction already ended', {});\n }\n try {\n await nestedTransaction?.drain();\n await transactionState.drain();\n const statementError = transactionState.getError();\n if (command === 'COMMIT' && statementError !== undefined) {\n throw statementError;\n }\n const result = await txClient.query(command);\n if (command === 'COMMIT') assertCommitted(result, transactionState);\n active = false;\n releaseTxClient(txClient);\n } catch (error) {\n active = false;\n const rollbackError = await discardTxClient(txClient);\n if (rollbackError !== undefined && error instanceof Error) {\n error.cause ??= rollbackError;\n }\n throw error;\n }\n };\n\n const commit = (): Promise<void> => end('COMMIT');\n\n const rollback = (): Promise<void> => end('ROLLBACK');\n\n const isActive = (): boolean => active;\n\n // Create a transaction-scoped database interface with commit/rollback\n let txHandle!: TransactionHandle;\n nestedTransaction = createNestedTransaction(\n transactionState.executor,\n () => txHandle,\n transactionState.clearError,\n new AsyncLocalStorage<NestedScope>(),\n );\n txHandle = {\n url,\n client: transactionState.client,\n ...createClientMethods(\n transactionState.executor,\n true,\n serializeRecord,\n transactionState.clearError,\n ),\n transaction: nestedTransaction,\n commit,\n rollback,\n isActive,\n };\n\n return txHandle;\n };\n\n /**\n * Initialize database schemas from JSON manifest\n * Supports dependency resolution and schema overrides\n *\n * @param options - Schema initialization options\n * @returns Promise that resolves when schemas are initialized\n */\n const initializeSchemas = async (\n options: SchemaInitializationOptions,\n ): Promise<void> => {\n const schemaManager = new DatabaseSchemaManager();\n const currentDb: DatabaseInterface = {\n url,\n client,\n insert,\n update,\n upsert,\n get,\n getOrInsert,\n delete: deleteRecords,\n count,\n list,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n transaction,\n };\n\n await schemaManager.initializeSchemas(currentDb, options);\n };\n\n db = {\n url,\n client,\n insert,\n update,\n upsert,\n get,\n getOrInsert,\n delete: deleteRecords,\n count,\n list,\n table,\n many,\n single,\n pluck,\n execute,\n query,\n oo,\n oO,\n ox,\n xx,\n tableExists,\n syncSchema,\n initializeSchemas,\n transaction,\n beginTransaction,\n acquireSession,\n getTableSchema,\n alterTable,\n vector: createVectorCapabilities(pool),\n close: async () => {\n await pool.end();\n },\n };\n\n return db;\n}\n","import { loadEnvConfig } from '@happyvertical/utils';\nimport type { PGliteOptions } from './pglite';\nimport type { PostgresOptions } from './postgres';\nimport type {\n DatabaseInterface,\n DuckDBOptions,\n JSONOptions,\n} from './shared/types';\nimport type { SqliteOptions } from './sqlite';\n\n/**\n * Union type of options for creating different database types\n */\nexport type GetDatabaseOptions =\n | (PostgresOptions & { type?: 'postgres' })\n | (SqliteOptions & { type?: 'sqlite' })\n | (DuckDBOptions & { type?: 'duckdb' })\n | (PGliteOptions & { type?: 'pglite' })\n | JSONOptions;\n\n/**\n * Checks if the provided value is a database instance rather than configuration options\n *\n * @param value - Value to check\n * @returns True if the value appears to be a DatabaseInterface instance\n */\nfunction isDatabaseInstance(value: any): value is DatabaseInterface {\n return (\n value &&\n typeof value === 'object' &&\n typeof value.client !== 'undefined' &&\n typeof value.insert === 'function' &&\n typeof value.get === 'function' &&\n typeof value.query === 'function'\n );\n}\n\n/**\n * Creates a database connection based on the provided options, or returns an existing database instance\n *\n * Loads configuration from environment variables using the HAVE_SQL_* pattern:\n * - HAVE_SQL_TYPE → type ('sqlite' | 'postgres' | 'pglite' | 'duckdb' | 'json')\n * - HAVE_SQL_URL → url (connection string)\n * - HAVE_SQL_HOST → host (database server hostname)\n * - HAVE_SQL_PORT → port (database server port number)\n * - HAVE_SQL_DATABASE → database (database name)\n * - HAVE_SQL_USER → user (authentication username)\n * - HAVE_SQL_PASSWORD → password (authentication password)\n *\n * User-provided options always take precedence over environment variables.\n *\n * ## Lazy schemas\n *\n * The `schemas` option accepts either an eagerly-built record or a lazy\n * function (`() => Record<string, SchemaProvider>`). Adapters that manage\n * tables via migrations (Postgres, SQLite) never resolve the function, so\n * there is zero cost for callers that pass schemas uniformly regardless of\n * adapter type. Only JSON and DuckDB adapters call the function.\n *\n * ```typescript\n * // Caller doesn't need to know the adapter type:\n * await getDatabase({\n * ...config,\n * schemas: () => ObjectRegistry.getAllSchemas(),\n * });\n * ```\n *\n * @param options - Configuration options for the database connection or an existing database instance\n * @returns Promise resolving to a DatabaseInterface implementation\n * @throws Error if the database type is invalid\n */\nexport async function getDatabase(\n options: GetDatabaseOptions | DatabaseInterface = {},\n): Promise<DatabaseInterface> {\n // If a database instance is passed, return it directly\n if (isDatabaseInstance(options)) {\n return options;\n }\n\n // Load HAVE_SQL_* environment variables and merge into options\n // This ensures options object is mutated (needed for dbid propagation)\n Object.assign(\n options,\n loadEnvConfig(options as any, {\n packageName: 'sql',\n schema: {\n type: 'string',\n url: 'string',\n host: 'string',\n port: 'number',\n database: 'string',\n user: 'string',\n password: 'string',\n },\n }) as Partial<GetDatabaseOptions>,\n );\n\n // if no type but url starts with file:, set to sqlite\n if (\n !options.type &&\n (options.url?.startsWith('file:') || options.url === ':memory:')\n ) {\n options.type = 'sqlite';\n }\n\n if (options.type === 'postgres') {\n const postgres = await import('./postgres.js');\n return postgres.getDatabase(options as PostgresOptions);\n }\n if (options.type === 'pglite') {\n const pglite = await import('./pglite.js');\n return pglite.getDatabase(options as PGliteOptions);\n }\n if (options.type === 'sqlite') {\n const sqlite = await import('./sqlite.js');\n return sqlite.getDatabase(options as SqliteOptions);\n }\n if (options.type === 'duckdb') {\n const duckdb = await import('./duckdb.js');\n return duckdb.getDatabase(options as DuckDBOptions);\n }\n if (options.type === 'json') {\n const json = await import('./json.js');\n return json.getDatabase(options as JSONOptions);\n }\n throw new Error('Invalid database type');\n}\n\n/**\n * Validates if a table name consists only of alphanumeric characters and underscores\n *\n * @param name - Table name to validate\n * @returns Boolean indicating if the name is valid\n */\nfunction _isValidTableName(name: string): boolean {\n // Simple regex to allow only alphanumeric characters and underscores\n return /^[a-zA-Z0-9_]+$/.test(name);\n}\n\n/**\n * Synchronizes a SQL schema definition with a database\n * Creates tables if they don't exist and adds missing columns to existing tables\n *\n * @param options - Object containing database and schema\n * @param options.db - Database interface to use\n * @param options.schema - SQL schema definition\n * @throws Error if db or schema are missing or if the database doesn't support syncSchema\n */\nexport async function syncSchema(options: {\n db: DatabaseInterface;\n schema: string;\n}) {\n const { db, schema } = options;\n if (!db || !schema) {\n throw new Error('db and schema are required');\n }\n\n // Delegate to the database adapter's syncSchema implementation\n if (db.syncSchema) {\n await db.syncSchema(schema);\n } else {\n throw new Error('Database adapter does not support schema synchronization');\n }\n}\n\n/**\n * Checks if a table exists in the database\n *\n * @param db - Database interface to use\n * @param tableName - Name of the table to check\n * @returns Promise resolving to boolean indicating if the table exists\n */\nexport async function tableExists(db: DatabaseInterface, tableName: string) {\n return db.tableExists(tableName);\n}\n\n/**\n * Escapes and formats a value for use in SQL queries\n *\n * @param value - Value to escape\n * @returns String representation of the value safe for SQL use\n */\nexport function escapeSqlValue(value: any): string {\n if (value === null) {\n return 'NULL';\n }\n if (value instanceof Date) {\n return `'${value.toISOString()}'`;\n }\n if (typeof value === 'number') {\n return value.toString();\n }\n if (typeof value === 'boolean') {\n return value ? '1' : '0';\n }\n // Escape single quotes and wrap in quotes\n return `'${String(value).replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Validates a column name for use in SQL queries\n *\n * @param column - Column name to validate\n * @returns The validated column name\n * @throws Error if the column name contains invalid characters\n */\nexport function validateColumnName(column: string): string {\n // Reject non-strings before the regex. `test` coerces via `toString`, so an\n // object whose `toString` differs between this read and a later interpolation\n // read would pass validation and then reach SQL as a different identifier.\n // Only allow alphanumeric characters, underscores, and dots (for table.column notation)\n if (typeof column !== 'string' || !/^[a-zA-Z0-9_.]+$/.test(column)) {\n throw new Error(`Invalid column name: ${column}`);\n }\n return column;\n}\n\n// Import utilities from shared utils\nimport { buildWhere, formatDbError, raw } from './shared/utils';\n\nexport {\n type AggregateBuildResult,\n type AggregateFunction,\n type AggregateSelectExpr,\n type AggregateSpec,\n type AggregateTimeBucketUnit,\n bucketExpr,\n buildAggregate,\n} from './aggregate.js';\nexport type { RawSqlKey, SqlAdapterType } from './shared/utils';\nexport { buildWhere, formatDbError, raw };\n\n// Import DuckDB schema transformation utilities\nimport { convertUniqueIndexesToInlineConstraints } from './shared/duckdb-schema-utils';\n\n// Generic database integrity-check framework\nexport {\n checkExpectedTables,\n checkRelationship,\n checkUniqueColumn,\n type DoctorCheck,\n type DoctorContext,\n type DoctorIssue,\n type DoctorLevel,\n type DoctorResult,\n type RelationshipSpec,\n type RunDoctorOptions,\n runDoctor,\n type UniqueColumnSpec,\n} from './doctor';\n\n// Export adapter cache utilities (for testing)\nexport { clearConnectionCache } from './json.js';\n// PGlite (browser-capable Postgres). Types only: the adapter and its\n// `PGlitePeerMissingError` load from `@happyvertical/sql/pglite`, which keeps\n// the root entry free of the optional `@electric-sql/pglite` peer.\nexport type { PGliteLike, PGliteOptions } from './pglite';\nexport { clearPostgresConnectionCache } from './postgres.js';\n// Postgres CLI shell-outs and URL helpers\nexport {\n assertCanExportDatabase,\n assertCanImportDatabase,\n type CreateDropOptions,\n createPostgresDatabase,\n type DumpOptions,\n databaseNameFromUrl,\n dropPostgresDatabase,\n dumpPostgresDatabase,\n isLocalDatabaseUrl,\n type PostgresImportLocalityOptions,\n type PostgresLocalityOptions,\n postgresEnvFromUrl,\n type RestoreOptions,\n redactDatabaseUrl,\n restorePostgresDatabase,\n} from './postgres-cli';\nexport type { SchemaInitializationResult } from './schema-manager';\n// Export schema management\nexport { DatabaseSchemaManager } from './schema-manager';\nexport * from './shared/types';\nexport type { SecureSqliteFileOptions, SqliteOptions } from './sqlite.js';\nexport { convertUniqueIndexesToInlineConstraints };\n\nexport default {\n getDatabase,\n syncSchema,\n tableExists,\n buildWhere,\n raw,\n};\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"mappings":"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"}