@happyvertical/sql 0.105.12 → 0.106.1

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/doctor.ts","../src/json.ts","../src/postgres.ts","../src/index.ts"],"sourcesContent":["/**\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 { serializeDuckDBStatements } from './shared/duckdb-statement-queue';\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 // One connection serves every call on this handle; queue its statements.\n connection = serializeDuckDBStatements(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// Driver-independent helpers live in ./query (also `@happyvertical/sql/query`).\nimport { buildWhere, raw, syncSchema, tableExists } from './query.js';\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// 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 {\n type AggregateBuildResult,\n type AggregateFunction,\n type AggregateSelectExpr,\n type AggregateSpec,\n type AggregateTimeBucketUnit,\n bucketExpr,\n buildAggregate,\n buildWhere,\n convertUniqueIndexesToInlineConstraints,\n DatabaseSchemaManager,\n escapeSqlValue,\n formatDbError,\n type RawSqlKey,\n raw,\n type SchemaInitializationResult,\n type SqlAdapterType,\n syncSchema,\n tableExists,\n validateColumnName,\n} from './query.js';\nexport * from './shared/types';\nexport type { SecureSqliteFileOptions, SqliteOptions } from './sqlite.js';\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/doctor.ts","../src/json.ts","../src/postgres.ts","../src/index.ts"],"sourcesContent":["/**\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 { serializeDuckDBStatements } from './shared/duckdb-statement-queue';\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 // One connection serves every call on this handle; queue its statements.\n connection = serializeDuckDBStatements(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// Driver-independent helpers live in ./query (also `@happyvertical/sql/query`).\nimport { buildWhere, raw, syncSchema, tableExists } from './query.js';\n\nexport type {\n AggregateArithmeticOp,\n AggregateComparisonOp,\n AggregateExpression,\n AggregateJoin,\n AggregateLiteral,\n AggregateOperand,\n} from './aggregate-expression';\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// 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 {\n type AggregateBuildResult,\n type AggregateFunction,\n type AggregateSelectExpr,\n type AggregateSpec,\n type AggregateTimeBucketUnit,\n bucketExpr,\n buildAggregate,\n buildWhere,\n convertUniqueIndexesToInlineConstraints,\n DatabaseSchemaManager,\n escapeSqlValue,\n formatDbError,\n type RawSqlKey,\n raw,\n type SchemaInitializationResult,\n type SqlAdapterType,\n syncSchema,\n tableExists,\n validateColumnName,\n} from './query.js';\nexport * from './shared/types';\nexport type { SecureSqliteFileOptions, SqliteOptions } from './sqlite.js';\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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"}