@happyvertical/sql 0.102.2 → 0.102.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +1 -1
- package/README.md +99 -4
- package/dist/chunks/{duckdb-DfikVgov.js → duckdb-LMyfKZGi.js} +3 -3
- package/dist/chunks/{duckdb-DfikVgov.js.map → duckdb-LMyfKZGi.js.map} +1 -1
- package/dist/chunks/postgres-dialect-Bfswr4Vs.js +2728 -0
- package/dist/chunks/postgres-dialect-Bfswr4Vs.js.map +1 -0
- package/dist/chunks/{sqlite-Cz2NMiDs.js → sqlite-0PMv1va2.js} +5 -5
- package/dist/chunks/{sqlite-Cz2NMiDs.js.map → sqlite-0PMv1va2.js.map} +1 -1
- package/dist/chunks/{sqlite-connection-cache-BAakj2HO.js → sqlite-connection-cache-ChWav9iY.js} +2 -2
- package/dist/chunks/{sqlite-connection-cache-BAakj2HO.js.map → sqlite-connection-cache-ChWav9iY.js.map} +1 -1
- package/dist/chunks/{sqlite-native-ByGwGEbS.js → sqlite-native--z4BnolL.js} +4 -4
- package/dist/chunks/{sqlite-native-ByGwGEbS.js.map → sqlite-native--z4BnolL.js.map} +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +163 -1365
- package/dist/index.js.map +1 -1
- package/dist/pglite.d.ts +150 -0
- package/dist/pglite.d.ts.map +1 -0
- package/dist/pglite.js +339 -0
- package/dist/pglite.js.map +1 -0
- package/dist/postgres.d.ts.map +1 -1
- package/dist/shared/postgres-dialect.d.ts +153 -0
- package/dist/shared/postgres-dialect.d.ts.map +1 -0
- package/dist/shared/types.d.ts +4 -4
- package/metadata.json +1 -0
- package/package.json +13 -2
- package/dist/chunks/connection-cache-Da5UcY5u.js +0 -1305
- package/dist/chunks/connection-cache-Da5UcY5u.js.map +0 -1
- package/dist/chunks/types-CwWJ-yju.js +0 -139
- package/dist/chunks/types-CwWJ-yju.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"connection-cache-Da5UcY5u.js","names":["#connections","#pending","#evictions","#requests","#clearing","#failedClosures","#registerRequest","#throwIfCancelled","#throwIfFailedClosure","#waitForEviction","#clearError","#finishRequest","#clearKey","#cancelRequests"],"sources":["../../src/shared/utils.ts","../../src/schema-manager.ts","../../src/shared/alter-utils.ts","../../src/shared/connection-cache.ts"],"sourcesContent":["/**\n * Shared SQL utilities that work in both browser and Node.js environments\n */\n\nimport { DatabaseError } from '@happyvertical/utils';\nimport type { WhereClause } from './types.js';\n\n/**\n * Formats a database error to include its useful non-secret details.\n *\n * Database drivers often include additional properties beyond just `message`:\n * - PostgreSQL: code, detail, hint, severity\n * - DuckDB: may include additional context\n * - SQLite/LibSQL: may include errno, code\n *\n * This function extracts an allowlist of driver properties and redacts\n * credential-shaped text. When parameter values are available, prefer\n * {@link wrapDatabaseError}; it also redacts values echoed by the driver.\n *\n * @param error - The caught error object\n * @returns Formatted error string with all available error details\n */\nexport function formatDbError(error: unknown): string {\n return formatDbErrorWithSecrets(error, []);\n}\n\ntype DriverError = {\n name?: string;\n message?: string;\n code?: string;\n detail?: string;\n hint?: string;\n severity?: string;\n errno?: number;\n};\n\nconst DRIVER_ERROR_FIELDS = [\n 'code',\n 'detail',\n 'hint',\n 'severity',\n 'errno',\n] as const;\n\nconst SENSITIVE_CONTEXT_KEY =\n /^(?:args?|parameters?|params?|queries|query|sql|values?)$/i;\nconst CREDENTIAL_KEY_PART =\n /^(?:credential|credentials|passphrase|password|pwd|secret|token)$/i;\nconst CREDENTIAL_KEY_QUALIFIER =\n /^(?:access|api|auth|client|private|refresh|service|signing)$/i;\nconst DATABASE_USER_KEY =\n /^(?:user(?:name)?|(?:database|db|pg|postgres|postgresql)[._-]?user(?:name)?)$/i;\nconst DATABASE_URL_USERINFO =\n /\\b((?:https?|libsql|postgres|postgresql):\\/\\/)[^\\s/@]+@/giu;\nconst DATABASE_URL =\n /\\b(?:https?|libsql|postgres|postgresql):\\/\\/[^\\s\"'`<>()]+/giu;\nconst CREDENTIAL_ASSIGNMENT =\n /(?<![\\p{L}\\p{N}_-])([\"']?)([\\p{L}\\p{N}_.-]+(?: key)?)\\1(\\s*[:=]\\s*)(?:\"(?:\\\\.|[^\"])*\"|'(?:\\\\.|[^'])*'|[^\\s,;}\\]]+)/giu;\nconst BEARER_CREDENTIAL = /\\bbearer\\s+[^\\s,;]+/giu;\nconst SQL_NUMERIC_LITERAL =\n /(?<![\\p{L}\\p{N}_$])[-+]?(?:\\d(?:_?\\d)*(?:\\.(?:\\d(?:_?\\d)*)?)?|\\.\\d(?:_?\\d)*)(?:e[-+]?\\d(?:_?\\d)*)?(?![\\p{L}\\p{N}_$])/giu;\nconst POSTGRES_KEY_VALUE =\n /(\\bKey\\s*\\([^\\r\\n)]*\\)\\s*=\\s*\\()([\\s\\S]*?)(\\)\\s*(?:already exists|is duplicated)\\b)/giu;\nconst POSTGRES_FAILING_ROW =\n /(\\bFailing row contains\\s*\\()([\\s\\S]*)(\\)(?:\\.|$))/giu;\nconst CAST_INPUT_VALUE =\n /(\\bwith value\\s+)([\\s\\S]*?)(\\s+(?:can't|cannot)\\s+be cast\\b)/giu;\n\nfunction isCredentialKey(key: string): boolean {\n if (DATABASE_USER_KEY.test(key)) return true;\n\n const parts = key\n .replace(/([\\p{Ll}\\p{N}])(\\p{Lu})/gu, '$1 $2')\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n if (parts.length === 1 && /^(?:auth|credential|key|pass)$/i.test(parts[0])) {\n return true;\n }\n if (parts.some((part) => CREDENTIAL_KEY_PART.test(part))) return true;\n\n return parts.some(\n (part, index) =>\n /^(?:key|auth)$/i.test(part) &&\n index > 0 &&\n CREDENTIAL_KEY_QUALIFIER.test(parts[index - 1]),\n );\n}\n\nfunction toDriverError(error: unknown): DriverError {\n return error && (typeof error === 'object' || typeof error === 'function')\n ? (error as DriverError)\n : {};\n}\n\nfunction redactUrl(value: string): string {\n try {\n const url = new URL(value);\n if (url.username) url.username = '[redacted]';\n if (url.password) url.password = '[redacted]';\n for (const key of url.searchParams.keys()) {\n if (isCredentialKey(key)) {\n url.searchParams.set(key, '[redacted]');\n }\n }\n if (url.hash) url.hash = '[redacted]';\n return url.toString();\n } catch {\n return value.replace(/\\/\\/[^/@\\s]+@/u, '//[redacted]@');\n }\n}\n\nfunction redactKnownValue(input: string, value: string): string {\n if (!value || !input.includes(value)) return input;\n if (!/^[\\p{L}\\p{N}_-]+$/u.test(value)) {\n return input.split(value).join('[redacted]');\n }\n\n const identifierCharacter = /^[\\p{L}\\p{N}_-]$/u;\n let cursor = 0;\n let redacted = '';\n let match = input.indexOf(value);\n while (match !== -1) {\n const end = match + value.length;\n const before = [...input.slice(Math.max(0, match - 2), match)].at(-1);\n const afterCodePoint = input.codePointAt(end);\n const after =\n afterCodePoint === undefined\n ? undefined\n : String.fromCodePoint(afterCodePoint);\n const bounded =\n (!before || !identifierCharacter.test(before)) &&\n (!after || !identifierCharacter.test(after));\n\n redacted += input.slice(cursor, match);\n redacted += bounded ? '[redacted]' : value;\n cursor = end;\n match = input.indexOf(value, cursor);\n }\n\n return redacted + input.slice(cursor);\n}\n\nfunction redactDatabaseErrorText(\n value: string,\n knownSecrets: readonly string[],\n): string {\n let redacted = value\n .replace(DATABASE_URL_USERINFO, '$1[redacted]@')\n .replace(DATABASE_URL, redactUrl)\n .replace(\n CREDENTIAL_ASSIGNMENT,\n (assignment, quote: string, key: string, delimiter: string) =>\n isCredentialKey(key)\n ? `${quote}${key}${quote}${delimiter}[redacted]`\n : assignment,\n )\n .replace(BEARER_CREDENTIAL, 'Bearer [redacted]')\n .replace(\n POSTGRES_KEY_VALUE,\n (_match, prefix: string, _value: string, suffix: string) =>\n `${prefix}[redacted]${suffix}`,\n )\n .replace(\n POSTGRES_FAILING_ROW,\n (_match, prefix: string, _value: string, suffix: string) =>\n `${prefix}[redacted]${suffix}`,\n )\n .replace(\n CAST_INPUT_VALUE,\n (_match, prefix: string, _value: string, suffix: string) =>\n `${prefix}[redacted]${suffix}`,\n );\n\n for (const secret of [...new Set(knownSecrets)].sort(\n (left, right) => right.length - left.length,\n )) {\n redacted = redactKnownValue(redacted, secret);\n }\n\n return redacted;\n}\n\nfunction collectSensitiveValues(\n value: unknown,\n output: Set<string>,\n seen: Set<object>,\n): void {\n if (typeof value === 'string') {\n if (value) output.add(value);\n return;\n }\n if (\n typeof value === 'number' ||\n typeof value === 'bigint' ||\n typeof value === 'boolean'\n ) {\n output.add(String(value));\n return;\n }\n if (!value || typeof value !== 'object' || seen.has(value)) return;\n seen.add(value);\n\n try {\n if (value instanceof Date) {\n output.add(value.toISOString());\n return;\n }\n\n if (ArrayBuffer.isView(value)) {\n const bytes = new Uint8Array(\n value.buffer,\n value.byteOffset,\n value.byteLength,\n );\n if (bytes.length > 0) {\n const hex = [...bytes]\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('');\n output.add([...bytes].join(','));\n output.add(hex);\n output.add(`\\\\x${hex}`);\n output.add(`\\\\x${hex.toUpperCase()}`);\n output.add(\n `<Buffer ${[...bytes]\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join(' ')}>`,\n );\n if (typeof Buffer !== 'undefined') {\n output.add(Buffer.from(bytes).toString('base64'));\n }\n }\n return;\n }\n } catch {\n return;\n }\n\n try {\n const serialized = JSON.stringify(value);\n if (serialized && serialized !== '{}' && serialized !== '[]') {\n output.add(serialized);\n }\n } catch {\n // Cyclic bind values are still traversed below.\n }\n\n let descriptors: Record<string, PropertyDescriptor>;\n try {\n descriptors = Object.getOwnPropertyDescriptors(value);\n } catch {\n // A proxy trap must not replace the database failure being reported.\n return;\n }\n\n for (const [key, descriptor] of Object.entries(descriptors)) {\n if (key !== 'length' && 'value' in descriptor) {\n collectSensitiveValues(descriptor.value, output, seen);\n }\n }\n}\n\nfunction collectSqlLiterals(sql: string, output: Set<string>): void {\n // Some drivers include the complete statement in their diagnostics. Treat\n // an exact echo as sensitive even when it contains no parseable literals.\n if (sql) output.add(sql);\n\n for (const match of sql.matchAll(/'((?:''|[^'])*)'/gu)) {\n const literal = match[1].replaceAll(\"''\", \"'\");\n if (literal) output.add(literal);\n }\n\n for (const match of sql.matchAll(/\\$\\$([\\s\\S]*?)\\$\\$/gu)) {\n if (match[1]) output.add(match[1]);\n }\n\n for (const match of sql.matchAll(\n /\\$([A-Za-z_][A-Za-z0-9_]*)\\$([\\s\\S]*?)\\$\\1\\$/gu,\n )) {\n if (match[2]) output.add(match[2]);\n }\n\n for (const match of sql.matchAll(SQL_NUMERIC_LITERAL)) {\n output.add(match[0]);\n output.add(match[0].replaceAll('_', ''));\n }\n}\n\nfunction contextSecrets(context: Record<string, unknown>): string[] {\n const output = new Set<string>();\n for (const [key, value] of Object.entries(context)) {\n if (/^(?:args?|parameters?|params?|values?)$/i.test(key)) {\n collectSensitiveValues(value, output, new Set());\n } else if (\n /^(?:queries|query|sql)$/i.test(key) &&\n typeof value === 'string'\n ) {\n collectSqlLiterals(value, output);\n }\n }\n return [...output];\n}\n\nfunction sanitizeContextValue(\n value: unknown,\n knownSecrets: readonly string[],\n seen: WeakSet<object>,\n): unknown {\n if (typeof value === 'string') {\n return redactDatabaseErrorText(value, knownSecrets);\n }\n if (!value || typeof value !== 'object') return value;\n if (seen.has(value)) return '[circular]';\n seen.add(value);\n\n if (Array.isArray(value)) {\n return value.map((entry) =>\n sanitizeContextValue(entry, knownSecrets, seen),\n );\n }\n\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).map(([key, entry]) => [\n key,\n isCredentialKey(key)\n ? '[redacted]'\n : sanitizeContextValue(entry, knownSecrets, seen),\n ]),\n );\n}\n\nfunction sanitizeDatabaseErrorContext(\n context: Record<string, unknown>,\n knownSecrets: readonly string[],\n): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(context).map(([key, value]) => [\n key,\n SENSITIVE_CONTEXT_KEY.test(key) || isCredentialKey(key)\n ? '[redacted]'\n : sanitizeContextValue(value, knownSecrets, new WeakSet()),\n ]),\n );\n}\n\nfunction formatDbErrorWithSecrets(\n error: unknown,\n knownSecrets: readonly string[],\n): string {\n const dbError = toDriverError(error);\n\n const parts: string[] = [];\n\n if (dbError.message) {\n parts.push(redactDatabaseErrorText(dbError.message, knownSecrets));\n }\n if (dbError.code) {\n parts.push(`code=${redactDatabaseErrorText(dbError.code, knownSecrets)}`);\n }\n if (dbError.detail) {\n parts.push(\n `detail=${redactDatabaseErrorText(dbError.detail, knownSecrets)}`,\n );\n }\n if (dbError.hint) {\n parts.push(`hint=${redactDatabaseErrorText(dbError.hint, knownSecrets)}`);\n }\n if (dbError.severity) {\n parts.push(\n `severity=${redactDatabaseErrorText(dbError.severity, knownSecrets)}`,\n );\n }\n if (dbError.errno !== undefined) parts.push(`errno=${dbError.errno}`);\n\n return parts.length > 0\n ? parts.join(', ')\n : redactDatabaseErrorText(String(error), knownSecrets);\n}\n\nfunction safeDatabaseCause(\n error: unknown,\n knownSecrets: readonly string[],\n): Error {\n const dbError = toDriverError(error);\n const message = dbError.message\n ? redactDatabaseErrorText(dbError.message, knownSecrets)\n : formatDbErrorWithSecrets(error, knownSecrets);\n const cause = new Error(message);\n cause.name = dbError.name\n ? redactDatabaseErrorText(dbError.name, knownSecrets)\n : 'DatabaseDriverError';\n\n for (const field of DRIVER_ERROR_FIELDS) {\n const value = dbError[field];\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n Object.defineProperty(cause, field, {\n configurable: true,\n enumerable: true,\n value:\n typeof value === 'string'\n ? redactDatabaseErrorText(value, knownSecrets)\n : value,\n writable: true,\n });\n }\n }\n\n return cause;\n}\n\n/**\n * Wraps an engine failure in a serializable, secret-safe DatabaseError.\n *\n * Raw SQL and parameter values are replaced in context. The native `cause` is\n * a sanitized driver-error snapshot rather than the original object so normal\n * Error inspection cannot bypass the same redaction applied to messages and\n * JSON serialization.\n */\nexport function wrapDatabaseError(\n message: string,\n error: unknown,\n context: Record<string, unknown> = {},\n): DatabaseError {\n const knownSecrets = contextSecrets(context);\n const originalError = formatDbErrorWithSecrets(error, knownSecrets);\n return new DatabaseError(\n `${message}: ${originalError}`,\n {\n ...sanitizeDatabaseErrorContext(context, knownSecrets),\n originalError,\n },\n safeDatabaseCause(error, knownSecrets),\n );\n}\n\n/**\n * Resolves the column list for a multi-row INSERT and rejects batches whose\n * records disagree on it.\n *\n * A batch INSERT emits one column list for the whole statement, so it can only\n * come from the first record. That leaves two ways for a later record to be\n * written wrongly, both of them silent:\n *\n * - Its keys are the same but in a different insertion order. Callers that\n * bound values with `Object.values(record)` bound them by position, so the\n * values cross-assigned into each other's columns. Projecting each record\n * through the returned column list — `keys.map((key) => record[key])` — is\n * what makes key order irrelevant, and every caller must do it.\n * - Its key set genuinely differs. An extra key has no column to go to and a\n * missing one has no value, so the record cannot be expressed by this\n * statement at all. Guessing (dropping the extra, writing NULL for the\n * missing) is what makes the failure invisible, so this throws instead.\n *\n * A key whose value is `undefined` counts as absent. Adapters that serialize\n * records first drop such keys before this point while the rest pass them\n * through, so comparing raw key lists would make the same batch throw on some\n * adapters and silently write NULL on others. Only the comparison ignores them;\n * the returned column list is untouched, so each adapter keeps its own\n * single-record handling of `undefined`.\n *\n * @param table - Table being inserted into, for error context\n * @param records - Non-empty batch, as the adapter will bind it\n * @returns Column list taken from the first record\n * @throws DatabaseError if any later record's key set differs from the first's\n */\nexport function resolveInsertColumns(\n table: string,\n records: Record<string, any>[],\n): string[] {\n const definedKeys = (record: Record<string, any>) =>\n new Set(Object.keys(record).filter((key) => record[key] !== undefined));\n\n const keys = Object.keys(records[0]);\n const expected = definedKeys(records[0]);\n\n for (let index = 1; index < records.length; index++) {\n const recordKeys = definedKeys(records[index]);\n const missing = [...expected].filter((key) => !recordKeys.has(key));\n const extra = [...recordKeys].filter((key) => !expected.has(key));\n\n if (missing.length === 0 && extra.length === 0) {\n continue;\n }\n\n const problems = [\n missing.length > 0 ? `missing ${missing.join(', ')}` : '',\n extra.length > 0 ? `unexpected ${extra.join(', ')}` : '',\n ].filter(Boolean);\n\n throw new DatabaseError(\n `Batch insert records must all have the same keys; record ${index} has ${problems.join(\n ' and ',\n )} relative to record 0 (${[...expected].join(', ')})`,\n {\n table,\n recordIndex: index,\n expectedKeys: keys,\n recordKeys: [...recordKeys],\n missing,\n extra,\n hint: 'A batch INSERT writes one column list for every row, taken from the first record, so record 0 defines the columns even when it is the one that looks wrong. A key whose value is undefined counts as absent. Split records with differing keys into separate insert() calls, or fill the gaps explicitly with null.',\n },\n );\n }\n\n return keys;\n}\n\n/**\n * Map of valid SQL operators for use in WHERE clauses\n */\nconst VALID_OPERATORS = {\n '=': '=',\n '>': '>',\n '>=': '>=',\n '<': '<',\n '<=': '<=',\n '!=': '!=',\n like: 'LIKE',\n contains: 'CONTAINS',\n in: 'IN',\n 'not in': 'NOT IN',\n} as const;\n\nfunction isSimpleSqlIdentifier(field: string): boolean {\n return /^[a-zA-Z0-9_.]+$/.test(field);\n}\n\ndeclare const rawSqlKeyBrand: unique symbol;\n\n/**\n * A WHERE-clause key produced by {@link raw}: SQL text the caller vouches for,\n * exempt from the identifier validation `buildWhere` applies to every other key.\n */\nexport type RawSqlKey = string & { readonly [rawSqlKeyBrand]: true };\n\n/**\n * Marks a condition key as caller-authored SQL text.\n *\n * A fixed, non-secret sentinel rather than a random per-process token. A random\n * token has to travel inside the key, which means it reaches every place a key\n * is echoed — error messages, logs, each adapter's `DatabaseError` context — and\n * one disclosure would let a caller mint raw keys for the life of the process.\n * A registry of minted expressions avoids that but is not referentially\n * transparent: it would make `raw('revenue >')` in one query change what the\n * plain key `'revenue >'` means in every other one.\n *\n * The NUL delimiters cannot appear in an identifier or in SQL a developer would\n * write, so the marker never collides with a legitimate key.\n */\nconst RAW_KEY_PREFIX = '\\u0000hv-sql-raw\\u0000';\n\n/**\n * Marks a WHERE-clause key as SQL expression text rather than an identifier.\n *\n * {@link buildWhere} validates every condition key as a plain identifier. Wrap a\n * key in `raw()` to opt one condition out of that check:\n *\n * ```typescript\n * buildWhere({\n * status: 'paid', // validated as an identifier\n * [raw('SUM(total) >')]: 100, // caller-authored SQL, not validated\n * });\n * ```\n *\n * Never build the argument from end-user input. `raw()` is an assertion that\n * the caller, not the request, authored this SQL.\n *\n * The returned key carries a marker, so it is a distinct string from the bare\n * expression: minting `raw('revenue >')` never changes what the plain key\n * `'revenue >'` means anywhere else.\n *\n * The marker is a fixed sentinel, not a secret. It stops an expression key being\n * used by accident — the shape of the key no longer grants raw access, a call to\n * `raw()` does — but a caller that controls a whole key string verbatim,\n * including the NUL-delimited marker, can still reproduce it. Validate at your\n * own trust boundary; do not rely on this as the only barrier against hostile\n * input.\n *\n * Enforcement is at runtime: `WhereClause` keys are plain `string`, so a\n * computed key widens and TypeScript will not reject an unmarked expression key\n * at the call site.\n *\n * A trailing operator is recognised only if it is one of `=`, `!=`, `>`, `>=`,\n * `<`, `<=`, `like`, `contains`, `in`, `not in`. Any other trailing token is\n * treated as part of the expression and `=` is appended, so\n * `raw('name ILIKE')` silently emits `name ILIKE = $1`, which the database then\n * rejects. Fold an unsupported operator into the expression instead:\n * `raw('LOWER(name) like')`.\n *\n * @param expression - SQL field or expression text, optionally ending in a\n * supported operator (for example `SUM(total) >`)\n * @returns A branded key for use in a {@link WhereClause}\n * @throws If the expression is empty\n */\nexport function raw(expression: string): RawSqlKey {\n // Unwrap first so raw(raw(x)) === raw(x) and a marker can never nest into the\n // emitted SQL.\n const trimmed = unwrapRawKey(expression).key.trim();\n if (!trimmed) {\n throw new Error('raw() requires a non-empty SQL expression');\n }\n return `${RAW_KEY_PREFIX}${trimmed}` as RawSqlKey;\n}\n\n/**\n * Splits the raw marker off a condition key.\n * @internal\n */\nexport function unwrapRawKey(fullKey: string): {\n key: string;\n isRaw: boolean;\n} {\n return fullKey.startsWith(RAW_KEY_PREFIX)\n ? { key: fullKey.slice(RAW_KEY_PREFIX.length), isRaw: true }\n : { key: fullKey, isRaw: false };\n}\n\nexport function parseConditionKey(fullKey: string): {\n field: string;\n operator: string;\n} {\n const trimmed = fullKey.trim();\n const lower = trimmed.toLowerCase();\n const operators = [\n 'not in',\n 'contains',\n 'like',\n 'in',\n '!=',\n '>=',\n '<=',\n '>',\n '<',\n '=',\n ];\n\n for (const operator of operators) {\n if (lower.endsWith(` ${operator}`)) {\n return {\n field: trimmed.slice(0, -(operator.length + 1)).trim(),\n operator,\n };\n }\n }\n\n // Deliberately no `explicitOperator` flag: \"key ends in an operator\" was the\n // condition that used to suppress identifier validation, and that was the\n // vulnerability. Nothing should gate trust on the shape of the key again.\n return { field: trimmed, operator: '=' };\n}\n\n/**\n * SQL adapter type for adapter-specific query generation\n */\nexport type SqlAdapterType = 'sqlite' | 'postgres' | 'duckdb' | 'json';\n\n/**\n * Builds a single condition for a WHERE clause\n * @internal\n */\nconst buildCondition = (\n fullKey: string,\n value: any,\n currIndex: { value: number },\n adapterType?: SqlAdapterType,\n): { sql: string; values: any[] } => {\n const { key, isRaw } = unwrapRawKey(fullKey);\n const { field, operator } = parseConditionKey(key);\n if (!isRaw && !isSimpleSqlIdentifier(field)) {\n throw new Error(\n `Invalid SQL identifier: ${field}. Condition keys must be plain ` +\n 'identifiers; wrap developer-authored SQL expressions in raw().',\n );\n }\n const sqlOperator =\n VALID_OPERATORS[operator as keyof typeof VALID_OPERATORS] || '=';\n const values: any[] = [];\n\n let sql: string;\n\n if (sqlOperator === 'CONTAINS') {\n if (!adapterType) {\n throw new Error(\n 'CONTAINS requires an adapter type so buildWhere can preserve case-sensitive semantics',\n );\n }\n if (typeof value !== 'string') {\n throw new Error('CONTAINS requires a string value');\n }\n\n const placeholder = `$${currIndex.value++}`;\n const containsFunction = adapterType === 'sqlite' ? 'instr' : 'strpos';\n sql = `${containsFunction}(${field}, ${placeholder}) > 0`;\n values.push(value);\n } else if (value === null) {\n sql = `${field} IS ${sqlOperator === '=' ? 'NULL' : 'NOT NULL'}`;\n } else if (\n (sqlOperator === 'IN' || sqlOperator === 'NOT IN') &&\n Array.isArray(value)\n ) {\n if (value.length === 0) {\n throw new Error(`${sqlOperator} requires at least one value`);\n }\n const placeholders = value.map(() => `$${currIndex.value++}`).join(', ');\n sql = `${field} ${sqlOperator} (${placeholders})`;\n values.push(...value);\n } else if (value instanceof Date) {\n // DuckDB/JSON need CAST to TIMESTAMP to prevent ANY type inference (issue #540)\n // SQLite/PostgreSQL handle ISO strings natively without CAST\n const needsCast = adapterType === 'duckdb' || adapterType === 'json';\n if (needsCast) {\n sql = `${field} ${sqlOperator} CAST($${currIndex.value++} AS TIMESTAMP)${\n sqlOperator === 'LIKE' ? \" ESCAPE '\\\\'\" : ''\n }`;\n } else {\n sql = `${field} ${sqlOperator} $${currIndex.value++}${\n sqlOperator === 'LIKE' ? \" ESCAPE '\\\\'\" : ''\n }`;\n }\n values.push(value.toISOString());\n } else {\n sql = `${field} ${sqlOperator} $${currIndex.value++}${\n sqlOperator === 'LIKE' ? \" ESCAPE '\\\\'\" : ''\n }`;\n values.push(value);\n }\n\n return { sql, values };\n};\n\n/**\n * Builds a SQL WHERE clause with parameterized values and flexible operators\n *\n * @param where - Conditions as object (AND-only) or 2D array (OR/AND compound)\n * @param startIndex - Starting index for parameter numbering (default: 1)\n * @param adapterType - Database adapter type. Controls Date and `contains`\n * handling:\n * - `'duckdb'` / `'json'`: Wraps Date values in `CAST($N AS TIMESTAMP)` to\n * prevent DuckDB ANY-type inference issues.\n * - `'sqlite'` / `'postgres'` / `undefined`: Passes ISO strings directly\n * (these adapters handle ISO timestamp strings natively).\n * - Required for `contains`, whose case-sensitive literal substring SQL is\n * emitted with adapter-specific functions.\n *\n * Every condition key is validated as a plain SQL identifier, with or without\n * an operator suffix. To use expression text as a key, wrap it in {@link raw}:\n *\n * ```typescript\n * buildWhere({ [raw('LOWER(status) =')]: 'paid' });\n * ```\n *\n * `raw()` is the only way to reach expression text, so mapping untrusted input\n * into a condition key throws rather than emitting attacker-controlled SQL.\n *\n * @returns Object containing the SQL clause and array of values\n * @throws If a key that is not wrapped in {@link raw} is not a plain identifier\n *\n * @example Basic Usage (Object format - AND-only):\n * ```typescript\n * buildWhere({\n * 'status': 'active', // equals operator is default\n * 'price >': 100, // greater than\n * 'stock <=': 5, // less than or equal\n * 'category in': ['A', 'B'], // IN clause for arrays\n * 'name like': '%shirt%', // LIKE pattern; backslash escapes `%` / `_`\n * 'notes contains': '100% off' // literal, case-sensitive substring\n * });\n * ```\n *\n * @example 2D Array Format (OR/AND compound logic):\n * ```typescript\n * // WHERE (status = 'active' AND price > 100) OR (status = 'pending' AND priority = 'high')\n * buildWhere([\n * [{ status: 'active' }, { 'price >': 100 }],\n * [{ status: 'pending' }, { priority: 'high' }]\n * ]);\n * ```\n *\n * @example NULL Handling:\n * ```typescript\n * buildWhere({\n * 'deleted_at': null, // becomes \"deleted_at IS NULL\"\n * 'updated_at !=': null, // becomes \"updated_at IS NOT NULL\"\n * 'status': 'active' // regular comparison\n * });\n * ```\n *\n * @example Common Patterns:\n * ```typescript\n * // Price range\n * buildWhere({\n * 'price >=': 10,\n * 'price <': 100\n * });\n *\n * // Date filtering\n * buildWhere({\n * 'created_at >': startDate,\n * 'created_at <=': endDate,\n * 'deleted_at': null\n * });\n *\n * // Search with LIKE\n * buildWhere({\n * 'title like': '%search%',\n * 'description like': '%search%',\n * 'status': 'published'\n * });\n *\n * // Multiple values with IN\n * buildWhere({\n * 'role in': ['admin', 'editor'],\n * 'active': true,\n * 'last_login !=': null\n * });\n * ```\n *\n * The function handles:\n * - Standard comparisons (=, >, >=, <, <=, !=)\n * - NULL checks (IS NULL, IS NOT NULL)\n * - IN clauses for arrays\n * - LIKE for pattern matching with an explicit backslash escape character\n * - Case-sensitive literal substring matching with `contains`\n * - Object format: Multiple conditions combined with AND\n * - 2D array format: Inner arrays ANDed, outer array ORed\n */\nexport const buildWhere = (\n where: WhereClause,\n startIndex = 1,\n adapterType?: SqlAdapterType,\n) => {\n let sql = '';\n const values: any[] = [];\n const currIndex = { value: startIndex };\n\n // Handle 2D array format for OR/AND compound logic\n if (Array.isArray(where)) {\n if (where.length === 0) {\n return { sql: '', values: [] };\n }\n\n const orGroups: string[] = [];\n\n for (const andGroup of where) {\n if (!Array.isArray(andGroup) || andGroup.length === 0) {\n continue;\n }\n\n const andConditions: string[] = [];\n\n for (const conditionObj of andGroup) {\n for (const [fullKey, value] of Object.entries(conditionObj)) {\n const result = buildCondition(fullKey, value, currIndex, adapterType);\n andConditions.push(result.sql);\n values.push(...result.values);\n }\n }\n\n if (andConditions.length > 0) {\n // Wrap AND group in parentheses\n orGroups.push(`(${andConditions.join(' AND ')})`);\n }\n }\n\n if (orGroups.length > 0) {\n sql = `WHERE ${orGroups.join(' OR ')}`;\n }\n\n return { sql, values };\n }\n\n // Handle object format (original behavior - AND-only)\n if (where && Object.keys(where).length > 0) {\n sql = 'WHERE ';\n for (const [fullKey, value] of Object.entries(where)) {\n const result = buildCondition(fullKey, value, currIndex, adapterType);\n\n if (sql !== 'WHERE ') {\n sql += ' AND ';\n }\n\n sql += result.sql;\n values.push(...result.values);\n }\n }\n\n return { sql, values };\n};\n","/**\n * Schema management and dependency resolution for JSON manifests\n * Moved from SMRT package to SQL package for proper separation of concerns\n */\n\nimport type {\n DatabaseInterface,\n IndexDefinition,\n SchemaDefinition,\n SchemaInitializationOptions,\n TriggerDefinition,\n} from './shared/types.js';\n\n/**\n * Result returned by DatabaseSchemaManager.initializeSchemas()\n *\n * Contains the list of schemas that were successfully initialized,\n * any that were skipped (already up to date), errors encountered,\n * and the total execution time in milliseconds.\n */\nexport interface SchemaInitializationResult {\n initialized: string[];\n skipped: string[];\n errors: Array<{ schema: string; error: string }>;\n executionTime: number;\n}\n\n/**\n * Manages schema initialization from JSON manifests with dependency resolution\n *\n * Handles topological sorting of schema dependencies, table creation,\n * index and trigger setup, and version tracking to avoid redundant re-initialization.\n */\nexport class DatabaseSchemaManager {\n private static initializationLock = new Map<string, Promise<void>>();\n private initializedSchemas = new Set<string>();\n private schemaVersions = new Map<string, string>();\n\n /**\n * Initializes database schemas from a JSON manifest with dependency resolution\n *\n * Schemas are topologically sorted by their declared dependencies and\n * initialized in order. Supports legacy raw-SQL schemas via the `schema` option,\n * JSON manifests via the `manifest` option, and schema overrides for extending\n * base schemas.\n *\n * @param db - Database interface to execute DDL against\n * @param options - Schema initialization options (manifest, overrides, force, debug)\n * @returns Result with lists of initialized/skipped schemas, errors, and execution time\n */\n async initializeSchemas(\n db: DatabaseInterface,\n options: SchemaInitializationOptions,\n ): Promise<SchemaInitializationResult> {\n const startTime = Date.now();\n const result: SchemaInitializationResult = {\n initialized: [],\n skipped: [],\n errors: [],\n executionTime: 0,\n };\n\n try {\n let schemas: Record<string, SchemaDefinition> = {};\n\n // Handle legacy SQL schema format\n if (options.schema) {\n console.log(\n '[schema] Legacy SQL schema provided, converting to manifest format',\n );\n // For legacy support, we'll still use the original syncSchema method\n if (db.syncSchema) {\n await db.syncSchema(options.schema);\n result.initialized.push('legacy-sql');\n }\n result.executionTime = Date.now() - startTime;\n return result;\n }\n\n // Handle JSON manifest format\n if (options.manifest) {\n schemas = { ...options.manifest.schemas };\n }\n\n // Apply schema overrides\n if (options.overrides) {\n schemas = { ...schemas, ...options.overrides };\n }\n\n if (Object.keys(schemas).length === 0) {\n console.warn('[schema] No schemas provided for initialization');\n result.executionTime = Date.now() - startTime;\n return result;\n }\n\n // Build dependency graph\n const dependencyGraph = this.buildDependencyGraph(schemas);\n\n // Get initialization order\n const initializationOrder = this.resolveDependencies(dependencyGraph);\n\n if (options.debug) {\n console.log('[schema] Initialization order:', initializationOrder);\n }\n\n // Initialize schemas in dependency order\n for (const schemaName of initializationOrder) {\n const schema = schemas[schemaName];\n if (!schema) continue;\n\n try {\n await this.initializeSchema(db, schemaName, schema, {\n force: options.force,\n debug: options.debug,\n });\n result.initialized.push(schemaName);\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n result.errors.push({ schema: schemaName, error: errorMessage });\n\n if (options.debug) {\n console.error(\n `[schema] Failed to initialize ${schemaName}:`,\n error,\n );\n }\n }\n }\n\n result.executionTime = Date.now() - startTime;\n\n if (options.debug) {\n console.log('[schema] Initialization complete:', result);\n }\n\n return result;\n } catch (error) {\n result.executionTime = Date.now() - startTime;\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n result.errors.push({\n schema: 'dependency-resolution',\n error: errorMessage,\n });\n return result;\n }\n }\n\n /**\n * Initialize a single schema\n */\n private async initializeSchema(\n db: DatabaseInterface,\n schemaName: string,\n schema: SchemaDefinition,\n options: { force?: boolean; debug?: boolean },\n ): Promise<void> {\n const { tableName, version } = schema;\n const lockKey = `${schemaName}:${tableName}`;\n\n // Check if already being initialized\n if (DatabaseSchemaManager.initializationLock.has(lockKey)) {\n await DatabaseSchemaManager.initializationLock.get(lockKey);\n return;\n }\n\n // Check if already initialized and up to date\n if (!options.force && this.isSchemaUpToDate(schemaName, version)) {\n if (options.debug) {\n console.log(`[schema] Skipping ${schemaName} - already up to date`);\n }\n return;\n }\n\n // Create initialization promise\n const initPromise = this.performSchemaInitialization(\n db,\n schemaName,\n schema,\n options,\n );\n DatabaseSchemaManager.initializationLock.set(lockKey, initPromise);\n\n try {\n await initPromise;\n this.markSchemaInitialized(schemaName, version);\n } finally {\n DatabaseSchemaManager.initializationLock.delete(lockKey);\n }\n }\n\n /**\n * Perform the actual schema initialization\n */\n private async performSchemaInitialization(\n db: DatabaseInterface,\n schemaName: string,\n schema: SchemaDefinition,\n options: { force?: boolean; debug?: boolean },\n ): Promise<void> {\n const { tableName, columns, indexes, triggers } = schema;\n\n if (options.debug) {\n console.log(`[schema] Initializing ${schemaName} (${tableName})`);\n }\n\n // Check if table exists\n const tableExists = await db.tableExists(tableName);\n\n if (!tableExists) {\n // Create table\n await this.createTable(db, schema);\n\n // Create indexes\n for (const index of indexes) {\n await this.createIndex(db, tableName, index);\n }\n\n // Create triggers\n for (const trigger of triggers) {\n await this.createTrigger(db, trigger);\n }\n } else if (options.force) {\n // Recreate table if forced\n await db.query(`DROP TABLE IF EXISTS ${tableName}`);\n await this.createTable(db, schema);\n\n for (const index of indexes) {\n await this.createIndex(db, tableName, index);\n }\n\n for (const trigger of triggers) {\n await this.createTrigger(db, trigger);\n }\n } else {\n // Table exists, check for schema changes\n await this.updateSchemaIfNeeded(db, schema);\n }\n }\n\n /**\n * Create table from schema definition\n */\n private async createTable(\n db: DatabaseInterface,\n schema: SchemaDefinition,\n ): Promise<void> {\n const { tableName, columns, foreignKeys } = schema;\n\n const columnDefinitions: string[] = [];\n\n // Add column definitions\n for (const [columnName, columnDef] of Object.entries(columns)) {\n let def = `${columnName} ${columnDef.type}`;\n\n if (columnDef.primaryKey) def += ' PRIMARY KEY';\n if (columnDef.unique && !columnDef.primaryKey) def += ' UNIQUE';\n if (columnDef.notNull) def += ' NOT NULL';\n if (columnDef.defaultValue !== undefined) {\n def += ` DEFAULT ${columnDef.defaultValue}`;\n }\n if (columnDef.check) def += ` CHECK (${columnDef.check})`;\n\n columnDefinitions.push(def);\n }\n\n // Add foreign key constraints\n for (const fk of foreignKeys) {\n let fkDef = `FOREIGN KEY (${fk.column}) REFERENCES ${fk.referencesTable}(${fk.referencesColumn})`;\n if (fk.onDelete) fkDef += ` ON DELETE ${fk.onDelete}`;\n if (fk.onUpdate) fkDef += ` ON UPDATE ${fk.onUpdate}`;\n columnDefinitions.push(fkDef);\n }\n\n const createTableSQL = `CREATE TABLE ${tableName} (\\n ${columnDefinitions.join(',\\n ')}\\n)`;\n await db.query(createTableSQL);\n }\n\n /**\n * Create index from definition\n */\n private async createIndex(\n db: DatabaseInterface,\n tableName: string,\n index: IndexDefinition,\n ): Promise<void> {\n const uniqueClause = index.unique ? 'UNIQUE ' : '';\n const whereClause = index.where ? ` WHERE ${index.where}` : '';\n const createIndexSQL = `CREATE ${uniqueClause}INDEX ${index.name} ON ${tableName} (${index.columns.join(', ')})${whereClause}`;\n\n await db.query(createIndexSQL);\n }\n\n /**\n * Create trigger from definition\n */\n private async createTrigger(\n db: DatabaseInterface,\n trigger: TriggerDefinition,\n ): Promise<void> {\n const conditionClause = trigger.condition\n ? ` WHEN ${trigger.condition}`\n : '';\n const createTriggerSQL = `CREATE TRIGGER ${trigger.name} ${trigger.when} ${trigger.event} ON ${trigger.table}${conditionClause} BEGIN ${trigger.body} END`;\n\n await db.query(createTriggerSQL);\n }\n\n /**\n * Update schema if changes are detected\n */\n private async updateSchemaIfNeeded(\n _db: DatabaseInterface,\n schema: SchemaDefinition,\n ): Promise<void> {\n // For now, just log that update is needed\n // In future, implement ALTER TABLE logic\n console.log(\n `[schema] Schema update logic not yet implemented for ${schema.tableName}`,\n );\n }\n\n /**\n * Build dependency graph from schemas\n */\n private buildDependencyGraph(\n schemas: Record<string, SchemaDefinition>,\n ): Map<string, string[]> {\n const graph = new Map<string, string[]>();\n\n for (const [schemaName, schema] of Object.entries(schemas)) {\n const dependencies = schema.dependencies.filter((dep) =>\n Object.values(schemas).some((s) => s.tableName === dep),\n );\n graph.set(\n schemaName,\n dependencies.map(\n (dep) =>\n Object.entries(schemas).find(\n ([_, s]) => s.tableName === dep,\n )?.[0] || dep,\n ),\n );\n }\n\n return graph;\n }\n\n /**\n * Resolve dependencies using topological sort\n */\n private resolveDependencies(graph: Map<string, string[]>): string[] {\n const resolved: string[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n const visit = (node: string) => {\n if (visiting.has(node)) {\n throw new Error(`Circular dependency detected involving ${node}`);\n }\n if (visited.has(node)) return;\n\n visiting.add(node);\n const dependencies = graph.get(node) || [];\n\n for (const dep of dependencies) {\n if (graph.has(dep)) {\n visit(dep);\n }\n }\n\n visiting.delete(node);\n visited.add(node);\n resolved.push(node);\n };\n\n for (const node of graph.keys()) {\n if (!visited.has(node)) {\n visit(node);\n }\n }\n\n return resolved;\n }\n\n /**\n * Check if schema is up to date\n */\n private isSchemaUpToDate(schemaName: string, version: string): boolean {\n return (\n this.initializedSchemas.has(schemaName) &&\n this.schemaVersions.get(schemaName) === version\n );\n }\n\n /**\n * Mark schema as initialized\n */\n private markSchemaInitialized(schemaName: string, version: string): void {\n this.initializedSchemas.add(schemaName);\n this.schemaVersions.set(schemaName, version);\n }\n\n /**\n * Reset initialization state (for testing)\n */\n reset(): void {\n this.initializedSchemas.clear();\n this.schemaVersions.clear();\n DatabaseSchemaManager.initializationLock.clear();\n }\n}\n","/**\n * Shared utilities for ALTER TABLE operations\n *\n * Provides database-agnostic utilities for generating ALTER TABLE statements,\n * handling column definitions, and creating indexes.\n */\n\nimport type { ColumnDefinitionWithName, IndexDefinition } from './types.js';\n\n/**\n * Database dialect type for SQL generation\n */\nexport type DatabaseDialect = 'sqlite' | 'postgres' | 'duckdb';\n\n/**\n * Generates an ALTER TABLE ADD COLUMN statement\n *\n * @param table - Table name\n * @param column - Column definition with name\n * @param dialect - Database dialect\n * @returns SQL statement string\n *\n * @example\n * ```typescript\n * const sql = generateAddColumnStatement('users', {\n * name: 'email',\n * type: 'TEXT',\n * unique: true,\n * notNull: false\n * }, 'sqlite');\n * // Returns: \"ALTER TABLE users ADD COLUMN email TEXT UNIQUE\"\n * ```\n */\nexport function generateAddColumnStatement(\n table: string,\n column: ColumnDefinitionWithName,\n _dialect: DatabaseDialect,\n): string {\n const parts: string[] = [column.name, column.type];\n\n // Primary key\n if (column.primaryKey) {\n parts.push('PRIMARY KEY');\n }\n\n // NOT NULL constraint\n if (column.notNull) {\n parts.push('NOT NULL');\n }\n\n // UNIQUE constraint\n if (column.unique) {\n parts.push('UNIQUE');\n }\n\n // DEFAULT clause\n if (column.defaultValue !== undefined) {\n const defaultClause = generateDefaultClause(\n column.type,\n column.defaultValue,\n );\n parts.push(`DEFAULT ${defaultClause}`);\n }\n\n // CHECK constraint\n if (column.check) {\n parts.push(`CHECK (${column.check})`);\n }\n\n // Foreign key (inline for some databases)\n if (column.foreignKey) {\n const fk = column.foreignKey;\n const fkParts = [`REFERENCES ${fk.table}(${fk.column})`];\n\n if (fk.onDelete) {\n fkParts.push(`ON DELETE ${fk.onDelete}`);\n }\n\n if (fk.onUpdate) {\n fkParts.push(`ON UPDATE ${fk.onUpdate}`);\n }\n\n parts.push(fkParts.join(' '));\n }\n\n const columnDef = parts.join(' ');\n return `ALTER TABLE ${table} ADD COLUMN ${columnDef}`;\n}\n\n/**\n * Generates a DEFAULT clause for a column definition\n *\n * Handles proper formatting and escaping of default values based on column type.\n *\n * @param columnType - SQL column type (TEXT, INTEGER, BOOLEAN, etc.)\n * @param defaultValue - Default value to format\n * @returns Formatted default value string\n *\n * @example\n * ```typescript\n * generateDefaultClause('TEXT', 'hello'); // Returns: \"'hello'\"\n * generateDefaultClause('INTEGER', 42); // Returns: \"42\"\n * generateDefaultClause('BOOLEAN', true); // Returns: \"true\"\n * generateDefaultClause('TEXT', null); // Returns: \"NULL\"\n * ```\n */\nexport function generateDefaultClause(\n columnType: string,\n defaultValue: any,\n): string {\n if (defaultValue === null) {\n return 'NULL';\n }\n\n const typeUpper = columnType.toUpperCase();\n\n // String types - need quotes\n if (\n typeUpper.includes('TEXT') ||\n typeUpper.includes('CHAR') ||\n typeUpper.includes('VARCHAR') ||\n typeUpper.includes('STRING')\n ) {\n // Handle empty strings\n if (defaultValue === '') {\n return \"''\";\n }\n // Escape single quotes\n const escaped = String(defaultValue).replace(/'/g, \"''\");\n return `'${escaped}'`;\n }\n\n // Boolean types\n if (typeUpper.includes('BOOL')) {\n return defaultValue ? 'true' : 'false';\n }\n\n // Numeric types - no quotes\n if (\n typeUpper.includes('INT') ||\n typeUpper.includes('REAL') ||\n typeUpper.includes('FLOAT') ||\n typeUpper.includes('DOUBLE') ||\n typeUpper.includes('NUMERIC') ||\n typeUpper.includes('DECIMAL')\n ) {\n return String(defaultValue);\n }\n\n // JSON types\n if (typeUpper.includes('JSON')) {\n if (typeof defaultValue === 'string') {\n return `'${defaultValue.replace(/'/g, \"''\")}'`;\n }\n return `'${JSON.stringify(defaultValue).replace(/'/g, \"''\")}'`;\n }\n\n // Date/timestamp types\n if (\n typeUpper.includes('DATE') ||\n typeUpper.includes('TIME') ||\n typeUpper.includes('TIMESTAMP')\n ) {\n if (defaultValue instanceof Date) {\n return `'${defaultValue.toISOString()}'`;\n }\n return `'${String(defaultValue).replace(/'/g, \"''\")}'`;\n }\n\n // Default: treat as string\n if (typeof defaultValue === 'string') {\n return `'${defaultValue.replace(/'/g, \"''\")}'`;\n }\n\n return String(defaultValue);\n}\n\n/**\n * Generates a CREATE INDEX statement\n *\n * @param table - Table name\n * @param index - Index definition\n * @returns SQL CREATE INDEX statement\n *\n * @example\n * ```typescript\n * const sql = generateCreateIndexStatement('users', {\n * name: 'idx_users_email',\n * columns: ['email'],\n * unique: true\n * });\n * // Returns: \"CREATE UNIQUE INDEX idx_users_email ON users (email)\"\n * ```\n */\nexport function generateCreateIndexStatement(\n table: string,\n index: IndexDefinition,\n): string {\n const parts: string[] = ['CREATE'];\n\n // UNIQUE modifier\n if (index.unique) {\n parts.push('UNIQUE');\n }\n\n parts.push('INDEX');\n\n // Index name\n parts.push(index.name);\n\n // ON table\n parts.push(`ON ${table}`);\n\n // Column list\n const columns = index.columns.join(', ');\n parts.push(`(${columns})`);\n\n // WHERE clause (partial index)\n if (index.where) {\n parts.push(`WHERE ${index.where}`);\n }\n\n return parts.join(' ');\n}\n\n/**\n * Validates a table name to prevent SQL injection\n *\n * The argument must be a primitive string. `RegExp.prototype.test` coerces its\n * argument with `toString`, so an object whose `toString` returns a valid name\n * on the call the validator makes and a different one on the call the caller's\n * `${table}` interpolation makes would pass here and then reach SQL. Rejecting\n * non-strings up front closes that gap: the value validated is the value\n * interpolated, because a primitive string cannot observe how many times it is\n * read.\n *\n * @param tableName - Table name to validate\n * @throws Error if the value is not a string or contains invalid characters\n */\nexport function validateTableName(tableName: string): void {\n if (\n typeof tableName !== 'string' ||\n !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)\n ) {\n throw new Error(\n `Invalid table name: ${tableName}. Table names must be a string that starts with a letter or underscore and contains only alphanumeric characters and underscores.`,\n );\n }\n}\n\n/**\n * Validates a column name to prevent SQL injection\n *\n * Rejects non-strings for the same reason as {@link validateTableName}: a value\n * whose `toString` differs between the validation read and the interpolation\n * read would otherwise slip a different identifier into SQL than the one\n * checked here.\n *\n * @param columnName - Column name to validate\n * @throws Error if the value is not a string or contains invalid characters\n */\nexport function validateColumnName(columnName: string): void {\n if (\n typeof columnName !== 'string' ||\n !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(columnName)\n ) {\n throw new Error(\n `Invalid column name: ${columnName}. Column names must be a string that starts with a letter or underscore and contains only alphanumeric characters and underscores.`,\n );\n }\n}\n\n/**\n * Validates every identifier in a column list to prevent SQL injection\n *\n * Column names reaching the CRUD methods come from `Object.keys(data)` and the\n * caller's `conflictColumns`, both of which are interpolated into SQL. The\n * accepted shape has no character that can end an identifier. Adapters may\n * render a validated name bare, or quote it under an adapter-specific contract.\n * PostgreSQL upsert uses {@link quotePostgresColumnName}, which canonicalizes\n * the name before quoting so its established unquoted case-folding semantics\n * do not change.\n *\n * @param columnNames - Column names to validate\n * @throws Error if any column name contains invalid characters\n */\nexport function validateColumnNames(columnNames: string[]): void {\n for (const columnName of columnNames) {\n validateColumnName(columnName);\n }\n}\n\n/**\n * Quotes a validated column name using PostgreSQL's historic case semantics.\n *\n * PostgreSQL folds every unquoted identifier to lowercase. The CRUD API has\n * always accepted mixed-case inputs while rendering them unquoted, so quoting\n * the caller's spelling directly would silently change which physical column\n * is addressed. Validate the existing plain-identifier contract first, then\n * lowercase before quoting: `mixedCase` still addresses `mixedcase`, while a\n * reserved word such as `end` is no longer parsed as SQL syntax.\n *\n * @param columnName - Plain column name to render for PostgreSQL\n * @returns A lowercase, double-quoted PostgreSQL identifier\n * @throws Error if the column name is outside the CRUD identifier contract\n */\nexport function quotePostgresColumnName(columnName: string): string {\n validateColumnName(columnName);\n return `\"${columnName.toLowerCase()}\"`;\n}\n\n/**\n * Escapes an identifier for interpolation inside double quotes\n *\n * For the positions that already quote the identifier — the DuckDB and JSON\n * upsert statements, which quote to match their schema generator — quoting is\n * what makes the identifier safe, so the only thing that has to be handled is\n * a quote character inside the name. Doubling it is the SQL escape.\n *\n * This is deliberately more permissive than {@link validateColumnName}: those\n * adapters accept column names that are not plain identifiers (`Full Name`,\n * `user-id`, `2024_total`), which matters most for the JSON adapter, whose\n * columns come straight from the keys of whatever JSON it was pointed at.\n * Rejecting them would break working code to fix an injection that quoting\n * already prevents.\n *\n * @param identifier - Identifier to place inside double quotes\n * @returns The identifier with embedded quotes doubled\n * @throws Error if the identifier is empty or contains a NUL byte\n */\nexport function escapeQuotedIdentifier(identifier: string): string {\n // Coerce to a primitive string first. The escape's safety rests on\n // `replaceAll`/`includes` being the real `String` methods, but a crafted\n // object could supply its own that return unescaped SQL while its `toString`\n // reads as a benign name elsewhere. Coercing once runs its `toString` here\n // and pins an immutable primitive for the length check and the escape, so the\n // value escaped is the value returned.\n const value = String(identifier);\n if (value.length === 0 || value.includes('\\0')) {\n throw new Error(\n `Invalid column name: ${JSON.stringify(value)}. Column names must be non-empty and must not contain a NUL byte.`,\n );\n }\n return value.replaceAll('\"', '\"\"');\n}\n\n/**\n * Escapes a value for interpolation inside a single-quoted SQL string literal\n *\n * The DuckDB-backed adapters read JSON through `read_json_auto('<path>')`, and\n * a file path is not something an identifier validator can check — paths\n * legitimately contain spaces, dots and, on Linux and macOS, quotes. Doubling\n * the quote is the SQL escape, and it is what keeps a file named `x'.json`\n * from ending the literal and starting a statement.\n *\n * @param value - Value to place inside single quotes\n * @returns The value with embedded single quotes doubled\n * @throws Error if the value contains a NUL byte\n */\nexport function escapeStringLiteral(value: string): string {\n // Coerce to a primitive string first, for the same reason as\n // {@link escapeQuotedIdentifier}: the escape must run on an immutable\n // primitive, not on an object that could supply its own `replaceAll`.\n const text = String(value);\n if (text.includes('\\0')) {\n throw new Error(\n `Invalid SQL string literal: ${JSON.stringify(text)}. Values must not contain a NUL byte.`,\n );\n }\n return text.replaceAll(\"'\", \"''\");\n}\n\n/**\n * Validates an index name to prevent SQL injection\n *\n * @param indexName - Index name to validate\n * @throws Error if index name contains invalid characters\n */\nexport function validateIndexName(indexName: string): void {\n if (\n typeof indexName !== 'string' ||\n !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(indexName)\n ) {\n throw new Error(\n `Invalid index name: ${indexName}. Index names must be a string that starts with a letter or underscore and contains only alphanumeric characters and underscores.`,\n );\n }\n}\n","import type { DatabaseCacheOptions } from './types';\n\nconst CACHED_VALIDATION_CAPABILITY = Symbol.for(\n '@happyvertical/sql/connection-cache-cached-validation/v1',\n);\n\ntype PendingConnection<T> = {\n invalidated: false | 'clear' | 'evict';\n promise: Promise<T>;\n};\n\ntype EvictionChain<T> = {\n promise: Promise<void>;\n state: {\n invalidated: Set<PendingConnection<T>>;\n invalidation: 'clear' | 'evict';\n };\n};\n\ntype CacheRequest = {\n cancelled: boolean;\n done: Promise<void>;\n finish: () => void;\n};\n\n/** Race-safe async resource cache used by database adapters. */\nexport class ConnectionCache<T> {\n readonly [CACHED_VALIDATION_CAPABILITY] = true;\n readonly #connections = new Map<string, T>();\n readonly #pending = new Map<string, PendingConnection<T>>();\n readonly #evictions = new Map<string, EvictionChain<T>>();\n readonly #requests = new Map<string, Set<CacheRequest>>();\n readonly #clearing = new Map<string, number>();\n readonly #failedClosures = new Map<\n string,\n { error: unknown; resources: Set<T> }\n >();\n\n async getOrCreate(\n key: string | undefined,\n options: DatabaseCacheOptions,\n create: () => Promise<T>,\n close: (connection: T) => Promise<void>,\n validateCached?: (connection: T) => Promise<boolean>,\n ): Promise<T> {\n validateDatabaseCacheOptions(options);\n const request =\n key && options.cache !== false ? this.#registerRequest(key) : undefined;\n try {\n this.#throwIfCancelled(request);\n if (key && options.clearCache) {\n await this.evict(key, close);\n this.#throwIfCancelled(request);\n }\n if (key) this.#throwIfFailedClosure(key);\n\n if (!key || options.cache === false) {\n return create();\n }\n\n while (true) {\n if (this.#evictions.has(key)) {\n await this.#waitForEviction(key);\n this.#throwIfCancelled(request);\n }\n this.#throwIfFailedClosure(key);\n\n const cached = this.#connections.get(key);\n if (cached !== undefined) {\n if (!validateCached) return cached;\n\n // Validation may yield while another caller evicts this entry. Only\n // return the resource when it remains this key's live cache entry.\n const isValid = await validateCached(cached);\n this.#throwIfCancelled(request);\n if (\n isValid &&\n this.#connections.get(key) === cached &&\n !this.#evictions.has(key)\n ) {\n return cached;\n }\n if (\n !isValid &&\n this.#connections.get(key) === cached &&\n !this.#evictions.has(key)\n ) {\n await this.evict(key, close);\n this.#throwIfCancelled(request);\n }\n continue;\n }\n\n let pending = this.#pending.get(key);\n if (!pending) {\n this.#throwIfCancelled(request);\n pending = { invalidated: false, promise: create() };\n this.#pending.set(key, pending);\n }\n\n let connection: T;\n try {\n connection = await pending.promise;\n } catch (error) {\n if (this.#pending.get(key) === pending) {\n this.#pending.delete(key);\n }\n throw error;\n }\n\n if (pending.invalidated || this.#evictions.has(key)) {\n if (this.#evictions.has(key)) {\n await this.#waitForEviction(key);\n }\n this.#throwIfCancelled(request);\n if (pending.invalidated === 'clear') {\n throw this.#clearError();\n }\n continue;\n }\n\n this.#throwIfCancelled(request);\n if (this.#pending.get(key) === pending) {\n this.#pending.delete(key);\n }\n this.#connections.set(key, connection);\n return connection;\n }\n } finally {\n if (request && key) {\n this.#finishRequest(key, request);\n }\n }\n }\n\n async evict(\n key: string,\n close: (connection: T) => Promise<void>,\n invalidation: 'clear' | 'evict' = 'evict',\n ) {\n const previous = this.#evictions.get(key);\n const state = previous?.state ?? {\n invalidated: new Set<PendingConnection<T>>(),\n invalidation,\n };\n if (invalidation === 'clear') {\n state.invalidation = 'clear';\n for (const pending of state.invalidated) {\n pending.invalidated = 'clear';\n }\n }\n\n const eviction = (previous?.promise ?? Promise.resolve()).then(async () => {\n const resources = new Set<T>(\n this.#failedClosures.get(key)?.resources ?? [],\n );\n this.#failedClosures.delete(key);\n const cached = this.#connections.get(key);\n this.#connections.delete(key);\n if (cached) resources.add(cached);\n\n const pending = this.#pending.get(key);\n this.#pending.delete(key);\n if (pending) {\n state.invalidated.add(pending);\n pending.invalidated = state.invalidation;\n // The original initializer caller still observes its rejection. For\n // eviction, a stale failure means there is simply no resource to close.\n const initialized = await pending.promise.catch(() => undefined);\n if (initialized !== undefined) resources.add(initialized);\n }\n\n const closeResults = await Promise.allSettled(\n [...resources].map(async (resource) => {\n await close(resource);\n return resource;\n }),\n );\n const failures = closeResults.filter(\n (result): result is PromiseRejectedResult =>\n result.status === 'rejected',\n );\n if (failures.length) {\n const resourceList = [...resources];\n const failedResources = new Set<T>();\n closeResults.forEach((result, index) => {\n if (result.status === 'rejected') {\n failedResources.add(resourceList[index]);\n }\n });\n const error =\n failures.length === 1\n ? failures[0].reason\n : new AggregateError(\n failures.map((failure) => failure.reason),\n 'Multiple database connections failed to close',\n );\n this.#failedClosures.set(key, {\n error,\n resources: failedResources,\n });\n throw error;\n }\n });\n\n const chain = { promise: eviction, state };\n this.#evictions.set(key, chain);\n try {\n await eviction;\n } finally {\n if (this.#evictions.get(key) === chain) {\n this.#evictions.delete(key);\n }\n }\n }\n\n async clear(close: (connection: T) => Promise<void>) {\n const keys = new Set([\n ...this.#connections.keys(),\n ...this.#pending.keys(),\n ...this.#evictions.keys(),\n ...this.#requests.keys(),\n ...this.#clearing.keys(),\n ...this.#failedClosures.keys(),\n ]);\n const results = await Promise.allSettled(\n [...keys].map((key) => this.#clearKey(key, close)),\n );\n const failures = results.filter(\n (result): result is PromiseRejectedResult => result.status === 'rejected',\n );\n if (failures.length === 1) throw failures[0].reason;\n if (failures.length > 1) {\n throw new AggregateError(\n failures.map((failure) => failure.reason),\n 'Multiple database connection cache entries failed to clear',\n );\n }\n }\n\n forget(connection: T): void {\n for (const [key, cached] of this.#connections) {\n if (cached === connection) {\n this.#connections.delete(key);\n }\n }\n }\n\n /** @internal Test-only visibility; includes every retained namespace. */\n get size(): number {\n return (\n this.#connections.size +\n this.#pending.size +\n this.#evictions.size +\n this.#requests.size +\n this.#clearing.size +\n this.#failedClosures.size\n );\n }\n\n async #clearKey(key: string, close: (connection: T) => Promise<void>) {\n this.#clearing.set(key, (this.#clearing.get(key) ?? 0) + 1);\n try {\n this.#cancelRequests(key);\n await this.evict(key, close, 'clear');\n while (this.#requests.get(key)?.size) {\n this.#cancelRequests(key);\n await Promise.all(\n [...(this.#requests.get(key) ?? [])].map((request) => request.done),\n );\n }\n } finally {\n const remaining = (this.#clearing.get(key) ?? 1) - 1;\n if (remaining === 0) this.#clearing.delete(key);\n else this.#clearing.set(key, remaining);\n }\n }\n\n #cancelRequests(key: string): void {\n for (const request of this.#requests.get(key) ?? []) {\n request.cancelled = true;\n }\n }\n\n #clearError(): Error {\n return new Error(\n 'Database connection initialization was cancelled by cache clear',\n );\n }\n\n #finishRequest(key: string, request: CacheRequest): void {\n const requests = this.#requests.get(key);\n requests?.delete(request);\n if (requests?.size === 0) this.#requests.delete(key);\n request.finish();\n }\n\n #registerRequest(key: string): CacheRequest {\n let finish = () => {};\n const done = new Promise<void>((resolve) => {\n finish = resolve;\n });\n const request = {\n cancelled: this.#clearing.has(key),\n done,\n finish,\n };\n const requests = this.#requests.get(key) ?? new Set<CacheRequest>();\n requests.add(request);\n this.#requests.set(key, requests);\n return request;\n }\n\n #throwIfCancelled(request: CacheRequest | undefined): void {\n if (request?.cancelled) throw this.#clearError();\n }\n\n #throwIfFailedClosure(key: string): void {\n const failure = this.#failedClosures.get(key);\n if (failure) throw failure.error;\n }\n\n async #waitForEviction(key: string): Promise<void> {\n while (true) {\n const eviction = this.#evictions.get(key);\n if (!eviction) return;\n await eviction.promise;\n }\n }\n}\n\n/**\n * Adds cached-resource validation to a shared cache created by an older module.\n *\n * JSON connection caches live on `globalThis`, so a compatible older SDK copy\n * can create the shared object before this module loads. Mutating that object\n * preserves one cache for both module copies while upgrading its four-argument\n * `getOrCreate` method to the current validation contract.\n */\nexport function enableCachedValidation<T>(\n cache: ConnectionCache<T>,\n): ConnectionCache<T> {\n const compatible = cache as ConnectionCache<T> &\n Record<symbol, boolean | undefined>;\n if (compatible[CACHED_VALIDATION_CAPABILITY]) return cache;\n\n const legacyGetOrCreate = cache.getOrCreate.bind(cache) as (\n key: string | undefined,\n options: DatabaseCacheOptions,\n create: () => Promise<T>,\n close: (connection: T) => Promise<void>,\n ) => Promise<T>;\n const legacyClear = cache.clear.bind(cache);\n const locks = new Map<string, Promise<void>>();\n let clearGeneration = 0;\n let activeClear: Promise<void> | undefined;\n\n const runLocked = async <R>(key: string, work: () => Promise<R>) => {\n const previous = locks.get(key) ?? Promise.resolve();\n let release!: () => void;\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n const chain = previous.then(\n () => current,\n () => current,\n );\n locks.set(key, chain);\n await previous.catch(() => {});\n try {\n return await work();\n } finally {\n release();\n if (locks.get(key) === chain) locks.delete(key);\n }\n };\n\n Object.defineProperty(cache, 'getOrCreate', {\n configurable: true,\n value: async (\n key: string | undefined,\n options: DatabaseCacheOptions,\n create: () => Promise<T>,\n close: (connection: T) => Promise<void>,\n validateCached?: (connection: T) => Promise<boolean>,\n ): Promise<T> => {\n if (!key || options.cache === false) {\n return legacyGetOrCreate(key, options, create, close);\n }\n const requestGeneration = clearGeneration;\n const throwIfCleared = () => {\n if (activeClear || requestGeneration !== clearGeneration) {\n throw new Error(\n 'Database connection initialization was cancelled by cache clear',\n );\n }\n };\n\n return runLocked(key, async () => {\n throwIfCleared();\n let requestOptions = options;\n while (true) {\n throwIfCleared();\n const candidate = await legacyGetOrCreate(\n key,\n requestOptions,\n create,\n close,\n );\n throwIfCleared();\n if (!validateCached) return candidate;\n\n const isValid = await validateCached(candidate);\n throwIfCleared();\n if (isValid) {\n // Reacquire under the compatibility lock so an explicit eviction\n // that completed during validation cannot make us return its old\n // resource.\n const confirmed = await legacyGetOrCreate(\n key,\n { ...requestOptions, clearCache: false },\n create,\n close,\n );\n throwIfCleared();\n if (confirmed === candidate) return candidate;\n } else {\n await cache.evict(key, close);\n }\n requestOptions = { ...options, clearCache: false };\n }\n });\n },\n writable: true,\n });\n Object.defineProperty(cache, 'clear', {\n configurable: true,\n value: async (close: (connection: T) => Promise<void>): Promise<void> => {\n if (activeClear) return activeClear;\n\n clearGeneration += 1;\n const clearing = (async () => {\n let clearError: unknown;\n let clearFailed = false;\n try {\n await legacyClear(close);\n } catch (error) {\n clearFailed = true;\n clearError = error;\n }\n while (locks.size > 0) {\n await Promise.allSettled([...locks.values()]);\n }\n if (clearFailed) throw clearError;\n })();\n activeClear = clearing;\n try {\n await clearing;\n } finally {\n if (activeClear === clearing) activeClear = undefined;\n }\n },\n writable: true,\n });\n Object.defineProperty(cache, CACHED_VALIDATION_CAPABILITY, {\n configurable: false,\n enumerable: false,\n value: true,\n writable: false,\n });\n return cache;\n}\n\nexport function validateDatabaseCacheOptions(options: DatabaseCacheOptions) {\n if (options.dbid === '') {\n throw new Error('Database cache dbid must be a non-empty string');\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { DatabaseError } from "@happyvertical/utils";
|
|
2
|
-
//#region src/shared/transaction-lock.ts
|
|
3
|
-
/**
|
|
4
|
-
* How long a queued transaction waits for the connection before giving up.
|
|
5
|
-
*
|
|
6
|
-
* A transaction that legitimately runs longer than this will make the ones
|
|
7
|
-
* behind it fail rather than stall indefinitely. Raise it with the adapter's
|
|
8
|
-
* `transactionQueueTimeout` option if that is the wrong trade for a workload.
|
|
9
|
-
*/
|
|
10
|
-
var DEFAULT_TRANSACTION_QUEUE_TIMEOUT_MS = 3e4;
|
|
11
|
-
/**
|
|
12
|
-
* Creates a lock that serializes transactions on one connection.
|
|
13
|
-
*
|
|
14
|
-
* SQLite, DuckDB and the JSON adapter each drive a single connection, and a
|
|
15
|
-
* connection can only be in one transaction at a time. `transaction()` used to
|
|
16
|
-
* issue `BEGIN` on that shared connection with no serialization and no record
|
|
17
|
-
* of whether one was already open, so two overlapping calls corrupted each
|
|
18
|
-
* other: the second `BEGIN` threw, its `catch` ran `ROLLBACK`, and that
|
|
19
|
-
* rollback landed on the *first* transaction — committing part of its work,
|
|
20
|
-
* discarding the rest, and rejecting its promise. Nesting was not required;
|
|
21
|
-
* two concurrent requests were enough, which made it reachable from any server
|
|
22
|
-
* handling more than one at a time.
|
|
23
|
-
*
|
|
24
|
-
* The lock spans the whole `BEGIN` … `COMMIT`/`ROLLBACK` sequence, so a queued
|
|
25
|
-
* transaction starts only once the previous one has ended. It is a
|
|
26
|
-
* promise-chain mutex, the same shape as the `withNullAwareUpsertLock` the
|
|
27
|
-
* SQLite adapter already used for the narrower upsert case.
|
|
28
|
-
*
|
|
29
|
-
* Re-entrant calls must not come through here. A nested `tx.transaction()`
|
|
30
|
-
* arrives via the transaction-scoped interface, which re-enters under a
|
|
31
|
-
* `SAVEPOINT` (SQLite) or refuses (DuckDB, JSON) without ever reaching the
|
|
32
|
-
* top-level `transaction()`; routing one here instead would make it wait on a
|
|
33
|
-
* lock its own caller is holding.
|
|
34
|
-
*
|
|
35
|
-
* @param adapter - Adapter name, for the timeout error
|
|
36
|
-
* @param timeoutMs - Ceiling on how long to wait for a turn
|
|
37
|
-
*/
|
|
38
|
-
function createTransactionLock(adapter, timeoutMs = DEFAULT_TRANSACTION_QUEUE_TIMEOUT_MS) {
|
|
39
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new DatabaseError("transactionQueueTimeout must be a positive, finite number of milliseconds", {
|
|
40
|
-
adapter,
|
|
41
|
-
timeoutMs
|
|
42
|
-
});
|
|
43
|
-
let tail = Promise.resolve();
|
|
44
|
-
let held = false;
|
|
45
|
-
const acquire = async () => {
|
|
46
|
-
const previous = tail;
|
|
47
|
-
let release = () => {};
|
|
48
|
-
const current = new Promise((resolve) => {
|
|
49
|
-
release = resolve;
|
|
50
|
-
});
|
|
51
|
-
tail = previous.then(() => current, () => current);
|
|
52
|
-
let timer;
|
|
53
|
-
try {
|
|
54
|
-
await new Promise((resolve, reject) => {
|
|
55
|
-
timer = setTimeout(() => {
|
|
56
|
-
reject(new DatabaseError(`Timed out after ${timeoutMs}ms waiting for the ${adapter} connection's current transaction to finish. This connection runs one transaction at a time. Usually one of: a beginTransaction() handle that was never committed or rolled back; a top-level db.* call that opens its own transaction being made inside a transaction callback, which waits on the connection its own caller holds (use the tx-scoped method instead); or simply more queued transactions than transactionQueueTimeout allows for.`, {
|
|
57
|
-
adapter,
|
|
58
|
-
timeoutMs
|
|
59
|
-
}));
|
|
60
|
-
}, timeoutMs);
|
|
61
|
-
previous.then(resolve, resolve);
|
|
62
|
-
});
|
|
63
|
-
} catch (error) {
|
|
64
|
-
release();
|
|
65
|
-
throw error;
|
|
66
|
-
} finally {
|
|
67
|
-
if (timer) clearTimeout(timer);
|
|
68
|
-
}
|
|
69
|
-
let released = false;
|
|
70
|
-
held = true;
|
|
71
|
-
return () => {
|
|
72
|
-
if (released) return;
|
|
73
|
-
released = true;
|
|
74
|
-
held = false;
|
|
75
|
-
release();
|
|
76
|
-
};
|
|
77
|
-
};
|
|
78
|
-
const run = async (work) => {
|
|
79
|
-
const release = await acquire();
|
|
80
|
-
try {
|
|
81
|
-
return await work();
|
|
82
|
-
} finally {
|
|
83
|
-
release();
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
return {
|
|
87
|
-
acquire,
|
|
88
|
-
get held() {
|
|
89
|
-
return held;
|
|
90
|
-
},
|
|
91
|
-
run
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
//#endregion
|
|
95
|
-
//#region src/shared/types.ts
|
|
96
|
-
/**
|
|
97
|
-
* Error thrown when `transaction()` is called on a transaction-scoped interface
|
|
98
|
-
* belonging to an adapter whose engine has no savepoints.
|
|
99
|
-
*
|
|
100
|
-
* DuckDB — and therefore the JSON adapter built on it — does not implement
|
|
101
|
-
* `SAVEPOINT`, so there is no way to re-enter the current transaction. Adapters
|
|
102
|
-
* that can re-enter (PostgreSQL, SQLite) do so under a savepoint and never
|
|
103
|
-
* throw this.
|
|
104
|
-
*
|
|
105
|
-
* The alternative would be to open a second, independent transaction, which is
|
|
106
|
-
* what these adapters used to do; on a shared connection that silently
|
|
107
|
-
* destroyed the enclosing transaction's uncommitted work.
|
|
108
|
-
*
|
|
109
|
-
* @example Ensuring a helper works on every adapter
|
|
110
|
-
* ```typescript
|
|
111
|
-
* // Take the transaction as a parameter rather than opening a nested one.
|
|
112
|
-
* async function recordClick(db: DatabaseInterface, id: string) {
|
|
113
|
-
* await db.update('links', { id }, { clicks: 1 });
|
|
114
|
-
* }
|
|
115
|
-
* await db.transaction(async (tx) => recordClick(tx, id));
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
118
|
-
var NestedTransactionError = class extends DatabaseError {
|
|
119
|
-
constructor(message, context) {
|
|
120
|
-
super(message, context);
|
|
121
|
-
this.name = "NestedTransactionError";
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
/**
|
|
125
|
-
* Resolves a SchemasOption to its concrete value.
|
|
126
|
-
*
|
|
127
|
-
* Adapters that need schemas (JSON, DuckDB) call this to unwrap the
|
|
128
|
-
* lazy function. Adapters that don't (Postgres, SQLite) simply ignore
|
|
129
|
-
* the `schemas` option without calling this.
|
|
130
|
-
*/
|
|
131
|
-
function resolveSchemas(schemas) {
|
|
132
|
-
if (!schemas) return void 0;
|
|
133
|
-
if (typeof schemas === "function") return schemas();
|
|
134
|
-
return schemas;
|
|
135
|
-
}
|
|
136
|
-
//#endregion
|
|
137
|
-
export { createTransactionLock as i, resolveSchemas as n, DEFAULT_TRANSACTION_QUEUE_TIMEOUT_MS as r, NestedTransactionError as t };
|
|
138
|
-
|
|
139
|
-
//# sourceMappingURL=types-CwWJ-yju.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-CwWJ-yju.js","names":[],"sources":["../../src/shared/transaction-lock.ts","../../src/shared/types.ts"],"sourcesContent":["import { DatabaseError } from '@happyvertical/utils';\n\n/**\n * How long a queued transaction waits for the connection before giving up.\n *\n * A transaction that legitimately runs longer than this will make the ones\n * behind it fail rather than stall indefinitely. Raise it with the adapter's\n * `transactionQueueTimeout` option if that is the wrong trade for a workload.\n */\nexport const DEFAULT_TRANSACTION_QUEUE_TIMEOUT_MS = 30_000;\n\n/**\n * Serializes transactions on a connection that cannot hold more than one.\n */\nexport interface TransactionLock {\n /** Whether one caller currently owns the connection. */\n readonly held: boolean;\n\n /**\n * Takes the connection and returns the function that gives it back.\n *\n * Calling the returned function more than once is a no-op, so a teardown\n * path that runs twice cannot hand the connection to two callers at once.\n */\n acquire(): Promise<() => void>;\n\n /**\n * Runs `work` with exclusive use of the connection, releasing it however\n * `work` finishes.\n */\n run<T>(work: () => Promise<T>): Promise<T>;\n}\n\n/**\n * Creates a lock that serializes transactions on one connection.\n *\n * SQLite, DuckDB and the JSON adapter each drive a single connection, and a\n * connection can only be in one transaction at a time. `transaction()` used to\n * issue `BEGIN` on that shared connection with no serialization and no record\n * of whether one was already open, so two overlapping calls corrupted each\n * other: the second `BEGIN` threw, its `catch` ran `ROLLBACK`, and that\n * rollback landed on the *first* transaction — committing part of its work,\n * discarding the rest, and rejecting its promise. Nesting was not required;\n * two concurrent requests were enough, which made it reachable from any server\n * handling more than one at a time.\n *\n * The lock spans the whole `BEGIN` … `COMMIT`/`ROLLBACK` sequence, so a queued\n * transaction starts only once the previous one has ended. It is a\n * promise-chain mutex, the same shape as the `withNullAwareUpsertLock` the\n * SQLite adapter already used for the narrower upsert case.\n *\n * Re-entrant calls must not come through here. A nested `tx.transaction()`\n * arrives via the transaction-scoped interface, which re-enters under a\n * `SAVEPOINT` (SQLite) or refuses (DuckDB, JSON) without ever reaching the\n * top-level `transaction()`; routing one here instead would make it wait on a\n * lock its own caller is holding.\n *\n * @param adapter - Adapter name, for the timeout error\n * @param timeoutMs - Ceiling on how long to wait for a turn\n */\nexport function createTransactionLock(\n adapter: string,\n timeoutMs: number = DEFAULT_TRANSACTION_QUEUE_TIMEOUT_MS,\n): TransactionLock {\n // The timer is measured from enqueue, not from when the lock frees, so this\n // bounds the total wait rather than any one holder. A caller that expects\n // sustained bursts on one connection should raise it: a hundred queued\n // 400ms transactions exceed 30s even though none of them is stuck.\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new DatabaseError(\n 'transactionQueueTimeout must be a positive, finite number of milliseconds',\n { adapter, timeoutMs },\n );\n }\n // Resolves when the transaction currently holding the connection ends.\n let tail: Promise<void> = Promise.resolve();\n let held = false;\n\n const acquire = async (): Promise<() => void> => {\n const previous = tail;\n let release: () => void = () => {};\n const current = new Promise<void>((resolve) => {\n release = resolve;\n });\n\n // Publish our turn before awaiting, so a caller that arrives while we are\n // suspended queues behind us rather than racing us for the connection.\n tail = previous.then(\n () => current,\n () => current,\n );\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await new Promise<void>((resolve, reject) => {\n timer = setTimeout(() => {\n reject(\n new DatabaseError(\n `Timed out after ${timeoutMs}ms waiting for the ${adapter} connection's current transaction to finish. This connection runs one transaction at a time. Usually one of: a beginTransaction() handle that was never committed or rolled back; a top-level db.* call that opens its own transaction being made inside a transaction callback, which waits on the connection its own caller holds (use the tx-scoped method instead); or simply more queued transactions than transactionQueueTimeout allows for.`,\n { adapter, timeoutMs },\n ),\n );\n }, timeoutMs);\n previous.then(resolve, resolve);\n });\n } catch (error) {\n // Drop out of the queue rather than wedge it: `tail` still chains through\n // `current`, so resolving it lets the callers behind us take their turn.\n release();\n throw error;\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n let released = false;\n held = true;\n return () => {\n if (released) return;\n released = true;\n held = false;\n release();\n };\n };\n\n const run = async <T>(work: () => Promise<T>): Promise<T> => {\n const release = await acquire();\n try {\n return await work();\n } finally {\n release();\n }\n };\n\n return {\n acquire,\n get held() {\n return held;\n },\n run,\n };\n}\n","import { DatabaseError } from '@happyvertical/utils';\n\n/**\n * Error thrown when `transaction()` is called on a transaction-scoped interface\n * belonging to an adapter whose engine has no savepoints.\n *\n * DuckDB — and therefore the JSON adapter built on it — does not implement\n * `SAVEPOINT`, so there is no way to re-enter the current transaction. Adapters\n * that can re-enter (PostgreSQL, SQLite) do so under a savepoint and never\n * throw this.\n *\n * The alternative would be to open a second, independent transaction, which is\n * what these adapters used to do; on a shared connection that silently\n * destroyed the enclosing transaction's uncommitted work.\n *\n * @example Ensuring a helper works on every adapter\n * ```typescript\n * // Take the transaction as a parameter rather than opening a nested one.\n * async function recordClick(db: DatabaseInterface, id: string) {\n * await db.update('links', { id }, { clicks: 1 });\n * }\n * await db.transaction(async (tx) => recordClick(tx, id));\n * ```\n */\nexport class NestedTransactionError extends DatabaseError {\n constructor(message: string, context?: Record<string, unknown>) {\n super(message, context);\n this.name = 'NestedTransactionError';\n }\n}\n\n/**\n * WHERE clause input for database queries\n *\n * Supports two formats:\n * 1. Object format (AND-only): `{ status: 'active', 'price >': 100 }`\n * - All conditions are AND-joined\n *\n * 2. 2D array format (OR/AND compound logic): `[[cond1, cond2], [cond3, cond4]]`\n * - Inner arrays are AND-joined: `(cond1 AND cond2)`\n * - Outer array is OR-joined: `(cond1 AND cond2) OR (cond3 AND cond4)`\n *\n * @example Object format (backward compatible)\n * ```typescript\n * // WHERE status = 'active' AND name contains the exact text '100% cotton'\n * const where = {\n * status: 'active',\n * 'name contains': '100% cotton',\n * };\n * ```\n *\n * @example 2D array format for OR/AND compound logic\n * ```typescript\n * // WHERE (status = 'active' AND price > 100) OR (status = 'pending' AND priority = 'high')\n * const where = [\n * [{ status: 'active' }, { 'price >': 100 }],\n * [{ status: 'pending' }, { priority: 'high' }]\n * ];\n * ```\n */\nexport type WhereClause = Record<string, any> | Record<string, any>[][];\n\n/**\n * Common database connection options\n */\nexport interface DatabaseCacheOptions {\n /**\n * Stable, caller-supplied cache identity. When omitted, adapters derive an\n * identity from their effective connection configuration.\n */\n dbid?: string;\n\n /**\n * Whether this request may reuse and retain a shared adapter.\n * Set to `false` for a distinct connection that is never inserted into the\n * shared cache. Existing cached connections are left untouched.\n *\n * @default true for adapters that support caching\n */\n cache?: boolean;\n\n /**\n * Evict and close the matching cached adapter before serving this request.\n * Eviction waits for both resource closure and any in-flight initializer.\n * The replacement is cached unless `cache` is also `false`.\n *\n * @default false\n */\n clearCache?: boolean;\n}\n\nexport interface DatabaseOptions extends DatabaseCacheOptions {\n /**\n * Database connection URL\n */\n url?: string;\n\n /**\n * Authentication token for the database connection\n */\n authToken?: string;\n\n /**\n * How long a queued transaction waits for the connection, in milliseconds.\n *\n * Applies to the single-connection adapters — SQLite (both the LibSQL and\n * native paths), DuckDB and JSON. Those drive one connection, so transactions\n * run one at a time and an overlapping `transaction()` waits its turn.\n * PostgreSQL pools and ignores this.\n *\n * The clock starts when the call queues, not when the connection frees, so\n * this bounds the total wait rather than any single transaction: raise it for\n * workloads with long transactions *or* with sustained bursts on one\n * connection. Must be positive and finite.\n *\n * Read once, when the connection is created. Adapters that cache connections\n * hand a later `getDatabase()` for the same database the existing one, which\n * keeps the timeout the first caller asked for.\n *\n * @default 30000\n */\n transactionQueueTimeout?: number;\n}\n\n/**\n * DuckDB-specific connection options\n */\nexport interface DuckDBOptions extends DatabaseOptions {\n /**\n * Database type identifier\n */\n type?: 'duckdb';\n\n /**\n * Path to directory containing JSON files to auto-register as tables\n * @default './data'\n */\n dataDir?: string;\n\n /**\n * Automatically register JSON files in dataDir as queryable tables\n * @default true\n */\n autoRegisterJSON?: boolean;\n\n /**\n * Strategy for writing data back to JSON files\n * - 'immediate': Write changes to JSON files immediately\n * - 'manual': Require explicit export calls\n * - 'none': Read-only mode, no writes to JSON files\n * @default 'none'\n */\n writeStrategy?: 'immediate' | 'manual' | 'none';\n\n /**\n * Whether to use a persistent DuckDB file for caching/indexes\n * If true, uses url or defaults to ':memory:'\n * @default false\n */\n persistent?: boolean;\n\n /**\n * Accepted for uniform configuration. DuckDB creates a fresh adapter on\n * every call, so this option does not change its behavior.\n */\n cache?: boolean;\n\n /**\n * Accepted for uniform configuration. DuckDB has no shared cache to evict,\n * so this option does not change its behavior.\n */\n clearCache?: boolean;\n\n /**\n * Explicit schema definitions for tables\n * When provided, these schemas will be used for table creation\n */\n schemas?: SchemasOption;\n}\n\n/**\n * A JSON data file that parsed but whose records could not be loaded into the\n * table — e.g. a renamed or dropped column, a NOT NULL / PRIMARY KEY violation,\n * or a file whose fields match no column on the table.\n *\n * The JSON adapter keeps the table (empty) and continues loading every other\n * table, logging the failure to stderr. This record makes the same failure\n * observable to calling code so a table that silently stays empty can be\n * detected. See issue #1139.\n */\nexport interface JSONTableLoadError {\n /** Name of the table whose data file failed to load */\n table: string;\n /** Path to the JSON data file that could not be loaded */\n filePath: string;\n /**\n * The underlying failure. Typically a `DatabaseError` whose `context`\n * carries the engine-level cause (e.g. the DuckDB constraint message).\n */\n error: unknown;\n}\n\n/**\n * JSON database adapter options (DuckDB-backed)\n *\n * Uses DuckDB's in-memory engine to query JSON files directly.\n * No WAL files or persistent database files are created.\n */\nexport interface JSONOptions extends DatabaseOptions {\n /**\n * Database type identifier\n */\n type: 'json';\n\n /**\n * Path to directory containing JSON files (required)\n * JSON files in this directory will be loaded as queryable tables\n */\n url: string;\n\n /**\n * Automatically load all JSON files in dataDir as tables\n * @default true\n */\n autoRegister?: boolean;\n\n /**\n * Strategy for writing changes back to JSON files\n * - 'immediate': Auto-save after every insert/update (default)\n * - 'manual': Require explicit exportTable() calls\n * - 'none': Read-only mode, throws error on writes\n * @default 'immediate'\n */\n writeStrategy?: 'immediate' | 'manual' | 'none';\n\n /**\n * How long a queued transaction waits for the connection, in milliseconds.\n *\n * Applies to the single-connection adapters — SQLite (both the LibSQL and\n * native paths), DuckDB and JSON. Those drive one connection, so transactions\n * run one at a time and an overlapping `transaction()` waits its turn.\n * PostgreSQL pools and ignores this.\n *\n * The clock starts when the call queues, not when the connection frees, so\n * this bounds the total wait rather than any single transaction: raise it for\n * workloads with long transactions *or* with sustained bursts on one\n * connection. Must be positive and finite.\n *\n * Read once, when the connection is created. Adapters that cache connections\n * hand a later `getDatabase()` for the same database the existing one, which\n * keeps the timeout the first caller asked for.\n *\n * @default 30000\n */\n transactionQueueTimeout?: number;\n\n /**\n * Explicit schema definitions for tables\n *\n * When provided, these schemas will be used instead of auto-detection.\n * This allows frameworks to provide explicit schemas via dependency injection.\n *\n * @example\n * ```typescript\n * const db = await getDatabase({\n * type: 'json',\n * url: './data',\n * schemas: {\n * users: {\n * tableName: 'users',\n * ddl: 'CREATE TABLE users (id TEXT PRIMARY KEY, name TEXT)',\n * indexes: ['CREATE INDEX idx_users_name ON users(name)']\n * }\n * }\n * });\n * ```\n */\n schemas?: SchemasOption;\n\n /**\n * Unique identifier for in-memory DuckDB instances to enable connection sharing\n * When multiple getDatabase() calls use the same dbid, they receive\n * the same database connection instance.\n *\n * Auto-generated if not provided, enabling connection sharing by default.\n * This solves the issue where multiple collections create separate in-memory\n * databases that can't see each other's tables.\n */\n dbid?: string;\n\n /**\n * Eagerly load all JSON files as tables at connection time\n *\n * When true (default), creates tables with inferred schemas for all JSON files\n * in the data directory, even those without explicit schemas or .schema.sql files.\n * This enables cross-table queries (JOINs, NOT EXISTS subqueries) before\n * syncSchema() is called.\n *\n * Note: Inferred schemas don't include UNIQUE constraints. UPSERT operations\n * require syncSchema() to be called first to establish proper constraints.\n * When syncSchema() is called, tables with inferred schemas are dropped and\n * recreated with the proper schema.\n *\n * @default true\n */\n eagerLoadTables?: boolean;\n\n /**\n * Evict and close the existing database connection before creating a fresh one\n *\n * The new adapter is cached unless `cache` is also false. Eviction is awaited.\n * When automatic file registration is enabled, JSON adapters detect changed,\n * added, or removed `.json` and companion `.schema.sql` files on a later\n * acquisition. Use this option when a caller needs an unconditional reload\n * even if those sources are unchanged. Existing references point to the\n * closed adapter after replacement.\n *\n * @default false\n *\n * @example\n * // Initial connection - cached\n * const db1 = await getDatabase({ type: 'json', url: './data' });\n *\n * // Force an unconditional replacement...\n *\n * // Force reload from disk\n * const db2 = await getDatabase({\n * type: 'json',\n * url: './data',\n * clearCache: true\n * });\n */\n clearCache?: boolean;\n\n /**\n * Called when a JSON data file parses but its records cannot be loaded into a\n * table that was created for it — from a provided schema, a `.schema.sql`\n * file, or deferred `syncSchema()` / `execute()` DDL — leaving that table\n * present but empty (renamed/dropped column, NOT NULL / PRIMARY KEY violation,\n * or a file whose fields match no column). Every other table still loads.\n *\n * This targets the *silent* failure: a present-but-empty table a query cannot\n * tell apart from a legitimately empty one. A file with no schema at all is\n * loaded by DuckDB's own inference; if that fails the table is simply not\n * created, which already surfaces as an error when you query it, so it is not\n * reported here.\n *\n * Fires for both connection-time and deferred (`syncSchema()` / `execute()`)\n * loads. Without it, such a failure is only visible as a `console.error` on\n * stderr. The same failures are also retrievable after the fact via the\n * adapter's `getTableLoadErrors()` method.\n *\n * Registered once, on the `getDatabase()` call that creates the connection.\n * The JSON adapter caches connections per URL, so a later `getDatabase()` for\n * the same directory returns the existing connection and does *not* re-wire a\n * different `onTableLoadError` — read `getTableLoadErrors()` on the returned\n * adapter instead, which reflects the shared connection's failures.\n *\n * A callback that throws is caught and ignored so it cannot break loading of\n * the remaining tables. See issue #1139.\n */\n onTableLoadError?: (info: JSONTableLoadError) => void;\n}\n\n/**\n * Result of a database operation that modifies data\n */\nexport interface QueryResult {\n /**\n * Type of operation performed (e.g., \"insert\", \"update\", \"delete\")\n */\n operation: string;\n\n /**\n * Number of rows affected by the operation\n */\n affected: number;\n}\n\n/**\n * Options for upsert operations.\n */\nexport interface UpsertOptions {\n /**\n * Preserve database-native NULL-distinct conflict behavior.\n *\n * By default, upsert treats NULL values in conflict columns as matching\n * existing NULL values. Set this to true to opt out and use the underlying\n * database's native ON CONFLICT behavior where NULLs are distinct.\n */\n nullsDistinct?: boolean;\n}\n\n/**\n * Schema definition that can be provided to database adapters\n *\n * Allows frameworks like SMRT to provide explicit schemas instead of\n * relying on auto-detection or circular dependencies.\n *\n * This is a simpler, DDL-based alternative to SchemaDefinition which is\n * used for structured JSON manifests.\n */\nexport interface SchemaProvider {\n /**\n * Table name in snake_case plural form\n */\n tableName: string;\n\n /**\n * DDL statement(s) to create the table\n * Can be a single CREATE TABLE statement or multiple statements\n */\n ddl: string;\n\n /**\n * Index creation statements (optional)\n * Each string should be a complete CREATE INDEX statement\n */\n indexes?: string[];\n\n /**\n * Trigger creation statements (optional, adapter-specific support)\n * Each string should be a complete CREATE TRIGGER statement\n */\n triggers?: string[];\n\n /**\n * Schema version for migration tracking (optional)\n */\n version?: string;\n\n /**\n * Field metadata (optional, for advanced use cases)\n * Can be used by frameworks for runtime type information and validation\n */\n fields?: Map<string, any>;\n}\n\n/**\n * Schema definitions for getDatabase(), either eagerly built or lazy.\n *\n * Callers can pass schemas as a lazy function so that building the schema\n * map (potentially 200+ objects from ObjectRegistry) is deferred until an\n * adapter actually needs it. Adapters that manage tables via migrations\n * (Postgres, SQLite) never call the function, making the cost zero.\n *\n * @example Lazy — only resolved by JSON/DuckDB adapters\n * ```typescript\n * await getDatabase({\n * type: 'postgres',\n * url: '...',\n * schemas: () => ObjectRegistry.getAllSchemas(), // never called\n * });\n * ```\n *\n * @example Eager — built upfront (fine for JSON/DuckDB)\n * ```typescript\n * await getDatabase({\n * type: 'json',\n * url: './data',\n * schemas: { users: { tableName: 'users', ddl: '...' } },\n * });\n * ```\n */\nexport type SchemasOption =\n | Record<string, SchemaProvider>\n | (() => Record<string, SchemaProvider>);\n\n/**\n * Resolves a SchemasOption to its concrete value.\n *\n * Adapters that need schemas (JSON, DuckDB) call this to unwrap the\n * lazy function. Adapters that don't (Postgres, SQLite) simply ignore\n * the `schemas` option without calling this.\n */\nexport function resolveSchemas(\n schemas: SchemasOption | undefined,\n): Record<string, SchemaProvider> | undefined {\n if (!schemas) return undefined;\n if (typeof schemas === 'function') return schemas();\n return schemas;\n}\n\n/**\n * Schema manifest structure for JSON-based schema management\n */\nexport interface SchemaManifest {\n version: string;\n timestamp: number;\n packageName: string;\n schemas: Record<string, SchemaDefinition>;\n dependencies: string[];\n}\n\n/**\n * Schema definition for individual tables\n */\nexport interface SchemaDefinition {\n tableName: string;\n columns: Record<string, ColumnDefinition>;\n indexes: IndexDefinition[];\n triggers: TriggerDefinition[];\n foreignKeys: ForeignKeyDefinition[];\n dependencies: string[];\n version: string;\n packageName: string;\n baseClass?: string;\n}\n\n/**\n * Column definition structure\n */\nexport interface ColumnDefinition {\n type: string;\n primaryKey?: boolean;\n unique?: boolean;\n notNull?: boolean;\n defaultValue?: any;\n check?: string;\n foreignKey?: {\n table: string;\n column: string;\n onDelete?: string;\n onUpdate?: string;\n };\n description?: string;\n}\n\n/**\n * Column definition with name for ALTER TABLE operations\n */\nexport interface ColumnDefinitionWithName extends ColumnDefinition {\n name: string;\n}\n\n/**\n * Index definition structure\n */\nexport interface IndexDefinition {\n name: string;\n columns: string[];\n unique?: boolean;\n where?: string;\n description?: string;\n}\n\n/**\n * Trigger definition structure\n */\nexport interface TriggerDefinition {\n name: string;\n when: string;\n event: string;\n table: string;\n condition?: string;\n body: string;\n description?: string;\n}\n\n/**\n * Foreign key constraint definition\n */\nexport interface ForeignKeyDefinition {\n column: string;\n referencesTable: string;\n referencesColumn: string;\n onDelete?: string;\n onUpdate?: string;\n}\n\n/**\n * Options for schema initialization and management\n */\nexport interface SchemaInitializationOptions {\n /** Schema manifest containing table definitions */\n manifest?: SchemaManifest;\n /** Raw SQL DDL schema string (legacy support) */\n schema?: string;\n /** Schema overrides for extending base schemas */\n overrides?: Record<string, SchemaDefinition>;\n /** Force recreation of tables */\n force?: boolean;\n /** Enable debug logging */\n debug?: boolean;\n}\n\n/**\n * Table schema information returned by getTableSchema()\n */\nexport interface TableSchemaInfo {\n /** Table name */\n tableName: string;\n /** Columns in the table */\n columns: Record<string, ColumnDefinition>;\n /** Indexes on the table */\n indexes: IndexDefinition[];\n /** Foreign key constraints */\n foreignKeys: ForeignKeyDefinition[];\n}\n\n/**\n * Options for vector similarity search\n */\nexport interface VectorSearchOptions {\n /** Maximum number of results to return */\n limit?: number;\n /** Distance metric for similarity comparison @default 'cosine' */\n metric?: 'cosine' | 'l2' | 'ip';\n /**\n * Additional SQL WHERE clause.\n * Use $2, $3, etc. for parameter placeholders ($1 is reserved for the query vector).\n * @example `category = $2`\n */\n where?: string;\n /** Parameters for the WHERE clause, bound to $2, $3, etc. */\n params?: any[];\n}\n\n/**\n * Result of a vector similarity search\n */\nexport interface VectorSearchResult {\n /** Row primary key */\n id: string;\n /**\n * Distance from query vector (lower = more similar).\n * Range varies by metric: cosine [0, 2], L2 [0, Infinity), ip (-Infinity, Infinity).\n */\n distance: number;\n /** Additional columns from the row */\n [key: string]: any;\n}\n\n/**\n * Options for creating a vector index\n */\nexport interface VectorIndexOptions {\n /** Number of dimensions (informational only, dimensions are set via ensureColumn) */\n dimensions?: number;\n /** Distance metric @default 'cosine' */\n metric?: 'cosine' | 'l2' | 'ip';\n /** Index type @default 'hnsw' */\n type?: 'hnsw' | 'ivfflat';\n}\n\n/**\n * Vector operations capability for database adapters\n *\n * Provides native vector similarity search, storage, and indexing.\n * Implemented by PostgreSQL via pgvector and optionally by SQLite via\n * sqlite-vector.\n */\nexport interface VectorCapabilities {\n /**\n * Search for similar vectors\n *\n * @param table - Table containing vector column\n * @param column - Name of the vector column\n * @param embedding - Query vector\n * @param options - Search options\n * @returns Ranked results by similarity\n */\n search(\n table: string,\n column: string,\n embedding: number[],\n options?: VectorSearchOptions,\n ): Promise<VectorSearchResult[]>;\n\n /**\n * Ensure a vector column exists on a table\n *\n * @param table - Table name\n * @param column - Column name\n * @param dimensions - Number of vector dimensions\n */\n ensureColumn(\n table: string,\n column: string,\n dimensions: number,\n ): Promise<void>;\n\n /**\n * Ensure a vector index exists on a column\n *\n * @param table - Table name\n * @param column - Column name\n * @param options - Index options\n */\n ensureIndex(\n table: string,\n column: string,\n options?: VectorIndexOptions,\n ): Promise<void>;\n\n /**\n * Store or update a vector in a specific row\n *\n * @param table - Table name\n * @param where - Conditions to identify the row\n * @param column - Vector column name\n * @param embedding - Vector data\n */\n upsertVector(\n table: string,\n where: Record<string, any>,\n column: string,\n embedding: number[],\n ): Promise<void>;\n}\n\n/**\n * SQLite notification delivered by optional notification capabilities.\n */\nexport interface DatabaseNotification {\n id: number;\n channel: string;\n payload: any;\n createdAt?: number | string | Date | null;\n}\n\n/**\n * Options for notification listeners.\n */\nexport interface NotificationListenOptions {\n /**\n * Fallback poll interval in milliseconds when the native watcher does not\n * receive an update signal.\n */\n fallbackPollMs?: number | null;\n}\n\n/**\n * Options for waiting on the next database update.\n */\nexport interface NotificationWaitOptions {\n /**\n * Maximum time to wait in milliseconds. When omitted, waits indefinitely.\n */\n timeoutMs?: number;\n\n /**\n * Optional abort signal to cancel the wait.\n */\n signal?: AbortSignal;\n}\n\n/**\n * Options for pruning persisted notification rows.\n */\nexport interface NotificationPruneOptions {\n /** Delete notifications older than this many seconds. */\n olderThanS?: number | null;\n /** Keep at most this many most-recent notifications. */\n maxKeep?: number | null;\n}\n\n/**\n * Optional notification/pub-sub capabilities for database adapters.\n */\nexport interface NotificationCapabilities {\n notify(channel: string, payload: any): Promise<number>;\n listen(\n channel: string,\n options?: NotificationListenOptions,\n ): AsyncIterable<DatabaseNotification>;\n waitForUpdate(options?: NotificationWaitOptions): Promise<boolean>;\n prune(options?: NotificationPruneOptions): Promise<number>;\n}\n\n/**\n * Options for SQLite native notification support.\n */\nexport interface SqliteNotificationCapabilityOptions {\n watcherBackend?: 'polling' | 'kernel' | 'shm';\n maxReaders?: number;\n}\n\n/**\n * Options for SQLite native vector support.\n */\nexport interface SqliteVectorCapabilityOptions {\n preload?: boolean;\n quantization?: 'turbo4' | 'turbo3' | 'turbo2' | 'uint8' | 'int8' | '1bit';\n maxMemory?: string;\n}\n\n/**\n * Optional SQLite-only native capabilities.\n */\nexport interface SqliteCapabilitiesOptions {\n notifications?: boolean | SqliteNotificationCapabilityOptions;\n vector?: boolean | SqliteVectorCapabilityOptions;\n}\n\n/**\n * Common interface for database adapters\n * Provides a unified API for different database backends\n */\nexport interface DatabaseInterface {\n /**\n * Database location identifier\n * For file-based databases: file path\n * For directory-based databases (JSON/DuckDB with JSON): directory path\n * For in-memory databases: ':memory:'\n * For remote databases: connection URL\n */\n url: string;\n\n /**\n * Underlying database client instance\n */\n client: any;\n\n /**\n * Inserts one or more records into a table\n *\n * @param table - Table name\n * @param data - Single record or array of records to insert\n * @returns Promise resolving to operation result\n */\n insert: (\n table: string,\n data: Record<string, any> | Record<string, any>[],\n ) => Promise<QueryResult>;\n\n /**\n * Retrieves a single record matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to matching record or null if not found\n */\n get: (\n table: string,\n where: WhereClause,\n ) => Promise<Record<string, any> | null>;\n\n /**\n * Retrieves multiple records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to array of matching records\n */\n list: (table: string, where: WhereClause) => Promise<Record<string, any>[]>;\n\n /**\n * Updates records matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records to update (object for AND-only, 2D array for OR/AND)\n * @param data - New data to set\n * @returns Promise resolving to operation result\n */\n update: (\n table: string,\n where: WhereClause,\n data: Record<string, any>,\n ) => Promise<QueryResult>;\n\n /**\n * Inserts a record or updates it if it already exists (UPSERT)\n * Uses database-specific ON CONFLICT / ON DUPLICATE KEY syntax\n *\n * @param table - Table name\n * @param conflictColumns - Columns that define the uniqueness constraint\n * @param data - Data to insert or update\n * @returns Promise resolving to operation result\n *\n * @example\n * ```typescript\n * // Upsert a user by email\n * await db.upsert('users', ['email'], {\n * email: 'user@example.com',\n * name: 'John Doe',\n * updated_at: new Date().toISOString()\n * });\n *\n * // Upsert with composite key\n * await db.upsert('settings', ['user_id', 'key'], {\n * user_id: '123',\n * key: 'theme',\n * value: 'dark'\n * });\n * ```\n */\n upsert: (\n table: string,\n conflictColumns: string[],\n data: Record<string, any>,\n options?: UpsertOptions,\n ) => Promise<QueryResult>;\n\n /**\n * Gets a record matching the where criteria or inserts it if not found\n *\n * @param table - Table name\n * @param where - Criteria to match existing record (object for AND-only, 2D array for OR/AND)\n * @param data - Data to insert if no record found\n * @returns Promise resolving to the record (either retrieved or newly inserted)\n */\n getOrInsert: (\n table: string,\n where: WhereClause,\n data: Record<string, any>,\n ) => Promise<Record<string, any>>;\n\n /**\n * Deletes records from a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records for deletion (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to operation result with count of deleted rows\n *\n * @example\n * ```typescript\n * // Delete by ID\n * await db.delete('products', { id: 'prod-123' });\n *\n * // Delete by criteria\n * await db.delete('orders', { status: 'cancelled' });\n *\n * // Delete with multiple conditions\n * await db.delete('sessions', {\n * user_id: 'user-123',\n * expired: true\n * });\n * ```\n */\n delete: (table: string, where: WhereClause) => Promise<QueryResult>;\n\n /**\n * Counts records in a table matching the where criteria\n *\n * @param table - Table name\n * @param where - Criteria to match records (optional, counts all if omitted; object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to count of matching records\n *\n * @example\n * ```typescript\n * // Count all records\n * const total = await db.count('products');\n *\n * // Count with single condition\n * const activeCount = await db.count('products', { active: true });\n *\n * // Count with multiple conditions\n * const filteredCount = await db.count('orders', {\n * status: 'pending',\n * priority: 'high'\n * });\n * ```\n */\n count: (table: string, where?: WhereClause) => Promise<number>;\n\n /**\n * Creates a table-specific interface for simplified table operations\n *\n * @param table - Table name\n * @returns TableInterface for the specified table\n */\n table: (table: string) => TableInterface;\n\n /**\n * Checks if a table exists in the database\n *\n * @param table - Table name\n * @returns Promise resolving to boolean indicating existence\n */\n tableExists: (table: string) => Promise<boolean>;\n\n /**\n * Whether this adapter requires schema existence checks at runtime.\n *\n * Set to `true` on adapters where tables may not exist yet and need\n * to be verified before use (e.g. JSON/DuckDB with auto-created tables).\n *\n * When absent or `false`, frameworks skip `tableExists()` calls during\n * collection initialization — tables are assumed to exist because\n * schema is managed by migrations (e.g. `smrt db:migrate`).\n *\n * - Postgres adapter: not set (migration-managed)\n * - SQLite adapter: not set (migration-managed)\n * - JSON/DuckDB adapter: `true` (tables may be auto-created)\n */\n requiresSchemaCheck?: boolean;\n\n /**\n * Executes a SQL query using template literals and returns multiple rows\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to array of result records\n */\n many: (\n strings: TemplateStringsArray,\n ...vars: any[]\n ) => Promise<Record<string, any>[]>;\n\n /**\n * Executes a SQL query using template literals and returns a single row\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single result record or null\n */\n single: (\n strings: TemplateStringsArray,\n ...vars: any[]\n ) => Promise<Record<string, any> | null>;\n\n /**\n * Executes a SQL query using template literals and returns a single value\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise resolving to a single value (first column of first row)\n */\n pluck: (strings: TemplateStringsArray, ...vars: any[]) => Promise<any>;\n\n /**\n * Executes a SQL query using template literals without returning results\n *\n * @param strings - Template strings\n * @param vars - Variables to interpolate into the query\n * @returns Promise that resolves when the query completes\n */\n execute: (strings: TemplateStringsArray, ...vars: any[]) => Promise<void>;\n\n /**\n * Alias for many() - Executes a SQL query and returns multiple rows\n */\n oo: (\n strings: TemplateStringsArray,\n ...vars: any[]\n ) => Promise<Record<string, any>[]>;\n\n /**\n * Alias for single() - Executes a SQL query and returns a single row\n */\n oO: (\n strings: TemplateStringsArray,\n ...vars: any[]\n ) => Promise<Record<string, any> | null>;\n\n /**\n * Alias for pluck() - Executes a SQL query and returns a single value\n */\n ox: (strings: TemplateStringsArray, ...vars: any[]) => Promise<any>;\n\n /**\n * Alias for execute() - Executes a SQL query without returning results\n */\n xx: (strings: TemplateStringsArray, ...vars: any[]) => Promise<void>;\n\n /**\n * Executes a raw SQL query with parameterized values\n *\n * Raw queries use the adapter's native placeholder syntax.\n * Parameters may be passed as rest arguments or as one values array.\n * For adapter-specific array parameter caveats, see the package docs.\n *\n * @param str - SQL query string\n * @param vars - Variables to use as parameters\n * @returns Promise resolving to query result with rows and count\n */\n query: (\n str: string,\n ...vars: any[]\n ) => Promise<{ rows: Record<string, any>[]; rowCount: number }>;\n\n /**\n * Synchronizes database schema with provided SQL DDL\n * Creates tables if they don't exist and adds missing columns\n *\n * @param schema - SQL schema definition with CREATE TABLE statements\n * @returns Promise that resolves when schema is synchronized\n */\n syncSchema?: (schema: string) => Promise<void>;\n\n /**\n * Initialize database schemas from JSON manifest\n * Supports dependency resolution and schema overrides\n *\n * @param options - Schema initialization options\n * @returns Promise that resolves when schemas are initialized\n */\n initializeSchemas?: (options: SchemaInitializationOptions) => Promise<void>;\n\n /**\n * Executes a callback within a database transaction\n * Automatically commits on success or rolls back on error\n *\n * Calling `transaction()` again on the `tx` handed to a callback is\n * adapter-specific — do not assume one behaviour across adapters:\n *\n * - **SQLite** (both the libsql and native-capabilities paths) — the nested\n * scope re-enters the transaction already in progress under a `SAVEPOINT`.\n * It reads the enclosing transaction's uncommitted rows, and if it throws,\n * only its own work is rolled back; the enclosing transaction stays usable.\n * - **DuckDB and JSON** — the engine has no `SAVEPOINT`, so nesting throws\n * {@link NestedTransactionError} without touching the connection. The\n * enclosing transaction is unaffected and can continue.\n * - **PostgreSQL** — the nested scope re-enters the enclosing transaction\n * under a `SAVEPOINT` on the same pooled connection, like SQLite. It used\n * to check out a second connection and begin an independent transaction,\n * which could not see the enclosing transaction's uncommitted rows and\n * could deadlock against it undetectably; that was fixed in #1108.\n *\n * Code that must run on every adapter should take the transaction as a\n * parameter rather than opening a nested one.\n *\n * **Transactions are serialized per connection on the single-connection\n * adapters** — SQLite (both paths), DuckDB and JSON drive one connection, and\n * a connection can only be in one transaction at a time, so a concurrent\n * `transaction()` waits for the one in progress rather than corrupting it. A\n * queued call that waits longer than `transactionQueueTimeout` (30s by\n * default) rejects rather than stalling indefinitely. Re-entrant calls do not\n * queue — they take the savepoint or refusal path described above.\n * PostgreSQL pools, so top-level transactions run concurrently. Nested scopes\n * on one PostgreSQL transaction use a scope-local queue: concurrently started\n * siblings serialize, while each child gets its own queue for further nesting.\n *\n * @param callback - Function to execute within transaction\n * @returns Promise resolving to callback result\n * @throws {NestedTransactionError} When nesting on an adapter without\n * savepoint support\n *\n * @example Portable across all adapters\n * ```typescript\n * async function recordClick(db: DatabaseInterface, id: string) {\n * await db.update('links', { id }, { clicks: 1 });\n * }\n *\n * await db.transaction(async (tx) => {\n * await tx.query('SELECT * FROM links WHERE id = $1 FOR UPDATE', id);\n * await recordClick(tx, id); // reuses tx; never opens its own\n * });\n * ```\n */\n transaction?: <T>(\n callback: (tx: DatabaseInterface) => Promise<T>,\n ) => Promise<T>;\n\n /**\n * Begins a new transaction and returns a handle for manual control\n *\n * Unlike transaction(), this gives you explicit control over commit/rollback.\n * Ideal for test isolation where you want to rollback after each test.\n *\n * On the single-connection adapters — SQLite (both paths), DuckDB and JSON —\n * the handle owns the connection until {@link TransactionHandle.commit} or\n * {@link TransactionHandle.rollback} is called, so every other transaction on\n * that connection waits in the meantime. **A handle that is never ended holds\n * the connection for the life of the process**, and later transactions then\n * fail with the queue-timeout error instead of proceeding. End the handle in\n * a `finally`, and in per-test setup guard the teardown, because an\n * assertion that throws between `beginTransaction()` and the teardown would\n * otherwise strand it.\n *\n * @returns Promise resolving to a TransactionHandle\n *\n * @example Test isolation pattern\n * ```typescript\n * let tx: TransactionHandle | undefined;\n *\n * beforeEach(async () => {\n * tx = await db.beginTransaction();\n * });\n *\n * afterEach(async () => {\n * // Guarded: if beforeEach threw, there is no handle to end, and an\n * // unguarded rollback would mask that failure with its own.\n * await tx?.rollback(); // Discard all test changes\n * tx = undefined;\n * });\n *\n * it('creates user', async () => {\n * await tx.insert('users', { id: '1', name: 'Test' });\n * const user = await tx.get('users', { id: '1' });\n * expect(user).toBeDefined();\n * // Changes rolled back in afterEach\n * });\n * ```\n */\n beginTransaction?: () => Promise<TransactionHandle>;\n\n /**\n * Acquire a pinned single-connection session.\n *\n * Returns a {@link SessionHandle} whose queries all run on one underlying\n * connection, enabling session-scoped state (e.g. PostgreSQL session advisory\n * locks) that the pooled top-level {@link query} cannot hold reliably. The\n * caller must {@link SessionHandle.release} it.\n *\n * Only meaningful for engines with session-scoped state, so it is currently\n * implemented for PostgreSQL only; check for the method before calling\n * (`if (db.acquireSession)`). Single-connection engines (SQLite, DuckDB) have\n * no session-scoped locks to hold, so they leave it undefined.\n *\n * @returns Promise resolving to a SessionHandle\n *\n * @example Hold a Postgres session advisory lock\n * ```typescript\n * const session = await db.acquireSession();\n * try {\n * await session.query('SELECT pg_advisory_lock($1)', 42);\n * // ... lock is held for the life of this session ...\n * } finally {\n * await session.release(); // frees the lock and drops the connection\n * }\n * ```\n */\n acquireSession?: () => Promise<SessionHandle>;\n\n /**\n * Retrieves the schema information for a table\n *\n * @param table - Table name\n * @returns Promise resolving to table schema info or null if table doesn't exist\n */\n getTableSchema?: (table: string) => Promise<TableSchemaInfo | null>;\n\n /**\n * Vector operations for native similarity search\n *\n * When available, enables database-level vector operations (e.g., pgvector).\n * Consumers should check for this capability rather than checking the database type.\n */\n vector?: VectorCapabilities;\n\n /**\n * Optional notification/pub-sub operations.\n */\n notifications?: NotificationCapabilities;\n\n /**\n * Close the database adapter and release native resources when supported.\n */\n close?: () => Promise<void>;\n\n /**\n * ALTER TABLE operations for schema evolution\n */\n alterTable?: {\n /**\n * Adds a new column to an existing table\n *\n * @param table - Table name\n * @param column - Column definition with name\n * @returns Promise that resolves when column is added\n */\n addColumn: (\n table: string,\n column: ColumnDefinitionWithName,\n ) => Promise<void>;\n\n /**\n * Adds a new index to an existing table\n *\n * @param table - Table name\n * @param index - Index definition\n * @returns Promise that resolves when index is created\n */\n addIndex: (table: string, index: IndexDefinition) => Promise<void>;\n };\n}\n\n/**\n * Transaction handle for manual transaction control\n *\n * Extends DatabaseInterface with commit() and rollback() methods.\n * Use beginTransaction() to obtain a handle, then explicitly\n * commit or rollback when done.\n *\n * @example Test isolation with rollback\n * ```typescript\n * const tx = await db.beginTransaction();\n * try {\n * // All operations happen in the transaction\n * await tx.insert('users', { id: '1', name: 'Test' });\n * const user = await tx.get('users', { id: '1' });\n *\n * // Rollback discards all changes (useful for test isolation)\n * await tx.rollback();\n * } catch (error) {\n * await tx.rollback();\n * throw error;\n * }\n * ```\n *\n * @example Manual commit\n * ```typescript\n * const tx = await db.beginTransaction();\n * try {\n * await tx.insert('orders', { id: '1', total: 100 });\n * await tx.insert('order_items', { order_id: '1', product: 'Widget' });\n * await tx.commit(); // Persist changes\n * } catch (error) {\n * await tx.rollback();\n * throw error;\n * }\n * ```\n */\nexport interface TransactionHandle extends DatabaseInterface {\n /**\n * Commits the transaction, persisting all changes\n *\n * After commit, the transaction handle should not be used.\n */\n commit: () => Promise<void>;\n\n /**\n * Rolls back the transaction, discarding all changes\n *\n * After rollback, the transaction handle should not be used.\n * This is the key method for test isolation - rollback after each test.\n */\n rollback: () => Promise<void>;\n\n /**\n * Whether the transaction is still active (not committed or rolled back)\n */\n isActive: () => boolean;\n}\n\n/**\n * Pinned single-connection session handle.\n *\n * Obtained via {@link DatabaseInterface.acquireSession}. Unlike the pooled\n * top-level `query` (which may run each statement on a different connection),\n * every call on a session handle runs on the *same* underlying connection for\n * the handle's lifetime. This is required for session-scoped state such as\n * PostgreSQL session advisory locks (`pg_advisory_lock`), which must be\n * acquired, observed, and released on one connection — and which release\n * automatically if the connection drops (e.g. the process dies).\n *\n * A session holds one connection out of the pool (default max 20) for its\n * entire lifetime, so it is meant to be held one-per-process, not per-operation.\n * Always {@link SessionHandle.release} the handle when done, and release it\n * *before* calling `db.client.end()` — a pinned connection keeps `pool.end()`\n * from resolving.\n */\nexport interface SessionHandle {\n /**\n * Execute a raw query on this session's pinned connection.\n * Same placeholder/return contract as {@link DatabaseInterface.query}.\n * Throws if the session has been released or its connection was lost.\n */\n query: (\n sql: string,\n ...vars: any[]\n ) => Promise<{ rows: Record<string, any>[]; rowCount: number }>;\n\n /**\n * Whether the session is still usable — not released and its connection has\n * not errored/dropped. A long-lived lock holder can poll this without\n * issuing a query.\n */\n isActive: () => boolean;\n\n /**\n * Release the session. Idempotent. After release the handle must not be used.\n * Best-effort frees session-scoped locks, then drops the underlying\n * connection (it is NOT returned to the pool) so the database releases any\n * remaining session state — returning it to the pool would leak that state\n * onto a later, unrelated checkout.\n */\n release: () => Promise<void>;\n}\n\n/**\n * Simplified interface for table-specific operations\n */\nexport interface TableInterface {\n /**\n * Inserts one or more records into the table\n *\n * @param data - Single record or array of records to insert\n * @returns Promise resolving to operation result\n */\n insert: (\n data: Record<string, any> | Record<string, any>[],\n ) => Promise<QueryResult>;\n\n /**\n * Retrieves a single record from the table matching the where criteria\n *\n * @param where - Criteria to match records (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to matching record or null if not found\n */\n get: (where: WhereClause) => Promise<Record<string, any> | null>;\n\n /**\n * Retrieves multiple records from the table matching the where criteria\n *\n * @param where - Criteria to match records (object for AND-only, 2D array for OR/AND)\n * @returns Promise resolving to array of matching records\n */\n list: (where: WhereClause) => Promise<Record<string, any>[]>;\n}\n"],"mappings":";;;;;;;;;AASA,IAAa,uCAAuC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDpD,SAAgB,sBACd,SACA,YAAoB,sCACH;CAKjB,IAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,GAC9C,MAAM,IAAI,cACR,6EACA;EAAE;EAAS;CAAU,CACvB;CAGF,IAAI,OAAsB,QAAQ,QAAQ;CAC1C,IAAI,OAAO;CAEX,MAAM,UAAU,YAAiC;EAC/C,MAAM,WAAW;EACjB,IAAI,gBAA4B,CAAC;EACjC,MAAM,UAAU,IAAI,SAAe,YAAY;GAC7C,UAAU;EACZ,CAAC;EAID,OAAO,SAAS,WACR,eACA,OACR;EAEA,IAAI;EACJ,IAAI;GACF,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,QAAQ,iBAAiB;KACvB,OACE,IAAI,cACF,mBAAmB,UAAU,qBAAqB,QAAQ,sbAC1D;MAAE;MAAS;KAAU,CACvB,CACF;IACF,GAAG,SAAS;IACZ,SAAS,KAAK,SAAS,OAAO;GAChC,CAAC;EACH,SAAS,OAAO;GAGd,QAAQ;GACR,MAAM;EACR,UAAU;GACR,IAAI,OAAO,aAAa,KAAK;EAC/B;EAEA,IAAI,WAAW;EACf,OAAO;EACP,aAAa;GACX,IAAI,UAAU;GACd,WAAW;GACX,OAAO;GACP,QAAQ;EACV;CACF;CAEA,MAAM,MAAM,OAAU,SAAuC;EAC3D,MAAM,UAAU,MAAM,QAAQ;EAC9B,IAAI;GACF,OAAO,MAAM,KAAK;EACpB,UAAU;GACR,QAAQ;EACV;CACF;CAEA,OAAO;EACL;EACA,IAAI,OAAO;GACT,OAAO;EACT;EACA;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;ACpHA,IAAa,yBAAb,cAA4C,cAAc;CACxD,YAAY,SAAiB,SAAmC;EAC9D,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;CACd;AACF;;;;;;;;AA8bA,SAAgB,eACd,SAC4C;CAC5C,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,IAAI,OAAO,YAAY,YAAY,OAAO,QAAQ;CAClD,OAAO;AACT"}
|