@stacksjs/database 0.74.51 → 0.74.53

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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Exact match rather than `includes`: each message is Bun's own, thrown by one
3
+ * guard, and nothing that merely quotes it is the same failure.
4
+ */
5
+ export declare function isBrokenBunPoolError(error: unknown): boolean;
6
+ export declare function describeDatabasePool(pool: DatabasePoolIdentity): string;
7
+ export declare function createBrokenPoolDetector(options?: { log?: (line: string) => void, now?: () => number }): BrokenPoolDetector;
8
+ /** Record a query error from `pool`. Anything but Bun's broken-slot error is ignored. */
9
+ export declare function recordDatabaseQueryError(error: unknown, pool: DatabasePoolIdentity): boolean;
10
+ /** Every pool this process has seen broken. Empty until one is; never shrinks. */
11
+ export declare function getBrokenDatabasePools(): BrokenDatabasePool[];
12
+ /**
13
+ * Loud detection of Bun SQL pools broken by oven-sh/bun#42804. Detection
14
+ * only: nothing here retries, repairs or replaces a pool.
15
+ *
16
+ * When PostgreSQL ends a connection while Bun's pool is still opening it, Bun
17
+ * 1.4.1 can put that pool slot back into its connected state with no native
18
+ * connection behind it (robobun's analysis on the issue). From then on every
19
+ * query the pool routes to the slot rejects at once with a plain `Error` whose
20
+ * message is exactly `connection must be a PostgresSQLConnection` and which
21
+ * carries no `code`, and the pool's close() was still pending 2 seconds later
22
+ * in local runs. The upstream fix is oven-sh/bun#40913, open and unmerged at
23
+ * the time of writing.
24
+ *
25
+ * A broken pool is not expected to recover. In a local reproduction Bun
26
+ * rejected all 31 queries sent to one over 30 seconds and opened no new
27
+ * connection in that time, so a pool recorded here stays recorded until the
28
+ * process exits. Restarting the process is the remedy, and making that
29
+ * visible is the point: one error line per pool, and a record a health check
30
+ * can read (`getBrokenDatabasePools`).
31
+ *
32
+ * The message is Bun's generic guard, not unique to #42804: a raw reserved
33
+ * connection used after reserved.close() rejects the same way. That case could
34
+ * not be reached through Stacks' own query paths locally (a reserved builder's
35
+ * close() hangs instead), so a match is read as #42804.
36
+ *
37
+ * MySQL is matched too, from Bun's source, and was not reproduced locally.
38
+ * Bun's MySQL query runner has the same guard as the Postgres one
39
+ * (`connection must be a MySQLConnection`, thrown the same way, in
40
+ * src/sql_jsc/mysql/JSMySQLQuery.rs at bun-v1.4.1), and oven-sh/bun#40913
41
+ * changes Bun's MySQL connection code for the same ordering bug. Killing a
42
+ * MySQL 8.4 pool's sessions while it opened them produced the message in none
43
+ * of 160 local trials.
44
+ */
45
+ /** The exact message Bun's Postgres adapter rejects with for a broken slot. */
46
+ export declare const BUN_BROKEN_POSTGRES_POOL_MESSAGE: 'connection must be a PostgresSQLConnection';
47
+ /** The same guard in Bun's MySQL adapter, matched from Bun's source (see above). */
48
+ export declare const BUN_BROKEN_MYSQL_POOL_MESSAGE: 'connection must be a MySQLConnection';
49
+ /** Which pool, without credentials. */
50
+ export declare interface DatabasePoolIdentity {
51
+ driver: string
52
+ host: string
53
+ port?: number
54
+ database: string
55
+ }
56
+ export declare interface BrokenDatabasePool extends DatabasePoolIdentity {
57
+ detectedAt: number
58
+ }
59
+ export declare interface BrokenPoolDetector {
60
+ record: (error: unknown, pool: DatabasePoolIdentity) => boolean
61
+ pools: () => BrokenDatabasePool[]
62
+ }
@@ -0,0 +1 @@
1
+ export const BUN_BROKEN_POSTGRES_POOL_MESSAGE="connection must be a PostgresSQLConnection",BUN_BROKEN_MYSQL_POOL_MESSAGE="connection must be a MySQLConnection";const BROKEN_POOL_MESSAGES=new Set([BUN_BROKEN_POSTGRES_POOL_MESSAGE,BUN_BROKEN_MYSQL_POOL_MESSAGE]);export function isBrokenBunPoolError(error){return typeof error==="object"&&error!==null&&BROKEN_POOL_MESSAGES.has(error.message)}export function describeDatabasePool(pool){return`${pool.driver} ${pool.host}${pool.port?`:${pool.port}`:""}/${pool.database}`}export function createBrokenPoolDetector(options={}){const log=options.log??((line)=>console.error(line)),now=options.now??Date.now,broken=new Map,attributed=new WeakSet;return{record(error,pool){if(!isBrokenBunPoolError(error)||attributed.has(error))return!1;attributed.add(error);const key=describeDatabasePool(pool);if(broken.has(key))return!1;broken.set(key,{...pool,detectedAt:now()});log(`[database] oven-sh/bun#42804: a connection in the Bun SQL pool for ${key} rejected a query with "${error.message}". Bun is not expected to recover it, so queries routed to it can keep failing until this process restarts. Stacks does not repair or replace the pool; restart the process.`);return!0},pools:()=>[...broken.values()]}}const detector=createBrokenPoolDetector();export function recordDatabaseQueryError(error,pool){return detector.record(error,pool)}export function getBrokenDatabasePools(){return detector.pools()}
@@ -1 +1 @@
1
- import process from"node:process";import{SQL}from"bun";import{env as envVars}from"@stacksjs/env";import{DB_HOST_DEFAULT,DB_NAMES,DB_PORTS,DB_USERS,getConnectionDefaults}from"./defaults";const DEFAULT_TIMEOUT_MS=1e4,UNSAFE_IDENTIFIER_CHARS=/["'`\\;\0\n\r]/,MAX_IDENTIFIER_LENGTH=63;export function isValidDatabaseIdentifier(name){if(!name||name.length>MAX_IDENTIFIER_LENGTH)return!1;return!UNSAFE_IDENTIFIER_CHARS.test(name)}export function quoteIdentifier(dialect,name){return dialect==="mysql"?`\`${name}\``:`"${name}"`}function stripWrappingQuotes(value){return value.replace(/^['"]|['"]$/g,"")}export function classifyConnectionError(error){const e=error,errno=e?.errno,code=typeof e?.code==="string"?e.code:"",message=typeof e?.message==="string"?e.message:String(error??""),sqlState=typeof errno==="string"?errno.toUpperCase():"",mysqlErrno=typeof errno==="number"?errno:Number.NaN;if(sqlState==="3D000")return"missing-database";if(sqlState==="28000"||sqlState==="28P01")return sqlState==="28P01"?"auth-failed":"missing-role";if(sqlState==="42501")return"permission-denied";if(mysqlErrno===1049)return"missing-database";if(mysqlErrno===1045)return"auth-failed";if(mysqlErrno===1044)return"permission-denied";if(mysqlErrno===2002||mysqlErrno===2003)return"server-unreachable";if(code.includes("CONNECTION_REFUSED")||code.includes("CONNECTION_CLOSED")||code==="ECONNREFUSED"||code==="ENOTFOUND"||code==="EHOSTUNREACH")return"server-unreachable";if(code==="ETIMEDOUT"||code.includes("TIMEOUT"))return"timeout";if(/database .* does not exist|unknown database/i.test(message))return"missing-database";if(/role .* does not exist|user .* does not exist/i.test(message))return"missing-role";if(/password authentication failed|access denied for user/i.test(message))return"auth-failed";if(/permission denied|insufficient privilege/i.test(message))return"permission-denied";if(/econnrefused|connection refused|can'?t connect/i.test(message))return"server-unreachable";return"unknown"}export function resolveConnectionTarget(envProxy=envVars){const driver=String(envProxy.DB_CONNECTION||"sqlite");if(driver!=="postgres"&&driver!=="mysql"&&driver!=="singlestore")return null;const dialect=driver==="postgres"?"postgres":"mysql",defaults=getConnectionDefaults(dialect,envProxy),database=stripWrappingQuotes(String(envProxy.DB_DATABASE||defaults.database||DB_NAMES.default)),host=String(envProxy.DB_HOST||defaults.host||DB_HOST_DEFAULT),port=Number(envProxy.DB_PORT||defaults.port||DB_PORTS[dialect]),username=String(envProxy.DB_USERNAME||defaults.username||DB_USERS[dialect]),password=String(envProxy.DB_PASSWORD??defaults.password??"");return{dialect,driver,database,host,port,username,password,maintenanceCandidates:dialect==="postgres"?["postgres","template1"]:["information_schema","mysql"]}}export function buildConnectionUrl(target,database){const scheme=target.dialect==="postgres"?"postgres":"mysql",auth=target.password?`${encodeURIComponent(target.username)}:${encodeURIComponent(target.password)}`:encodeURIComponent(target.username),sslEnv=process.env.DB_SSL,ssl=sslEnv==="true"||sslEnv==="1"?"?ssl=true":"";return`${scheme}://${auth}@${target.host}:${target.port}/${encodeURIComponent(database)}${ssl}`}function defaultConnect(url){return new SQL(url)}async function withConnection(target,database,deps,work){const connect=deps.connect??defaultConnect,timeoutMs=deps.timeoutMs??Number(process.env.DB_PREFLIGHT_TIMEOUT_MS||DEFAULT_TIMEOUT_MS),client=connect(buildConnectionUrl(target,database));let timer;try{const timeout=new Promise((_,reject)=>{timer=setTimeout(()=>{const e=Error(`Timed out after ${timeoutMs}ms connecting to ${target.dialect} at ${target.host}:${target.port}`);e.code="ETIMEDOUT";reject(e)},timeoutMs)});return await Promise.race([work(client),timeout])}finally{if(timer)clearTimeout(timer);try{await client.close()}catch{}}}export async function probeTargetDatabase(target,deps={}){try{await withConnection(target,target.database,deps,async(client)=>client.unsafe("select 1"));return{ok:!0}}catch(error){return{ok:!1,kind:classifyConnectionError(error),error}}}export async function createDatabase(target,deps={}){if(!isValidDatabaseIdentifier(target.database))return{created:!1,kind:"unknown",error:Error(`Refusing to create a database named ${JSON.stringify(target.database)}. Database names must be 1 to 63 characters and may not contain quotes, backslashes, semicolons, or newlines.`)};const identifier=quoteIdentifier(target.dialect,target.database),sql=target.dialect==="mysql"?`CREATE DATABASE IF NOT EXISTS ${identifier}`:`CREATE DATABASE ${identifier}`;let lastError,lastKind="unknown";for(const candidate of target.maintenanceCandidates)try{await withConnection(target,candidate,deps,async(client)=>client.unsafe(sql));return{created:!0,via:candidate}}catch(error){const kind=classifyConnectionError(error),failure=error,message=failure?.message??"",errno=failure?.errno;if(errno==="42P04"||errno===1007||/already exists|database exists/i.test(String(message)))return{created:!1,via:candidate};lastError=error;lastKind=kind;if(kind==="permission-denied"||kind==="auth-failed"||kind==="missing-role")break}return{created:!1,kind:lastKind,error:lastError}}export async function canCreateDatabases(target,deps={}){if(target.dialect!=="postgres")return null;for(const candidate of target.maintenanceCandidates)try{return await withConnection(target,candidate,deps,async(client)=>{const rows=await client.unsafe("select rolcreatedb, rolsuper from pg_roles where rolname = current_user"),row=Array.isArray(rows)?rows[0]:void 0;if(!row)return null;return Boolean(row.rolcreatedb||row.rolsuper)})}catch{}return null}export function manualCreateHint(target){if(target.dialect==="postgres")return`createdb -h ${target.host} -p ${target.port} -U ${target.username} ${target.database}`;return`mysql -h ${target.host} -P ${target.port} -u ${target.username} -e "CREATE DATABASE \\\`${target.database}\\\`"`}export function describeTarget(target){return`the ${target.driver} connection (${target.host}:${target.port}, user "${target.username}")`}
1
+ import process from"node:process";import{SQL}from"bun";import{env as envVars}from"@stacksjs/env";import{DB_HOST_DEFAULT,DB_NAMES,DB_PORTS,DB_USERS,getConnectionDefaults}from"./defaults";const DEFAULT_TIMEOUT_MS=1e4,UNSAFE_IDENTIFIER_CHARS=/["'`\\;\0\n\r]/,MAX_IDENTIFIER_LENGTH=63;export function isValidDatabaseIdentifier(name){if(!name||name.length>MAX_IDENTIFIER_LENGTH)return!1;return!UNSAFE_IDENTIFIER_CHARS.test(name)}export function quoteIdentifier(dialect,name){return dialect==="mysql"?`\`${name}\``:`"${name}"`}function stripWrappingQuotes(value){return value.replace(/^['"]|['"]$/g,"")}export function classifyConnectionError(error){const e=error,errno=e?.errno,code=typeof e?.code==="string"?e.code:"",message=typeof e?.message==="string"?e.message:String(error??""),sqlState=typeof errno==="string"?errno.toUpperCase():"",mysqlErrno=typeof errno==="number"?errno:Number.NaN;if(sqlState==="3D000")return"missing-database";if(sqlState==="28000"||sqlState==="28P01")return sqlState==="28P01"?"auth-failed":"missing-role";if(sqlState==="42501")return"permission-denied";if(mysqlErrno===1049)return"missing-database";if(mysqlErrno===1045)return"auth-failed";if(mysqlErrno===1044)return"permission-denied";if(mysqlErrno===2002||mysqlErrno===2003)return"server-unreachable";if(code.includes("CONNECTION_REFUSED")||code.includes("CONNECTION_CLOSED")||code==="ECONNREFUSED"||code==="ENOTFOUND"||code==="EHOSTUNREACH")return"server-unreachable";if(code==="ETIMEDOUT"||code.includes("TIMEOUT"))return"timeout";if(/database .* does not exist|unknown database/i.test(message))return"missing-database";if(/role .* does not exist|user .* does not exist/i.test(message))return"missing-role";if(/password authentication failed|access denied for user/i.test(message))return"auth-failed";if(/permission denied|insufficient privilege/i.test(message))return"permission-denied";if(/econnrefused|connection refused|can'?t connect/i.test(message))return"server-unreachable";return"unknown"}export function resolveConnectionTarget(envProxy=envVars){const driver=String(envProxy.DB_CONNECTION||"sqlite");if(driver!=="postgres"&&driver!=="mysql"&&driver!=="singlestore")return null;const dialect=driver==="postgres"?"postgres":"mysql",defaults=getConnectionDefaults(dialect,envProxy),database=stripWrappingQuotes(String(envProxy.DB_DATABASE||defaults.database||DB_NAMES.default)),host=String(envProxy.DB_HOST||defaults.host||DB_HOST_DEFAULT),port=Number(envProxy.DB_PORT||defaults.port||DB_PORTS[dialect]),username=String(envProxy.DB_USERNAME||defaults.username||DB_USERS[dialect]),password=String(envProxy.DB_PASSWORD??defaults.password??"");return{dialect,driver,database,host,port,username,password,maintenanceCandidates:dialect==="postgres"?["postgres","template1"]:["information_schema","mysql"]}}export function buildConnectionUrl(target,database){const scheme=target.dialect==="postgres"?"postgres":"mysql",auth=target.password?`${encodeURIComponent(target.username)}:${encodeURIComponent(target.password)}`:encodeURIComponent(target.username),sslEnv=process.env.DB_SSL,ssl=sslEnv==="true"||sslEnv==="1"?"?ssl=true":"";return`${scheme}://${auth}@${target.host}:${target.port}/${encodeURIComponent(database)}${ssl}`}function defaultConnect(url){return new SQL(url,{max:1})}async function withConnection(target,database,deps,work){const connect=deps.connect??defaultConnect,timeoutMs=deps.timeoutMs??Number(process.env.DB_PREFLIGHT_TIMEOUT_MS||DEFAULT_TIMEOUT_MS),client=connect(buildConnectionUrl(target,database));let timer;try{const timeout=new Promise((_,reject)=>{timer=setTimeout(()=>{const e=Error(`Timed out after ${timeoutMs}ms connecting to ${target.dialect} at ${target.host}:${target.port}`);e.code="ETIMEDOUT";reject(e)},timeoutMs)});return await Promise.race([work(client),timeout])}finally{if(timer)clearTimeout(timer);try{await client.close()}catch{}}}export async function probeTargetDatabase(target,deps={}){try{await withConnection(target,target.database,deps,async(client)=>client.unsafe("select 1"));return{ok:!0}}catch(error){return{ok:!1,kind:classifyConnectionError(error),error}}}export async function createDatabase(target,deps={}){if(!isValidDatabaseIdentifier(target.database))return{created:!1,kind:"unknown",error:Error(`Refusing to create a database named ${JSON.stringify(target.database)}. Database names must be 1 to 63 characters and may not contain quotes, backslashes, semicolons, or newlines.`)};const identifier=quoteIdentifier(target.dialect,target.database),sql=target.dialect==="mysql"?`CREATE DATABASE IF NOT EXISTS ${identifier}`:`CREATE DATABASE ${identifier}`;let lastError,lastKind="unknown";for(const candidate of target.maintenanceCandidates)try{await withConnection(target,candidate,deps,async(client)=>client.unsafe(sql));return{created:!0,via:candidate}}catch(error){const kind=classifyConnectionError(error),failure=error,message=failure?.message??"",errno=failure?.errno;if(errno==="42P04"||errno===1007||/already exists|database exists/i.test(String(message)))return{created:!1,via:candidate};lastError=error;lastKind=kind;if(kind==="permission-denied"||kind==="auth-failed"||kind==="missing-role")break}return{created:!1,kind:lastKind,error:lastError}}export async function canCreateDatabases(target,deps={}){if(target.dialect!=="postgres")return null;for(const candidate of target.maintenanceCandidates)try{return await withConnection(target,candidate,deps,async(client)=>{const rows=await client.unsafe("select rolcreatedb, rolsuper from pg_roles where rolname = current_user"),row=Array.isArray(rows)?rows[0]:void 0;if(!row)return null;return Boolean(row.rolcreatedb||row.rolsuper)})}catch{}return null}export function manualCreateHint(target){if(target.dialect==="postgres")return`createdb -h ${target.host} -p ${target.port} -U ${target.username} ${target.database}`;return`mysql -h ${target.host} -P ${target.port} -u ${target.username} -e "CREATE DATABASE \\\`${target.database}\\\`"`}export function describeTarget(target){return`the ${target.driver} connection (${target.host}:${target.port}, user "${target.username}")`}
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export type {
12
12
  } from './driver-config';
13
13
  // Core database utilities and default instance
14
14
  export type { FrameworkSchema } from './framework-schema';
15
+ export type { BrokenDatabasePool, DatabasePoolIdentity } from './broken-pool';
15
16
  export type { SchemaDriftColumn, SchemaDriftReport } from './schema-drift';
16
17
  export type { DeclaredFK, FkAuditResult, FkOrphan, FkOrphanReport, LiveFK } from './fk-audit';
17
18
  export type { DeclaredUnique, LiveUniqueIndex, UniqueAuditResult } from './unique-audit';
@@ -77,6 +78,8 @@ export {
77
78
  validateDriverConfig,
78
79
  } from './driver-config';
79
80
  export * from './utils';
81
+ // Pools broken by oven-sh/bun#42804, for health checks to read
82
+ export { BUN_BROKEN_MYSQL_POOL_MESSAGE, BUN_BROKEN_POSTGRES_POOL_MESSAGE, getBrokenDatabasePools, isBrokenBunPoolError } from './broken-pool';
80
83
  // Types (compatibility layer for Kysely types)
81
84
  export * from './types';
82
85
  // Migrations
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{Database,createDatabase,createMysqlDatabase,createPostgresDatabase,createSqliteDatabase}from"./database";export{detectDriver,driverDefaults,getConfigFromEnv,getConnectionString,mergeWithDefaults,validateDriverConfig}from"./driver-config";export*from"./utils";export*from"./types";export*from"./migrations";export{setQueryTracker,logQuery}from"./query-logger";export{addColumnSafely,backfillInBatches,renameColumnSafely}from"./safe-migrations";export{matchedRows,mutationCount}from"./affected-rows";export*from"./seeder";export*from"./drivers";export*from"./custom";export*from"./auth-tables";export*from"./utc-defaults";export*from"./uuid-columns";export*from"./managed-columns";export*from"./relation-columns";export{ensureNotificationForeignKeys,migrateNotificationTables}from"./notification-tables";export{migrateRbacTables}from"./rbac-tables";export*from"./trait-tables";export*from"./datetime-columns";export*from"./dialect";export*from"./replicas";export*from"./ddl-constraints";export*from"./erd";export*from"./vschema";export*from"./sql-helpers";export*from"./defaults";export*from"./migration-dialect";export*from"./migration-path";export*from"./migration-ledger";export*from"./model-sources";export*from"./package-migrations";export*from"./package-models";export*from"./shadowed-models";export*from"./ensure-database";export{auditForeignKeys,classifyDeclaredFKs,findFkOrphans,fkKey,getDeclaredFKs,getLiveFKs,getLiveTables}from"./fk-audit";export{auditSchemaDrift,formatSchemaDrift}from"./schema-drift";export{auditUniqueIndexes,getDeclaredUniques,getLiveUniqueIndexes}from"./unique-audit";export{__flushAfterCommitNow,__pendingAfterCommitCount,enqueueAfterCommit,isInTransaction,runInTransactionScope}from"./transaction-context";export{createQueryBuilder,setConfig}from"@stacksjs/query-builder";export{createDynamo,dynamo,EntityQueryBuilder,generateKeyPattern,parseKeyPattern,buildKey,marshall,unmarshall}from"./drivers/dynamodb";
1
+ export{Database,createDatabase,createMysqlDatabase,createPostgresDatabase,createSqliteDatabase}from"./database";export{detectDriver,driverDefaults,getConfigFromEnv,getConnectionString,mergeWithDefaults,validateDriverConfig}from"./driver-config";export*from"./utils";export{BUN_BROKEN_MYSQL_POOL_MESSAGE,BUN_BROKEN_POSTGRES_POOL_MESSAGE,getBrokenDatabasePools,isBrokenBunPoolError}from"./broken-pool";export*from"./types";export*from"./migrations";export{setQueryTracker,logQuery}from"./query-logger";export{addColumnSafely,backfillInBatches,renameColumnSafely}from"./safe-migrations";export{matchedRows,mutationCount}from"./affected-rows";export*from"./seeder";export*from"./drivers";export*from"./custom";export*from"./auth-tables";export*from"./utc-defaults";export*from"./uuid-columns";export*from"./managed-columns";export*from"./relation-columns";export{ensureNotificationForeignKeys,migrateNotificationTables}from"./notification-tables";export{migrateRbacTables}from"./rbac-tables";export*from"./trait-tables";export*from"./datetime-columns";export*from"./dialect";export*from"./replicas";export*from"./ddl-constraints";export*from"./erd";export*from"./vschema";export*from"./sql-helpers";export*from"./defaults";export*from"./migration-dialect";export*from"./migration-path";export*from"./migration-ledger";export*from"./model-sources";export*from"./package-migrations";export*from"./package-models";export*from"./shadowed-models";export*from"./ensure-database";export{auditForeignKeys,classifyDeclaredFKs,findFkOrphans,fkKey,getDeclaredFKs,getLiveFKs,getLiveTables}from"./fk-audit";export{auditSchemaDrift,formatSchemaDrift}from"./schema-drift";export{auditUniqueIndexes,getDeclaredUniques,getLiveUniqueIndexes}from"./unique-audit";export{__flushAfterCommitNow,__pendingAfterCommitCount,enqueueAfterCommit,isInTransaction,runInTransactionScope}from"./transaction-context";export{createQueryBuilder,setConfig}from"@stacksjs/query-builder";export{createDynamo,dynamo,EntityQueryBuilder,generateKeyPattern,parseKeyPattern,buildKey,marshall,unmarshall}from"./drivers/dynamodb";
@@ -4,7 +4,17 @@
4
4
  * as `remember token`) so `pin` matches a `pin` column and not `shipping`.
5
5
  */
6
6
  export declare function isSensitiveName(name: string): boolean;
7
+ /**
8
+ * Whether a value looks like a credential by itself, read in time linear in
9
+ * its length whatever it holds (see {@link CREDENTIAL_PATTERNS}).
10
+ */
7
11
  export declare function looksLikeCredential(text: string): boolean;
12
+ /**
13
+ * A `queryLogging.sensitiveColumns` entry as the scanner compares it:
14
+ * `column`, or `table.column` with any schema before the table ignored,
15
+ * without quotes and letter case. Undefined for an entry that is neither.
16
+ */
17
+ export declare function parseSensitiveColumn(entry: unknown): ListedColumn | undefined;
8
18
  /**
9
19
  * `captureBindings` as config or `DB_QUERY_LOGGING_CAPTURE_BINDINGS` gave it:
10
20
  * `undefined` when it is not set, `null` when it is set to something that is
@@ -17,6 +27,35 @@ export declare function parseCaptureBindings(setting: unknown): boolean | null |
17
27
  * each the value itself, `<redacted>`, or a type tag such as `<string>`.
18
28
  */
19
29
  export declare function queryLogBindings(sql: string, parameters: readonly unknown[], options: QueryLogBindingOptions): unknown[];
30
+ /**
31
+ * `error` as `query_logs.error` may keep it: at most
32
+ * MAX_QUERY_LOG_ERROR_LENGTH characters of it, with each value its binding
33
+ * does not keep replaced by that binding (`<redacted>`, `<string>`, ...)
34
+ * where the driver printed it. MySQL prints the value of a duplicate entry
35
+ * ("Duplicate entry '...' for key ...") and of a value a column rejects
36
+ * ("Incorrect integer value: '...'"); PostgreSQL prints one it cannot parse
37
+ * ("invalid input syntax for type uuid: \"...\""). A value is found as it
38
+ * is and as MySQL escapes it (see {@link printedText}), whole where it stands
39
+ * as a word of its own, and cut short from its first CUT_VALUE_LENGTH
40
+ * characters on (see {@link scanForValues}).
41
+ *
42
+ * `entries` are {@link queryLogBindings} of `parameters`, and `secret` says
43
+ * whether the value at an index would be `<redacted>` if values were kept,
44
+ * which types alone do not. Text, JSON and bytes are taken out wherever
45
+ * their binding is a marker, a copy of fewer than MIN_TYPED_COPY_LENGTH
46
+ * characters only when the value is secret; a number only when it is
47
+ * secret; a date or a boolean not at all. `secret` is asked only about a
48
+ * value it decides something for, once. A value too deep to look for, or
49
+ * values too many or too long together (see {@link printedValue}), leave
50
+ * none of the error: the stored error is the marker of the binding where
51
+ * reading stopped.
52
+ */
53
+ export declare function queryLogError(error: string, parameters: readonly unknown[], entries: readonly unknown[], secret?: (index: number) => boolean): string;
54
+ /**
55
+ * What a query log row keeps of the values a query bound: its `bindings`,
56
+ * and `error` (the failed query's error text) without them.
57
+ */
58
+ export declare function persistQueryLogValues(sql: string, parameters: unknown, error: string | undefined, options: QueryLogBindingOptions): PersistedQueryLogValues;
20
59
  /** {@link queryLogBindings} as the JSON text the `bindings` column holds. */
21
60
  export declare function serializeQueryLogBindings(sql: string, parameters: unknown, options: QueryLogBindingOptions): string;
22
61
  /**
@@ -45,8 +84,37 @@ export declare function serializeQueryLogBindings(sql: string, parameters: unkno
45
84
  * Without `captureValues` nothing but each value's type is kept, so the count
46
85
  * and shape of the bindings survive without the data. Binary values are only
47
86
  * ever recorded by type.
87
+ *
88
+ * The error text of a failed query can copy a bound value too, so the values
89
+ * the bindings withhold are taken out of `query_logs.error` where the driver
90
+ * printed them, as far as {@link queryLogError} can recognise them, and it
91
+ * keeps at most the first MAX_QUERY_LOG_ERROR_LENGTH characters of the
92
+ * message.
48
93
  */
49
94
  export declare const REDACTED_BINDING: '<redacted>';
95
+ /**
96
+ * The most characters of a failed query's error that `query_logs.error`
97
+ * keeps. PostgreSQL prints a value it cannot parse in full (a 20,000
98
+ * character value gave a 20,056 character message on PostgreSQL 16), so
99
+ * without a limit the column holds as much as a caller binds. The rest of a
100
+ * longer error is dropped and counted, as `<truncated: 15960 more
101
+ * characters>`, and the limit is also what bounds the cost of taking values
102
+ * out of it (see {@link scanForValues}).
103
+ */
104
+ export declare const MAX_QUERY_LOG_ERROR_LENGTH: 4096;
105
+ /**
106
+ * A column the application lists in `queryLogging.sensitiveColumns`: `column`
107
+ * in every table, or only in `table`. Both are lowercase and unquoted.
108
+ */
109
+ declare interface ListedColumn {
110
+ table?: string
111
+ column: string
112
+ }
50
113
  export declare interface QueryLogBindingOptions {
51
114
  captureValues: boolean
115
+ sensitiveColumns?: readonly unknown[]
116
+ }
117
+ export declare interface PersistedQueryLogValues {
118
+ bindings: string
119
+ error?: string
52
120
  }
@@ -1,2 +1,2 @@
1
- export const REDACTED_BINDING="<redacted>";const SENSITIVE_WORDS=new Set(["apikey","authorization","bearer","challenge","challenges","cookie","cookies","credential","credentials","cvc","cvv","hash","hotp","jwt","nonce","otp","passphrase","passwd","password","passwords","pin","pwd","salt","secret","secrets","session","sessions","ssn","token","tokens","totp","verification","verifications"]),SENSITIVE_PAIRS=new Set(["access key","api key","app key","auth code","auth codes","backup code","backup codes","card number","credit card","encryption key","license key","license keys","private key","recovery code","recovery codes","secret key","security code","signing key","two factor"]);export function isSensitiveName(name){const words=name.replace(/([a-z\d])([A-Z])/g,"$1_$2").toLowerCase().split(/[^a-z\d]+/).filter(Boolean);return words.some((word,index)=>SENSITIVE_WORDS.has(word)||SENSITIVE_PAIRS.has(`${word} ${words[index+1]}`))}const CREDENTIAL_PATTERNS=[/\$(?:2[abxy]?|argon2(?:id|i|d)|scrypt|pbkdf2[\w-]*|[156y])\$/,/\beyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]*/,/[\da-f]{32,}/i,/\b(?:sk|pk|rk)_(?:live|test)_\w{16,}/,/\bAKIA[\dA-Z]{16}\b/],OPAQUE_RUN=/[\w-]{32,}/g;export function looksLikeCredential(text){if(CREDENTIAL_PATTERNS.some((pattern)=>pattern.test(text)))return!0;for(const[run]of text.matchAll(OPAQUE_RUN))if(/[A-Z]/.test(run)&&/[a-z]/.test(run)&&/\d/.test(run))return!0;return!1}const KEY_END=/"\s*:/g,MAX_KEY_LENGTH=128,MAX_ESCAPES=16;function escapesBefore(text,at){let count=0;while(count<MAX_ESCAPES&&text[at-count-1]==="\\")count++;return count}function hasSensitiveKey(text){for(const{index:end}of text.matchAll(KEY_END)){const escapes=escapesBefore(text,end),floor=Math.max(0,end-escapes-MAX_KEY_LENGTH);for(let start=end-escapes-1;start>=floor;start--){if(text[start]!=='"'||escapesBefore(text,start)!==escapes)continue;if(isSensitiveName(text.slice(start+1,end-escapes)))return!0;break}}return!1}const KEYWORDS=new Set(["all","and","any","as","between","by","case","collate","conflict","delete","distinct","do","duplicate","else","end","escape","exists","false","from","glob","having","ignore","ilike","in","insert","into","is","join","like","limit","match","not","null","offset","on","only","or","regexp","replace","returning","rlike","select","set","some","table","then","true","update","using","values","when","where","with"]),COMPARISONS=new Set(["=","==","!=","<>","<",">","<=",">=","glob","ilike","is","like","match","regexp","rlike"]),TABLE_KEYWORDS=new Set(["from","into","join","table","update"]);function keyword(token){return token?.kind==="word"?token.text.toLowerCase():void 0}function isName(token){return token?.kind==="name"||token?.kind==="word"&&!KEYWORDS.has(keyword(token))}function isWord(token,...words){const word=keyword(token);return word!==void 0&&words.includes(word)}function isComparison(token){return token?.kind==="op"&&COMPARISONS.has(token.text)||COMPARISONS.has(keyword(token)??"")}function scanSql(sql){const tokens=[];let complete=!0,positional=0,at=0;const push=(token)=>{const dot=tokens.at(-1);if((token.kind==="name"||token.kind==="word")&&dot?.kind==="op"&&dot.text==="."&&isName(tokens.at(-2))){tokens.splice(-2,2);token={kind:"name",text:token.text}}tokens.push(token)},quoted=(quote,backslashEscapes)=>{let text="";for(at++;at<sql.length;at++){const char=sql[at];if(backslashEscapes&&char==="\\"){text+=sql[++at]??"";continue}if(char===quote){if(sql[at+1]!==quote){at++;return text}at++}text+=char}complete=!1;return text},sticky=(pattern)=>{pattern.lastIndex=at;const match=pattern.exec(sql);if(match)at+=match[0].length;return match};while(at<sql.length){const char=sql[at];if(/\s/.test(char))at++;else if(sql.startsWith("--",at)){const end=sql.indexOf(`
2
- `,at);at=end<0?sql.length:end}else if(sql.startsWith("/*",at)){const end=sql.indexOf("*/",at+2);if(end<0)complete=!1;at=end<0?sql.length:end+2}else if(char==="'")push({kind:"value",text:quoted("'",!0)});else if(char==='"'||char==="`")push({kind:"name",text:quoted(char,!1)});else if(char==="$"){const numbered=sticky(/\$(\d+)/y);if(numbered){push({kind:"param",text:numbered[0],param:Number(numbered[1])-1});continue}const dollar=sticky(/\$([A-Z_a-z]\w*)?\$/y);if(!dollar){push({kind:"op",text:char});at++;continue}const end=sql.indexOf(dollar[0],at);if(end<0)complete=!1;at=end<0?sql.length:end+dollar[0].length;push({kind:"value",text:""})}else if(char==="?"){const numbered=sticky(/\?(\d+)/y);if(numbered)push({kind:"param",text:numbered[0],param:Number(numbered[1])-1});else{at++;push({kind:"param",text:"?",param:positional++})}}else if(/\d/.test(char)){sticky(/\d+(?:\.\d+)?(?:e[+-]?\d+)?/iy);push({kind:"value",text:""})}else if(/[A-Z_a-z]/.test(char))push({kind:"word",text:sticky(/[A-Z_a-z][\w$]*/y)[0]});else if(char==="("||char===")"||char===","){at++;push({kind:char,text:char})}else push({kind:"op",text:sticky(/<=|>=|<>|!=|==|\|\||::|->>|->|[^\s\w]/y)[0]})}if(tokens.some((token)=>token.kind==="param"&&token.text.startsWith("$"))){for(const token of tokens)if(token.kind==="param"&&token.text.startsWith("?")){token.kind="op";delete token.param}}return{tokens,complete}}function pairParentheses(tokens){const openers=[],open=[];for(const[index,token]of tokens.entries()){openers.push((token.kind===")"?open.pop():open.at(-1))??-1);if(token.kind==="(")open.push(index)}return openers}function skipNot(tokens,at){return isWord(tokens[at],"not")?at-1:at}function operandColumn({tokens,openers},at){const token=tokens[at];if(token?.kind!==")")return isName(token)?token.text:void 0;return tokens.slice(openers[at]+1,at).find((inner)=>isName(inner))?.text}function valueColumn(statement,start,end){const{tokens,openers}=statement,operator=isWord(tokens[start-1],"not")&&isWord(tokens[start-2],"is")?start-2:start-1,before=tokens[operator];if(isComparison(before))return operandColumn(statement,skipNot(tokens,operator-1));if(isWord(before,"between"))return operandColumn(statement,skipNot(tokens,start-2));if(isWord(before,"and")&&isWord(tokens[start-3],"between"))return operandColumn(statement,skipNot(tokens,start-4));if(before?.kind==="("||before?.kind===","){const open=openers[start];if(open>0&&isWord(tokens[open-1],"in"))return operandColumn(statement,skipNot(tokens,open-2));if(open>0&&(isName(tokens[open-1])||isWord(tokens[open-1],"any","all","some")))return valueColumn(statement,open-1,open-1)}const after=tokens[end+1],operand=tokens[end+2];if(after?.kind==="op"&&isComparison(after)&&isName(operand)&&tokens[end+3]?.kind!=="(")return operand.text;return}function insertColumns(tokens){const columns=new Map,insert=tokens.findIndex((token)=>isWord(token,"insert","replace")),into=tokens.findIndex((token,index)=>index>insert&&isWord(token,"into"));if(insert<0||into<0||tokens[into+2]?.kind!=="(")return columns;const names=[];let index=into+3,expectName=!0;for(;index<tokens.length&&tokens[index].kind!==")";index++){const token=tokens[index];if(token.kind===",")expectName=!0;else if(expectName){names.push(isName(token)?token.text:void 0);expectName=!1}}if(!isWord(tokens[index+1],"values","value"))return columns;let depth=0,item=0;for(index+=2;index<tokens.length;index++){const token=tokens[index];if(token.kind==="("){if(depth++===0)item=0}else if(token.kind===")")depth--;else if(token.kind===","&&depth===1)item++;else if(token.kind==="param"&&depth>0)columns.set(index,names[item]);else if(depth===0&&token.kind==="word")break}return columns}function bindingContext(sql){const{tokens,complete}=scanSql(sql),columns=new Map;if(!complete){const sensitive=(sql.match(/[A-Z_a-z][\w$]*/g)??[]).some((word)=>isSensitiveName(word));return{columns,sensitiveTable:sensitive,sensitiveMention:sensitive}}const statement={tokens,openers:pairParentheses(tokens)},inserted=insertColumns(tokens);for(const[index,token]of tokens.entries()){if(token.kind!=="param"||token.param===void 0)continue;const column=inserted.get(index)??valueColumn(statement,index,index),seen=columns.get(token.param)??[];seen.push(column);columns.set(token.param,seen)}const sensitiveTable=tokens.some((token,index)=>isName(token)&&TABLE_KEYWORDS.has(keyword(tokens[index-1])??"")&&!isWord(tokens[index-2],"key")&&isSensitiveName(token.text)),sensitiveMention=tokens.some((token)=>isName(token)&&isSensitiveName(token.text));return{columns,sensitiveTable,sensitiveMention}}function isBytes(value){return value instanceof ArrayBuffer||ArrayBuffer.isView(value)}function typeTag(value){if(value instanceof Date)return"<date>";if(isBytes(value))return"<bytes>";return`<${typeof value}>`}function jsonText(value){try{return JSON.stringify(value)}catch{return}}const SWITCH_ON=new Set(["1","true","yes","on"]),SWITCH_OFF=new Set(["0","false","no","off"]);export function parseCaptureBindings(setting){if(typeof setting==="boolean")return setting;if(setting===void 0||setting===null)return;const text=String(setting).trim().toLowerCase();if(text==="")return;if(SWITCH_ON.has(text))return!0;if(SWITCH_OFF.has(text))return!1;return null}export function queryLogBindings(sql,parameters,options){if(parameters.length===0)return[];const context=options.captureValues?bindingContext(sql):void 0;return parameters.map((value,index)=>{if(value===null||value===void 0)return null;if(!context)return typeTag(value);const columns=context.columns.get(index)??[];if(columns.some((column)=>column!==void 0&&isSensitiveName(column)))return REDACTED_BINDING;if(isBytes(value))return typeTag(value);if(typeof value==="string"||typeof value==="object"&&!(value instanceof Date)){const unresolved=columns.every((column)=>column===void 0);if(context.sensitiveTable||unresolved&&context.sensitiveMention)return REDACTED_BINDING;const text=typeof value==="string"?value:jsonText(value);if(text===void 0)return typeTag(value);if(looksLikeCredential(text)||hasSensitiveKey(text))return REDACTED_BINDING}return typeof value==="bigint"?String(value):value})}export function serializeQueryLogBindings(sql,parameters,options){const values=Array.isArray(parameters)?parameters:[parameters];try{return JSON.stringify(queryLogBindings(sql,values,options))}catch{return"[]"}}
1
+ export const REDACTED_BINDING="<redacted>";const SENSITIVE_WORDS=new Set(["apikey","authorization","bearer","challenge","challenges","cookie","cookies","credential","credentials","cvc","cvv","hash","hotp","jwt","nonce","otp","passphrase","passwd","password","passwords","pin","pwd","salt","secret","secrets","session","sessions","ssn","token","tokens","totp","verification","verifications"]),SENSITIVE_PAIRS=new Set(["access key","api key","app key","auth code","auth codes","backup code","backup codes","card number","credit card","encryption key","license key","license keys","private key","recovery code","recovery codes","secret key","security code","signing key","two factor"]);export function isSensitiveName(name){const words=name.replace(/([a-z\d])([A-Z])/g,"$1_$2").toLowerCase().split(/[^a-z\d]+/).filter(Boolean);return words.some((word,index)=>SENSITIVE_WORDS.has(word)||SENSITIVE_PAIRS.has(`${word} ${words[index+1]}`))}const CREDENTIAL_PATTERNS=[/\$(?:2[abxy]?|argon2(?:id|i|d)|scrypt|pbkdf2[\w-]*|[156y])\$/,/[\da-f]{32,}/i,/\b(?:sk|pk|rk)_(?:live|test)_\w{16,}/,/\bAKIA[\dA-Z]{16}\b/],JWT_RUN=/[\w.-]{21,}/g,JWT_HEADER=/(?:^|-)eyJ[\w-]{8}/;function hasJwt(text){for(const[run]of text.matchAll(JWT_RUN)){const segments=run.split(".");for(let at=0;at+2<segments.length;at++)if(segments[at+1].length>=8&&JWT_HEADER.test(segments[at]))return!0}return!1}const OPAQUE_RUN=/[\w-]{32,}/g;export function looksLikeCredential(text){if(CREDENTIAL_PATTERNS.some((pattern)=>pattern.test(text))||hasJwt(text))return!0;for(const[run]of text.matchAll(OPAQUE_RUN))if(/[A-Z]/.test(run)&&/[a-z]/.test(run)&&/\d/.test(run))return!0;return!1}const KEY_END=/"\s*:/g,MAX_KEY_LENGTH=128,MAX_ESCAPES=16;function escapesBefore(text,at){let count=0;while(count<MAX_ESCAPES&&text[at-count-1]==="\\")count++;return count}function hasSensitiveKey(text){for(const{index:end}of text.matchAll(KEY_END)){const escapes=escapesBefore(text,end),floor=Math.max(0,end-escapes-MAX_KEY_LENGTH);for(let start=end-escapes-1;start>=floor;start--){if(text[start]!=='"'||escapesBefore(text,start)!==escapes)continue;if(isSensitiveName(text.slice(start+1,end-escapes)))return!0;break}}return!1}const KEYWORDS=new Set(["all","and","any","as","between","by","case","collate","conflict","delete","distinct","do","duplicate","else","end","escape","exists","false","from","glob","having","ignore","ilike","in","insert","into","is","join","like","limit","match","not","null","offset","on","only","or","regexp","replace","returning","rlike","select","set","some","table","then","true","update","using","values","when","where","with"]),COMPARISONS=new Set(["=","==","!=","<>","<",">","<=",">=","glob","ilike","is","like","match","regexp","rlike"]),TABLE_KEYWORDS=new Set(["from","into","join","table","update"]);function keyword(token){return token?.kind==="word"?token.text.toLowerCase():void 0}function isName(token){return token?.kind==="name"||token?.kind==="word"&&!KEYWORDS.has(keyword(token))}function isWord(token,...words){const word=keyword(token);return word!==void 0&&words.includes(word)}function isComparison(token){return token?.kind==="op"&&COMPARISONS.has(token.text)||COMPARISONS.has(keyword(token)??"")}function scanSql(sql){const tokens=[];let complete=!0,positional=0,at=0;const push=(token)=>{const dot=tokens.at(-1);if((token.kind==="name"||token.kind==="word")&&dot?.kind==="op"&&dot.text==="."&&isName(tokens.at(-2))){const qualifier=tokens.at(-2).text;tokens.splice(-2,2);token={kind:"name",text:token.text,qualifier}}tokens.push(token)},quoted=(quote,backslashEscapes)=>{let text="";for(at++;at<sql.length;at++){const char=sql[at];if(backslashEscapes&&char==="\\"){text+=sql[++at]??"";continue}if(char===quote){if(sql[at+1]!==quote){at++;return text}at++}text+=char}complete=!1;return text},sticky=(pattern)=>{pattern.lastIndex=at;const match=pattern.exec(sql);if(match)at+=match[0].length;return match};while(at<sql.length){const char=sql[at];if(/\s/.test(char))at++;else if(sql.startsWith("--",at)){const end=sql.indexOf(`
2
+ `,at);at=end<0?sql.length:end}else if(sql.startsWith("/*",at)){const end=sql.indexOf("*/",at+2);if(end<0)complete=!1;at=end<0?sql.length:end+2}else if(char==="'")push({kind:"value",text:quoted("'",!0)});else if(char==='"'||char==="`")push({kind:"name",text:quoted(char,!1)});else if(char==="$"){const numbered=sticky(/\$(\d+)/y);if(numbered){push({kind:"param",text:numbered[0],param:Number(numbered[1])-1});continue}const dollar=sticky(/\$([A-Z_a-z]\w*)?\$/y);if(!dollar){push({kind:"op",text:char});at++;continue}const end=sql.indexOf(dollar[0],at);if(end<0)complete=!1;at=end<0?sql.length:end+dollar[0].length;push({kind:"value",text:""})}else if(char==="?"){const numbered=sticky(/\?(\d+)/y);if(numbered)push({kind:"param",text:numbered[0],param:Number(numbered[1])-1});else{at++;push({kind:"param",text:"?",param:positional++})}}else if(/\d/.test(char)){sticky(/\d+(?:\.\d+)?(?:e[+-]?\d+)?/iy);push({kind:"value",text:""})}else if(/[A-Z_a-z]/.test(char))push({kind:"word",text:sticky(/[A-Z_a-z][\w$]*/y)[0]});else if(char==="("||char===")"||char===","){at++;push({kind:char,text:char})}else push({kind:"op",text:sticky(/<=|>=|<>|!=|==|\|\||::|->>|->|[^\s\w]/y)[0]})}if(tokens.some((token)=>token.kind==="param"&&token.text.startsWith("$"))){for(const token of tokens)if(token.kind==="param"&&token.text.startsWith("?")){token.kind="op";delete token.param}}return{tokens,complete}}function pairParentheses(tokens){const openers=[],open=[];for(const[index,token]of tokens.entries()){openers.push((token.kind===")"?open.pop():open.at(-1))??-1);if(token.kind==="(")open.push(index)}return openers}function skipNot(tokens,at){return isWord(tokens[at],"not")?at-1:at}function operandColumn({tokens,openers},at){const token=tokens[at];if(token?.kind!==")")return isName(token)?token:void 0;return tokens.slice(openers[at]+1,at).find((inner)=>isName(inner))}function valueColumn(statement,start,end){const{tokens,openers}=statement,operator=isWord(tokens[start-1],"not")&&isWord(tokens[start-2],"is")?start-2:start-1,before=tokens[operator];if(isComparison(before))return operandColumn(statement,skipNot(tokens,operator-1));if(isWord(before,"between"))return operandColumn(statement,skipNot(tokens,start-2));if(isWord(before,"and")&&isWord(tokens[start-3],"between"))return operandColumn(statement,skipNot(tokens,start-4));if(before?.kind==="("||before?.kind===","){const open=openers[start];if(open>0&&isWord(tokens[open-1],"in"))return operandColumn(statement,skipNot(tokens,open-2));if(open>0&&(isName(tokens[open-1])||isWord(tokens[open-1],"any","all","some")))return valueColumn(statement,open-1,open-1)}const after=tokens[end+1],operand=tokens[end+2];if(after?.kind==="op"&&isComparison(after)&&isName(operand)&&tokens[end+3]?.kind!=="(")return operand;return}function insertColumns(tokens){const columns=new Map,insert=tokens.findIndex((token)=>isWord(token,"insert","replace")),into=tokens.findIndex((token,index)=>index>insert&&isWord(token,"into"));if(insert<0||into<0||tokens[into+2]?.kind!=="(")return columns;const names=[];let index=into+3,expectName=!0;for(;index<tokens.length&&tokens[index].kind!==")";index++){const token=tokens[index];if(token.kind===",")expectName=!0;else if(expectName){names.push(isName(token)?token:void 0);expectName=!1}}if(!isWord(tokens[index+1],"values","value"))return columns;let depth=0,item=0;for(index+=2;index<tokens.length;index++){const token=tokens[index];if(token.kind==="("){if(depth++===0)item=0}else if(token.kind===")")depth--;else if(token.kind===","&&depth===1)item++;else if(token.kind==="param"&&depth>0)columns.set(index,names[item]);else if(depth===0&&token.kind==="word")break}return columns}const LISTED_PART=/^[\p{L}\p{N}_$-]+$/u;export function parseSensitiveColumn(entry){if(typeof entry!=="string")return;const parts=entry.split(".").map((part)=>part.trim().replace(/^(["`])(.*)\1$/,"$2").toLowerCase());if(parts.length>3||!parts.every((part)=>LISTED_PART.test(part)))return;return parts.length===1?{column:parts[0]}:{table:parts.at(-2),column:parts.at(-1)}}function listedColumnTest(tokens,listed){if(listed.length===0)return()=>!1;const named=new Set,tables=new Set,aliases=new Map;for(const[index,token]of tokens.entries()){if(!isName(token))continue;named.add(token.text.toLowerCase());if(token.qualifier)named.add(token.qualifier.toLowerCase());if(!TABLE_KEYWORDS.has(keyword(tokens[index-1])??"")||isWord(tokens[index-2],"key"))continue;const table=token.text.toLowerCase();tables.add(table);const at=isWord(tokens[index+1],"as")?index+2:index+1;if(isName(tokens[at])&&tokens[at+1]?.kind!=="(")aliases.set(tokens[at].text.toLowerCase(),table)}return(column)=>{const name=column.text.toLowerCase(),qualifier=column.qualifier?.toLowerCase(),against=qualifier===void 0?void 0:aliases.get(qualifier)??qualifier;return listed.some((entry)=>entry.column===name&&(entry.table===void 0||against===entry.table||(against===void 0||!tables.has(against))&&named.has(entry.table)))}}function bindingContext(sql,listed){const{tokens,complete}=scanSql(sql),columns=new Map;if(!complete){const words=sql.match(/[A-Z_a-z][\w$]*/g)??[],lower=new Set(words.map((word)=>word.toLowerCase())),sensitive=words.some((word)=>isSensitiveName(word))||listed.some((entry)=>lower.has(entry.column)&&(entry.table===void 0||lower.has(entry.table)));return{columns,sensitiveColumn:()=>sensitive,sensitiveTable:sensitive,sensitiveMention:sensitive}}const statement={tokens,openers:pairParentheses(tokens)},inserted=insertColumns(tokens);for(const[index,token]of tokens.entries()){if(token.kind!=="param"||token.param===void 0)continue;const column=inserted.get(index)??valueColumn(statement,index,index),seen=columns.get(token.param)??[];seen.push(column);columns.set(token.param,seen)}const isListed=listedColumnTest(tokens,listed),sensitiveColumn=(column)=>isSensitiveName(column.text)||isListed(column),sensitiveTable=tokens.some((token,index)=>isName(token)&&TABLE_KEYWORDS.has(keyword(tokens[index-1])??"")&&!isWord(tokens[index-2],"key")&&isSensitiveName(token.text)),sensitiveMention=tokens.some((token)=>isName(token)&&sensitiveColumn(token));return{columns,sensitiveColumn,sensitiveTable,sensitiveMention}}function isBytes(value){return value instanceof ArrayBuffer||ArrayBuffer.isView(value)}function typeTag(value){if(value instanceof Date)return"<date>";if(isBytes(value))return"<bytes>";return`<${typeof value}>`}function isStructured(value){return typeof value==="object"&&value!==null&&!(value instanceof Date)&&!isBytes(value)}const TOO_LONG=Symbol("longer than the limit");function jsonText(value,limit=Number.POSITIVE_INFINITY){let written=0,root=!0;function count(key,item){const member=!root&&!Array.isArray(this);root=!1;const omitted=item===void 0||typeof item==="function"||typeof item==="symbol";if(member&&omitted)return item;written+=(member?key.length+3:0)+(omitted?4:typeof item==="string"?item.length+2:typeof item==="object"&&item!==null?2:String(item).length);if(written>limit)throw TOO_LONG;return item}try{return JSON.stringify(value,limit===Number.POSITIVE_INFINITY?void 0:count)}catch(error){return error===TOO_LONG?TOO_LONG:void 0}}const SWITCH_ON=new Set(["1","true","yes","on"]),SWITCH_OFF=new Set(["0","false","no","off"]);export function parseCaptureBindings(setting){if(typeof setting==="boolean")return setting;if(setting===void 0||setting===null)return;const text=String(setting).trim().toLowerCase();if(text==="")return;if(SWITCH_ON.has(text))return!0;if(SWITCH_OFF.has(text))return!1;return null}function listedColumns(options){return options.sensitiveColumns?.flatMap((entry)=>parseSensitiveColumn(entry)??[])??[]}function keptValue(context,value,index){if(value===null||value===void 0)return null;const columns=context.columns.get(index)??[];if(columns.some((column)=>column!==void 0&&context.sensitiveColumn(column)))return REDACTED_BINDING;if(isBytes(value))return typeTag(value);if(typeof value==="string"||typeof value==="object"&&!(value instanceof Date)){const unresolved=columns.every((column)=>column===void 0);if(context.sensitiveTable||unresolved&&context.sensitiveMention)return REDACTED_BINDING;const text=typeof value==="string"?value:jsonText(value);if(text===void 0)return typeTag(value);if(looksLikeCredential(text)||hasSensitiveKey(text))return REDACTED_BINDING}return typeof value==="bigint"?String(value):value}export function queryLogBindings(sql,parameters,options){if(parameters.length===0)return[];if(!options.captureValues)return parameters.map((value)=>value===null||value===void 0?null:typeTag(value));const context=bindingContext(sql,listedColumns(options));return parameters.map((value,index)=>keptValue(context,value,index))}export const MAX_QUERY_LOG_ERROR_LENGTH=4096;const CUT_VALUE_LENGTH=16,MIN_TYPED_COPY_LENGTH=8,WORD_CHAR=/[\p{L}\p{N}_]/u,MAX_JUDGED_LENGTH=65536,MAX_PRINTED_DEPTH=32,MAX_PRINTED_ITEMS=1e5,MAX_PRINTED_CHARACTERS=16777216,utf8=new TextEncoder;function bytesOf(value){return value instanceof ArrayBuffer?new Uint8Array(value):new Uint8Array(value.buffer,value.byteOffset,value.byteLength)}function mysqlPrintable(bytes,limit){let text="";for(let at=0;at<bytes.length&&text.length<=limit;at++){const byte=bytes[at];text+=byte>=32&&byte<127?String.fromCharCode(byte):`\\x${byte.toString(16).toUpperCase().padStart(2,"0")}`}return text}function upTo(text,whole,limit){return text.length<=limit?{text,whole}:{text:text.slice(0,limit),whole:!1}}const MAX_MYSQL_PRINTED_LENGTH=512;function printedText(text,whole,limit){const forms=[upTo(text,whole,limit)];if(/[^\x20-\x7E]/.test(text)){const mysqlLimit=Math.min(limit,MAX_MYSQL_PRINTED_LENGTH),head=text.slice(0,2*mysqlLimit+2),key=head.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"?"),nul=key.indexOf("\x00");for(const form of[nul<0?key:key.slice(0,nul),mysqlPrintable(utf8.encode(head),mysqlLimit)]){const printed=upTo(form,whole,mysqlLimit);if(!forms.some((known)=>known.text===printed.text))forms.push(printed)}}return forms}function printedItem(value,limit,json){if(typeof value==="string")return printedText(value.slice(0,limit),value.length<=limit,limit);if(isBytes(value)){const bytes=bytesOf(value);return[upTo(mysqlPrintable(bytes.subarray(0,limit),limit),bytes.length<=limit,limit)]}if(typeof value==="number"||typeof value==="bigint")return typeof value==="bigint"||Number.isFinite(value)?[{text:String(value),whole:!0,number:!0}]:[];if(isStructured(value)){const text=json(value);return text===void 0?[]:printedText(text.slice(0,limit),text.length<=limit,limit)}return[]}function printedValue(value,limit,budget){const heads=new Map,json=(item)=>{if(!heads.has(item))heads.set(item,jsonText(item)?.slice(0,limit+1));return heads.get(item)},forms=[],add=(printed)=>{for(const form of printed){forms.push(form);budget.characters-=form.text.length}return budget.characters>=0};if(--budget.items<0)return;if(!Array.isArray(value))return add(printedItem(value,limit,json))?forms:void 0;const append=(array,item)=>{if(array.text!==void 0&&array.text.length<=limit)array.text=item===void 0?void 0:`${array.text}${array.next>1?",":""}${item}`},open=[{items:value,next:0,text:"["}];while(open.length>0){const array=open.at(-1);if(array.next<array.items.length){const item=array.items[array.next++];if(--budget.items<0)return;if(Array.isArray(item)){if(open.length>=MAX_PRINTED_DEPTH)return;open.push({items:item,next:0,text:"["});continue}if(!add(printedItem(item,limit,json)))return;if(array.text!==void 0&&array.text.length<=limit)append(array,typeof item==="string"?JSON.stringify(item.slice(0,limit+1-array.text.length)):item===void 0||typeof item==="function"||typeof item==="symbol"?"null":typeof item==="object"&&item!==null?json(item):jsonText(item));continue}open.pop();const text=array.text===void 0?void 0:`${array.text}]`,around=open.at(-1);if(around)append(around,text);else if(text!==void 0&&!add(printedText(text.slice(0,limit),text.length<=limit,limit)))return}return forms}function addCopy(root,{text,whole},marker){const redacted=marker===REDACTED_BINDING;let node=root,depth=0;for(;;){node.redacted||=redacted;if(depth===text.length){node.end=node.end?{text,whole:node.end.whole&&whole,marker:node.end.marker===REDACTED_BINDING?REDACTED_BINDING:marker}:{text,whole,marker};return}const children=node.children??=new Map,child=children.get(text.charCodeAt(depth));if(!child){children.set(text.charCodeAt(depth),{source:text,depth:text.length,end:{text,whole,marker},redacted,marker});return}let at=depth+1;while(at<child.depth&&at<text.length&&text.charCodeAt(at)===child.source.charCodeAt(at))at++;if(at<child.depth){node={source:child.source,depth:at,children:new Map([[child.source.charCodeAt(at),child]]),redacted:child.redacted,marker:child.marker};children.set(text.charCodeAt(depth),node)}else node=child;depth=at}}function scanForValues(text,root){const spans=[],words=new Uint8Array(text.length);for(let at=0;at<text.length;at++)words[at]=WORD_CHAR.test(text[at])?1:0;const word=(at)=>words[at]===1;for(let start=0;start<text.length;start++){if(word(start)&&word(start-1))continue;let end=start,marker,node=root,depth=0,matched=0,cut;for(;;){const whole=node.end;if(whole&&depth<CUT_VALUE_LENGTH&&!(whole.whole&&word(start+depth-1)&&word(start+depth))){end=start+depth;marker=marker===REDACTED_BINDING?marker:whole.marker}const child=start+depth<text.length?node.children?.get(text.charCodeAt(start+depth)):void 0;if(!child)break;let at=depth+1;while(at<child.depth&&start+at<text.length&&text.charCodeAt(start+at)===child.source.charCodeAt(at))at++;matched=at;if(!cut&&at>=CUT_VALUE_LENGTH)cut=child;if(at<child.depth)break;node=child;depth=at}if(cut){end=start+matched;marker=marker===REDACTED_BINDING||cut.redacted?REDACTED_BINDING:cut.marker}if(marker!==void 0&&end>start)spans.push({start,end,marker})}return spans}export function queryLogError(error,parameters,entries,secret=(index)=>entries[index]===REDACTED_BINDING){const truncated=error.length>MAX_QUERY_LOG_ERROR_LENGTH,text=truncated?error.slice(0,MAX_QUERY_LOG_ERROR_LENGTH+CUT_VALUE_LENGTH):error,root={source:"",depth:0,redacted:!1,marker:""},budget={items:MAX_PRINTED_ITEMS,characters:MAX_PRINTED_CHARACTERS};let copies=0;for(const[index,value]of parameters.entries()){const entry=entries[index];if(typeof entry!=="string"||entry===value||typeof value==="bigint"&&entry===String(value))continue;const printed=printedValue(value,text.length,budget);if(printed===void 0)return entry;let isSecret;for(const copy of printed){if(copy.number||copy.text.length<MIN_TYPED_COPY_LENGTH){isSecret??=secret(index);if(!isSecret||copy.text.length===0)continue}addCopy(root,copy,entry);copies++}}const spans=copies>0?scanForValues(text,root):[],limit=truncated?MAX_QUERY_LOG_ERROR_LENGTH:text.length;let kept="",at=0;for(let index=0;index<spans.length&&spans[index].start<limit;){let{start,end,marker}=spans[index];for(index++;index<spans.length&&spans[index].start<end;index++){end=Math.max(end,spans[index].end);if(spans[index].marker===REDACTED_BINDING)marker=REDACTED_BINDING}kept+=text.slice(at,start)+marker;at=end}if(!truncated)return kept+text.slice(at);const cut=Math.max(at,MAX_QUERY_LOG_ERROR_LENGTH);return`${kept}${text.slice(at,cut)}<truncated: ${error.length-cut} more characters>`}function secretValues(sql,values,options){let context;return(index)=>{const value=values[index];if(typeof value==="string"?value.length>MAX_JUDGED_LENGTH:isStructured(value)&&jsonText(value,MAX_JUDGED_LENGTH)===TOO_LONG)return!0;context??=bindingContext(sql,listedColumns(options));return keptValue(context,value,index)===REDACTED_BINDING}}export function persistQueryLogValues(sql,parameters,error,options){const values=Array.isArray(parameters)?parameters:[parameters];try{const entries=queryLogBindings(sql,values,options);if(error===void 0)return{bindings:JSON.stringify(entries)};const secret=options.captureValues?void 0:secretValues(sql,values,options);return{bindings:JSON.stringify(entries),error:queryLogError(error,values,entries,secret)}}catch{try{return{bindings:"[]",error:error===void 0?void 0:queryLogError(error,values,values.map(()=>REDACTED_BINDING),()=>!0)}}catch{return{bindings:"[]",error:error===void 0?void 0:REDACTED_BINDING}}}}export function serializeQueryLogBindings(sql,parameters,options){return persistQueryLogValues(sql,parameters,void 0,options).bindings}
@@ -1,2 +1,2 @@
1
- import{sqlDateTime}from"./sql-helpers";import{memoryUsage}from"node:process";import{AsyncLocalStorage}from"node:async_hooks";import{config}from"@stacksjs/config";import{env as envVars}from"@stacksjs/env";import{log}from"@stacksjs/logging";import{parseCaptureBindings,serializeQueryLogBindings}from"./query-log-bindings";import{normalizeQuery,parseQuery}from"./query-parser";import{db,getDatabaseDialect}from"./utils";const QUERY_TRACKER_KEY=Symbol.for("stacks.database.queryTracker");let configuredQueryTracker=()=>{};export function setQueryTracker(fn){configuredQueryTracker=fn}function trackQuery(query,durationMs,connection){const shared=globalThis[QUERY_TRACKER_KEY];(typeof shared==="function"?shared:configuredQueryTracker)(query,durationMs,connection)}const queryLogContext=new AsyncLocalStorage,QUERY_LOG_BATCH_SIZE=100,QUERY_LOG_BATCH_DELAY_MS=5,pendingQueryLogs=[];let queryLogFlushScheduled=!1,queryLogFlushInFlight=!1;function enqueueQueryLog(record){const settled=new Promise((resolve)=>{pendingQueryLogs.push({record,resolve})});if(pendingQueryLogs.length>=QUERY_LOG_BATCH_SIZE)flushQueuedQueryLogs();else if(!queryLogFlushScheduled){queryLogFlushScheduled=!0;setTimeout(()=>{queryLogFlushScheduled=!1;flushQueuedQueryLogs()},QUERY_LOG_BATCH_DELAY_MS)}return settled}async function flushQueuedQueryLogs(){if(queryLogFlushInFlight)return;queryLogFlushInFlight=!0;try{while(pendingQueryLogs.length>0){const batch=pendingQueryLogs.splice(0,QUERY_LOG_BATCH_SIZE),recordsByShape=Map.groupBy(batch.map((item)=>item.record),(record)=>Object.keys(record).join("\x00"));for(const records of recordsByShape.values())if(!await queryLogContext.run(!0,()=>storeQueryLogs(records,records.length===1)))for(const record of records)await queryLogContext.run(!0,()=>storeQueryLogs([record]));for(const item of batch)item.resolve()}}finally{queryLogFlushInFlight=!1;if(pendingQueryLogs.length>0&&!queryLogFlushScheduled){queryLogFlushScheduled=!0;setTimeout(()=>{queryLogFlushScheduled=!1;flushQueuedQueryLogs()},QUERY_LOG_BATCH_DELAY_MS)}}}export async function logQuery(event){if(queryLogContext.getStore())return;try{const{query,durationMs,error,parameters}=extractQueryInfo(event);if(query)try{trackQuery(query,durationMs,config.database?.default||"unknown")}catch{}if(!config.database?.queryLogging?.enabled)return;const excludedQueries=config.database?.queryLogging?.excludedQueries;if(!query.trim()||isExcludedQuery(query,Array.isArray(excludedQueries)?excludedQueries:[]))return;const status=determineQueryStatus(durationMs,error),logRecord=await createQueryLogRecord(query,durationMs,status,error,parameters);if(config.database?.queryLogging?.analysis?.enabled&&(status==="slow"||config.database.queryLogging.analysis.analyzeAll))await enhanceWithQueryAnalysis(logRecord);await enqueueQueryLog(logRecord);if(status!=="completed")log[status==="failed"?"error":"warn"](`Query ${status}:`,{query:logRecord.query,duration:logRecord.duration,connection:logRecord.connection,...error&&{error}})}catch(err){log.error("Failed to log query:",err)}}export function isExcludedQuery(query,excludedQueries){const normalizedQuery=query.toLowerCase();return excludedQueries.some((pattern)=>{const normalizedPattern=pattern.trim().toLowerCase();return normalizedPattern.length>0&&normalizedQuery.includes(normalizedPattern)})}function queryText(sql){if(typeof sql==="string")return usableQueryText(sql);if(sql&&typeof sql==="object"&&typeof sql.then==="function")return"";if(sql&&typeof sql.toString==="function")return usableQueryText(String(sql));return""}function usableQueryText(text){return text.startsWith("[object ")?"":text}function extractQueryInfo(event){const query=queryText(event.query?.sql),durationMs=event.queryDurationMillis||0,error=event.error,parameters=event.query?.parameters;return{query,durationMs,error,parameters}}function determineQueryStatus(durationMs,error){const slowThreshold=config.database?.queryLogging?.slowThreshold||100;if(error)return"failed";if(durationMs>slowThreshold)return"slow";return"completed"}const warnedCaptureBindings=new Set;function capturesBindingValues(){const setting=config.database?.queryLogging?.captureBindings??envVars.DB_QUERY_LOGGING_CAPTURE_BINDINGS,capture=parseCaptureBindings(setting);if(capture===null){const shown=String(setting);if(!warnedCaptureBindings.has(shown)){warnedCaptureBindings.add(shown);console.warn(`[database] queryLogging.captureBindings (DB_QUERY_LOGGING_CAPTURE_BINDINGS) is "${shown}", which is not true, false, 1, 0, yes, no, on or off; query logs keep only the type of each binding.`)}return!1}if(capture!==void 0)return capture;const appEnv=config.app?.env;return appEnv!=="production"&&appEnv!=="prod"}async function createQueryLogRecord(query,durationMs,status,error,parameters){const connection=config.database.default||"unknown",normalizedQuery=normalizeQuery(query)||query,traceInfo=status==="completed"&&!config.database?.queryLogging?.captureAllTraces?void 0:extractTraceInfo(),bindings=parameters?serializeQueryLogBindings(query,parameters,{captureValues:capturesBindingValues()}):void 0;return{query,normalized_query:normalizedQuery,duration:durationMs,connection,status,error:error?String(error):void 0,executed_at:sqlDateTime(),bindings,trace:traceInfo?.trace,...traceInfo?.caller??{},memory_usage:memoryUsage().heapUsed/1024/1024}}const SECRET_PATTERNS=[/\b(?:sk|pk|rk|api|secret|token|bearer|password|pwd|key)[_-]?[A-Za-z0-9]{12,}/gi,/\bsk_(?:live|test)_[A-Za-z0-9]{20,}/g,/\bAKIA[0-9A-Z]{16}/g,/\b[A-Za-z0-9_-]{40,}\b(?=\s|$|['"])/g];function sanitizeStackTrace(stack){let out=stack;for(const pattern of SECRET_PATTERNS)out=out.replace(pattern,"<redacted>");return out}let lastTraceInfo;function extractTraceInfo(){try{const stack=Error("Stack trace capture").stack||"";if(lastTraceInfo?.trace===stack)return lastTraceInfo;const callerLine=stack.split(`
2
- `).slice(1).find((line)=>!line.includes("query-logger.ts"));let caller={};if(callerLine){const methodMatch=callerLine.match(/at (.+?) \(/),fileMatch=callerLine.match(/\((.+?):(\d+):(\d+)\)/);if(methodMatch&&methodMatch[1]){const methodParts=methodMatch[1].split(".");caller={model:methodParts.length>1?methodParts[0]:void 0,method:methodParts.length>1?methodParts[1]:methodParts[0]}}if(fileMatch&&fileMatch[1]&&fileMatch[2])caller={...caller,file:fileMatch[1],line:Number.parseInt(fileMatch[2],10)}}const result={trace:sanitizeStackTrace(stack),caller};if(result.trace===stack&&stack.length<=8192)lastTraceInfo=result;return result}catch{return{trace:"",caller:{}}}}async function enhanceWithQueryAnalysis(logRecord){try{const{tables,type}=parseQuery(logRecord.query);logRecord.affected_tables=JSON.stringify(tables||[]);if(type==="SELECT"&&config.database?.queryLogging?.analysis?.explainPlan){const explainResult=await getExplainPlan(logRecord.query);if(explainResult){logRecord.explain_plan=explainResult.plan;logRecord.indexes_used=JSON.stringify(explainResult.indexesUsed||[]);logRecord.missing_indexes=JSON.stringify(explainResult.missingIndexes||[]);if(config.database?.queryLogging?.analysis?.suggestions)logRecord.optimization_suggestions=JSON.stringify(generateOptimizationSuggestions(explainResult,logRecord))}}const tags=[type];if(tables&&tables.length>0)tags.push(...tables.map((table)=>`table:${table}`));logRecord.tags=JSON.stringify(tags)}catch(error){log.debug("Error during query analysis:",error)}}const EXPLAIN_DIALECTS=new Set(["sqlite","mysql","postgres"]);async function getExplainPlan(query){try{const rawDriver=(await import("@stacksjs/config")).config?.database?.default,driver=typeof rawDriver==="string"?rawDriver:"sqlite";if(!EXPLAIN_DIALECTS.has(driver))return null;let sqlText;if(driver==="mysql")sqlText=`EXPLAIN FORMAT=JSON ${query}`;else if(driver==="postgres")sqlText=`EXPLAIN (FORMAT JSON) ${query}`;else sqlText=`EXPLAIN QUERY PLAN ${query}`;const result=await db.unsafe?.(sqlText);if(!result)return null;const raw=result,rows=Array.isArray(raw)?raw:raw?.rows??[],planText=JSON.stringify(rows),indexesUsed=[],missingIndexes=[];if(driver==="sqlite")for(const row of rows){const detail=row?.detail||"",idxMatch=detail.match(/USING (?:COVERING )?INDEX (\w+)/i);if(idxMatch&&idxMatch[1])indexesUsed.push(idxMatch[1]);else{const scanMatch=detail.match(/^SCAN\s+(\w+)/i);if(scanMatch&&scanMatch[1])missingIndexes.push(scanMatch[1])}}else if(driver==="mysql"){const text=planText;for(const m of text.matchAll(/"key"\s*:\s*"([^"]+)"/g))if(m[1])indexesUsed.push(m[1]);for(const m of text.matchAll(/"table_name"\s*:\s*"([^"]+)"[^}]*"access_type"\s*:\s*"ALL"/g))if(m[1])missingIndexes.push(m[1])}else if(driver==="postgres"){const text=planText;for(const m of text.matchAll(/"Index Name"\s*:\s*"([^"]+)"/g))if(m[1])indexesUsed.push(m[1]);for(const m of text.matchAll(/"Node Type"\s*:\s*"Seq Scan"[^}]*"Relation Name"\s*:\s*"([^"]+)"/g))if(m[1])missingIndexes.push(m[1])}return{plan:planText.length>4000?`${planText.slice(0,4000)}\u2026`:planText,indexesUsed:Array.from(new Set(indexesUsed)),missingIndexes:Array.from(new Set(missingIndexes))}}catch(err){log.debug("[query-logger] EXPLAIN failed (non-fatal):",err);return null}}function generateOptimizationSuggestions(explainResult,logRecord){const suggestions=[];if(explainResult.missingIndexes&&explainResult.missingIndexes.length>0)suggestions.push(`Consider adding index on ${explainResult.missingIndexes.join(", ")}`);if(logRecord.status==="slow"){suggestions.push("Consider optimizing this query to reduce execution time");if(logRecord.query.toLowerCase().includes("select *"))suggestions.push("Specify only needed columns instead of using SELECT *");if(!logRecord.query.toLowerCase().includes("limit"))suggestions.push("Consider adding LIMIT clause to reduce result set size")}return suggestions}async function storeQueryLogs(logRecords,reportFailure=!0){try{const values=logRecords;if(values.length===1)await db.insertInto("query_logs").values(values).execute();else if(getDatabaseDialect()==="sqlite"){const columns=Object.keys(values[0]),identifiers=columns.map((column)=>`"${column.replaceAll('"','""')}"`).join(", "),row=`(${columns.map(()=>"?").join(", ")})`,statement=`INSERT INTO query_logs (${identifiers}) VALUES ${values.map(()=>row).join(", ")}`,bindings=values.flatMap((value)=>columns.map((column)=>value[column]??null));db.unsafe("SAVEPOINT stacks_query_log_batch").executeSync();try{db.unsafe(statement,bindings).executeSync();db.unsafe("RELEASE SAVEPOINT stacks_query_log_batch").executeSync()}catch(error){db.unsafe("ROLLBACK TO SAVEPOINT stacks_query_log_batch").executeSync();db.unsafe("RELEASE SAVEPOINT stacks_query_log_batch").executeSync();throw error}}else await db.transaction(async(rawTrx)=>{await rawTrx.insertInto("query_logs").values(values).execute()});return!0}catch(error){if(reportFailure){const message=error instanceof Error?error.message:String(error);if(/no such table|does not exist|doesn't exist/i.test(message)&&/query_logs/i.test(message))log.debug("Query logging will start after the query_logs table is migrated.");else log.error("Failed to store query log:",error)}return!1}}
1
+ import{sqlDateTime}from"./sql-helpers";import{memoryUsage}from"node:process";import{AsyncLocalStorage}from"node:async_hooks";import{config}from"@stacksjs/config";import{env as envVars}from"@stacksjs/env";import{log}from"@stacksjs/logging";import{parseCaptureBindings,parseSensitiveColumn,persistQueryLogValues,queryLogError}from"./query-log-bindings";import{normalizeQuery,parseQuery}from"./query-parser";import{db,getDatabaseDialect}from"./utils";const QUERY_TRACKER_KEY=Symbol.for("stacks.database.queryTracker");let configuredQueryTracker=()=>{};export function setQueryTracker(fn){configuredQueryTracker=fn}function trackQuery(query,durationMs,connection){const shared=globalThis[QUERY_TRACKER_KEY];(typeof shared==="function"?shared:configuredQueryTracker)(query,durationMs,connection)}const queryLogContext=new AsyncLocalStorage,QUERY_LOG_BATCH_SIZE=100,QUERY_LOG_BATCH_DELAY_MS=5,pendingQueryLogs=[];let queryLogFlushScheduled=!1,queryLogFlushInFlight=!1;function enqueueQueryLog(record){const settled=new Promise((resolve)=>{pendingQueryLogs.push({record,resolve})});if(pendingQueryLogs.length>=QUERY_LOG_BATCH_SIZE)flushQueuedQueryLogs();else if(!queryLogFlushScheduled){queryLogFlushScheduled=!0;setTimeout(()=>{queryLogFlushScheduled=!1;flushQueuedQueryLogs()},QUERY_LOG_BATCH_DELAY_MS)}return settled}async function flushQueuedQueryLogs(){if(queryLogFlushInFlight)return;queryLogFlushInFlight=!0;try{while(pendingQueryLogs.length>0){const batch=pendingQueryLogs.splice(0,QUERY_LOG_BATCH_SIZE),recordsByShape=Map.groupBy(batch.map((item)=>item.record),(record)=>Object.keys(record).join("\x00"));for(const records of recordsByShape.values())if(!await queryLogContext.run(!0,()=>storeQueryLogs(records,records.length===1)))for(const record of records)await queryLogContext.run(!0,()=>storeQueryLogs([record]));for(const item of batch)item.resolve()}}finally{queryLogFlushInFlight=!1;if(pendingQueryLogs.length>0&&!queryLogFlushScheduled){queryLogFlushScheduled=!0;setTimeout(()=>{queryLogFlushScheduled=!1;flushQueuedQueryLogs()},QUERY_LOG_BATCH_DELAY_MS)}}}export async function logQuery(event){if(queryLogContext.getStore())return;try{const{query,durationMs,error,parameters}=extractQueryInfo(event);if(query)try{trackQuery(query,durationMs,config.database?.default||"unknown")}catch{}if(!config.database?.queryLogging?.enabled)return;const excludedQueries=config.database?.queryLogging?.excludedQueries;if(!query.trim()||isExcludedQuery(query,Array.isArray(excludedQueries)?excludedQueries:[]))return;const status=determineQueryStatus(durationMs,error),logRecord=await createQueryLogRecord(query,durationMs,status,error,parameters);if(config.database?.queryLogging?.analysis?.enabled&&(status==="slow"||config.database.queryLogging.analysis.analyzeAll))await enhanceWithQueryAnalysis(logRecord);await enqueueQueryLog(logRecord);if(status!=="completed")log[status==="failed"?"error":"warn"](`Query ${status}:`,{query:logRecord.query,duration:logRecord.duration,connection:logRecord.connection,...error&&{error}})}catch(err){log.error("Failed to log query:",err)}}export function isExcludedQuery(query,excludedQueries){const normalizedQuery=query.toLowerCase();return excludedQueries.some((pattern)=>{const normalizedPattern=pattern.trim().toLowerCase();return normalizedPattern.length>0&&normalizedQuery.includes(normalizedPattern)})}function queryText(sql){if(typeof sql==="string")return usableQueryText(sql);if(sql&&typeof sql==="object"&&typeof sql.then==="function")return"";if(sql&&typeof sql.toString==="function")return usableQueryText(String(sql));return""}function usableQueryText(text){return text.startsWith("[object ")?"":text}function extractQueryInfo(event){const query=queryText(event.query?.sql),durationMs=event.queryDurationMillis||0,error=event.error,parameters=event.query?.parameters;return{query,durationMs,error,parameters}}function determineQueryStatus(durationMs,error){const slowThreshold=config.database?.queryLogging?.slowThreshold||100;if(error)return"failed";if(durationMs>slowThreshold)return"slow";return"completed"}const warnedCaptureBindings=new Set;function capturesBindingValues(){const setting=config.database?.queryLogging?.captureBindings??envVars.DB_QUERY_LOGGING_CAPTURE_BINDINGS,capture=parseCaptureBindings(setting);if(capture===null){const shown=String(setting);if(!warnedCaptureBindings.has(shown)){warnedCaptureBindings.add(shown);console.warn(`[database] queryLogging.captureBindings (DB_QUERY_LOGGING_CAPTURE_BINDINGS) is "${shown}", which is not true, false, 1, 0, yes, no, on or off; query logs keep only the type of each binding.`)}return!1}if(capture!==void 0)return capture;const appEnv=config.app?.env;return appEnv!=="production"&&appEnv!=="prod"}const warnedSensitiveColumns=new Set;function sensitiveColumns(){const setting=config.database?.queryLogging?.sensitiveColumns;if(setting===void 0||setting===null)return[];const entries=Array.isArray(setting)?setting:[setting];for(const entry of entries){const shown=String(entry);if(parseSensitiveColumn(entry)===void 0&&!warnedSensitiveColumns.has(shown)){warnedSensitiveColumns.add(shown);console.warn(`[database] queryLogging.sensitiveColumns has "${shown}", which is not a column or table.column; it marks nothing as sensitive.`)}}return entries}async function createQueryLogRecord(query,durationMs,status,error,parameters){const connection=config.database.default||"unknown",normalizedQuery=normalizeQuery(query)||query,traceInfo=status==="completed"&&!config.database?.queryLogging?.captureAllTraces?void 0:extractTraceInfo(),errorText=error?String(error):void 0,persisted=parameters?persistQueryLogValues(query,parameters,errorText,{captureValues:capturesBindingValues(),sensitiveColumns:sensitiveColumns()}):{bindings:void 0,error:errorText===void 0?void 0:queryLogError(errorText,[],[])};return{query,normalized_query:normalizedQuery,duration:durationMs,connection,status,error:persisted.error,executed_at:sqlDateTime(),bindings:persisted.bindings,trace:traceInfo?.trace,...traceInfo?.caller??{},memory_usage:memoryUsage().heapUsed/1024/1024}}const SECRET_PATTERNS=[/\b(?:sk|pk|rk|api|secret|token|bearer|password|pwd|key)[_-]?[A-Za-z0-9]{12,}/gi,/\bsk_(?:live|test)_[A-Za-z0-9]{20,}/g,/\bAKIA[0-9A-Z]{16}/g,/\b[A-Za-z0-9_-]{40,}\b(?=\s|$|['"])/g];function sanitizeStackTrace(stack){let out=stack;for(const pattern of SECRET_PATTERNS)out=out.replace(pattern,"<redacted>");return out}let lastTraceInfo;function extractTraceInfo(){try{const stack=Error("Stack trace capture").stack||"";if(lastTraceInfo?.trace===stack)return lastTraceInfo;const callerLine=stack.split(`
2
+ `).slice(1).find((line)=>!line.includes("query-logger.ts"));let caller={};if(callerLine){const methodMatch=callerLine.match(/at (.+?) \(/),fileMatch=callerLine.match(/\((.+?):(\d+):(\d+)\)/);if(methodMatch&&methodMatch[1]){const methodParts=methodMatch[1].split(".");caller={model:methodParts.length>1?methodParts[0]:void 0,method:methodParts.length>1?methodParts[1]:methodParts[0]}}if(fileMatch&&fileMatch[1]&&fileMatch[2])caller={...caller,file:fileMatch[1],line:Number.parseInt(fileMatch[2],10)}}const result={trace:sanitizeStackTrace(stack),caller};if(result.trace===stack&&stack.length<=8192)lastTraceInfo=result;return result}catch{return{trace:"",caller:{}}}}async function enhanceWithQueryAnalysis(logRecord){try{const{tables,type}=parseQuery(logRecord.query);logRecord.affected_tables=JSON.stringify(tables||[]);if(type==="SELECT"&&config.database?.queryLogging?.analysis?.explainPlan){const explainResult=await getExplainPlan(logRecord.query);if(explainResult){logRecord.explain_plan=explainResult.plan;logRecord.indexes_used=JSON.stringify(explainResult.indexesUsed||[]);logRecord.missing_indexes=JSON.stringify(explainResult.missingIndexes||[]);if(config.database?.queryLogging?.analysis?.suggestions)logRecord.optimization_suggestions=JSON.stringify(generateOptimizationSuggestions(explainResult,logRecord))}}const tags=[type];if(tables&&tables.length>0)tags.push(...tables.map((table)=>`table:${table}`));logRecord.tags=JSON.stringify(tags)}catch(error){log.debug("Error during query analysis:",error)}}const EXPLAIN_DIALECTS=new Set(["sqlite","mysql","postgres"]);async function getExplainPlan(query){try{const rawDriver=(await import("@stacksjs/config")).config?.database?.default,driver=typeof rawDriver==="string"?rawDriver:"sqlite";if(!EXPLAIN_DIALECTS.has(driver))return null;let sqlText;if(driver==="mysql")sqlText=`EXPLAIN FORMAT=JSON ${query}`;else if(driver==="postgres")sqlText=`EXPLAIN (FORMAT JSON) ${query}`;else sqlText=`EXPLAIN QUERY PLAN ${query}`;const result=await db.unsafe?.(sqlText);if(!result)return null;const raw=result,rows=Array.isArray(raw)?raw:raw?.rows??[],planText=JSON.stringify(rows),indexesUsed=[],missingIndexes=[];if(driver==="sqlite")for(const row of rows){const detail=row?.detail||"",idxMatch=detail.match(/USING (?:COVERING )?INDEX (\w+)/i);if(idxMatch&&idxMatch[1])indexesUsed.push(idxMatch[1]);else{const scanMatch=detail.match(/^SCAN\s+(\w+)/i);if(scanMatch&&scanMatch[1])missingIndexes.push(scanMatch[1])}}else if(driver==="mysql"){const text=planText;for(const m of text.matchAll(/"key"\s*:\s*"([^"]+)"/g))if(m[1])indexesUsed.push(m[1]);for(const m of text.matchAll(/"table_name"\s*:\s*"([^"]+)"[^}]*"access_type"\s*:\s*"ALL"/g))if(m[1])missingIndexes.push(m[1])}else if(driver==="postgres"){const text=planText;for(const m of text.matchAll(/"Index Name"\s*:\s*"([^"]+)"/g))if(m[1])indexesUsed.push(m[1]);for(const m of text.matchAll(/"Node Type"\s*:\s*"Seq Scan"[^}]*"Relation Name"\s*:\s*"([^"]+)"/g))if(m[1])missingIndexes.push(m[1])}return{plan:planText.length>4000?`${planText.slice(0,4000)}\u2026`:planText,indexesUsed:Array.from(new Set(indexesUsed)),missingIndexes:Array.from(new Set(missingIndexes))}}catch(err){log.debug("[query-logger] EXPLAIN failed (non-fatal):",err);return null}}function generateOptimizationSuggestions(explainResult,logRecord){const suggestions=[];if(explainResult.missingIndexes&&explainResult.missingIndexes.length>0)suggestions.push(`Consider adding index on ${explainResult.missingIndexes.join(", ")}`);if(logRecord.status==="slow"){suggestions.push("Consider optimizing this query to reduce execution time");if(logRecord.query.toLowerCase().includes("select *"))suggestions.push("Specify only needed columns instead of using SELECT *");if(!logRecord.query.toLowerCase().includes("limit"))suggestions.push("Consider adding LIMIT clause to reduce result set size")}return suggestions}async function storeQueryLogs(logRecords,reportFailure=!0){try{const values=logRecords;if(values.length>1&&getDatabaseDialect()==="sqlite"){const columns=Object.keys(values[0]),identifiers=columns.map((column)=>`"${column.replaceAll('"','""')}"`).join(", "),row=`(${columns.map(()=>"?").join(", ")})`,statement=`INSERT INTO query_logs (${identifiers}) VALUES ${values.map(()=>row).join(", ")}`,bindings=values.flatMap((value)=>columns.map((column)=>value[column]??null));db.unsafe("SAVEPOINT stacks_query_log_batch").executeSync();try{db.unsafe(statement,bindings).executeSync();db.unsafe("RELEASE SAVEPOINT stacks_query_log_batch").executeSync()}catch(error){db.unsafe("ROLLBACK TO SAVEPOINT stacks_query_log_batch").executeSync();db.unsafe("RELEASE SAVEPOINT stacks_query_log_batch").executeSync();throw error}}else await db.insertInto("query_logs").values(values).execute();return!0}catch(error){if(reportFailure){const message=error instanceof Error?error.message:String(error);if(/no such table|does not exist|doesn't exist/i.test(message)&&/query_logs/i.test(message))log.debug("Query logging will start after the query_logs table is migrated.");else log.error("Failed to store query log:",error)}return!1}}
package/dist/runtime.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * query facade and the small helpers used while handling application work.
7
7
  */
8
8
  export * from './affected-rows';
9
+ export * from './broken-pool';
9
10
  export * from './dialect';
10
11
  export * from './replicas';
11
12
  export * from './sql-helpers';
package/dist/runtime.js CHANGED
@@ -1 +1 @@
1
- export*from"./affected-rows";export*from"./dialect";export*from"./replicas";export*from"./sql-helpers";export*from"./transaction-context";export*from"./types";export*from"./utils";
1
+ export*from"./affected-rows";export*from"./broken-pool";export*from"./dialect";export*from"./replicas";export*from"./sql-helpers";export*from"./transaction-context";export*from"./types";export*from"./utils";
package/dist/utils.d.ts CHANGED
@@ -24,6 +24,15 @@ declare function getDialect(): QueryBuilderDialect;
24
24
  */
25
25
  export declare function qbSnapshotDir(): string;
26
26
  export declare function createDatabaseQueryHooks(dispatch: (event: DatabaseQueryLogEvent) => void | Promise<void>): QueryHooks;
27
+ /**
28
+ * Report a query error from the primary pool to ./broken-pool, which ignores
29
+ * anything but Bun's broken-slot error (oven-sh/bun#42804).
30
+ *
31
+ * The query hook below calls this for every error bun-query-builder reports.
32
+ * Code holding an error from a path that fires no hook, such as a query
33
+ * through `db.unsafe()`, calls it directly.
34
+ */
35
+ export declare function recordPrimaryDatabaseQueryError(error: unknown): void;
27
36
  export declare function ensureDatabaseConfigLoaded(): Promise<void>;
28
37
  /**
29
38
  * Discard every cached database client after the underlying query-builder
package/dist/utils.js CHANGED
@@ -1 +1 @@
1
- import{AsyncLocalStorage}from"node:async_hooks";import{types as nodeUtilTypes}from"node:util";import{config as queryBuilderConfig,createQueryBuilder,registerPersistentQueryHooks,resetConnection as resetQueryBuilderConnection,setConfig}from"@stacksjs/query-builder";import{SQL}from"bun";import{closeDatabaseConnectionsAndPending,retireDatabaseConnections}from"./connection-lifecycle";import{runInTransactionScope}from"./transaction-context";import{env as envVars}from"@stacksjs/env";import{getConnectionDefaults}from"./defaults";import{isMysqlWire,isVitessSharded,toQueryBuilderDialect}from"./dialect";import{relativeMigrationDirectory,resolveMigrationDirectory,snapshotDirForQueryBuilder}from"./migration-path";import{configureDatabaseRoutingContext,contextInTransaction,markContextWrote,resolveReplicaConnection,runInDatabaseRoutingContext,selectReplica,shouldRouteToReplica,withDatabaseRoutingContext,withTransactionContext}from"./replicas";import{aggregateFunctions}from"./types";const sqliteDefaults=getConnectionDefaults("sqlite",envVars),mysqlDefaults=getConnectionDefaults("mysql",envVars),postgresDefaults=getConnectionDefaults("postgres",envVars);let appEnv=envVars.APP_ENV||"local",dbDriver=envVars.DB_CONNECTION||"sqlite",queryBuilderDialect=toQueryBuilderDialect(dbDriver),queryLoggingEnabled=envVars.DB_QUERY_LOGGING_ENABLED??!isProductionEnvironment(appEnv),dbConfig={connections:{sqlite:{database:sqliteDefaults.database,prefix:""},mysql:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},singlestore:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},vitess:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:15306,prefix:"",sharded:isVitessSharded()},postgres:{name:postgresDefaults.database,host:postgresDefaults.host,username:postgresDefaults.username,password:postgresDefaults.password,port:postgresDefaults.port,prefix:""}}},dbConfigLockTail=Promise.resolve();const DB_CONFIG_LOCK_MAX_HOLD_MS=60000;export function acquireDbConfigLock(){let release=()=>{};const held=new Promise((resolve)=>{release=resolve}),acquired=dbConfigLockTail.then(()=>{const watchdog=setTimeout(()=>{console.warn(`[database] the db config lock was held for over ${DB_CONFIG_LOCK_MAX_HOLD_MS/1000}s. Releasing it so the queue can proceed - a test file most likely failed before its afterAll ran.`);release()},DB_CONFIG_LOCK_MAX_HOLD_MS);watchdog.unref?.();held.then(()=>clearTimeout(watchdog));return release});dbConfigLockTail=dbConfigLockTail.then(()=>held).catch(()=>{});return acquired}export function initializeDbConfig(config){if(config?.app?.env)appEnv=config.app.env;if(config?.database?.default)dbDriver=config.database.default;queryBuilderDialect=toQueryBuilderDialect(dbDriver);if(config?.database?.connections)dbConfig=config.database;configureDatabaseRoutingContext(getReplicas().length>0);queryLoggingEnabled=config?.database?.queryLogging?.enabled??envVars.DB_QUERY_LOGGING_ENABLED??!isProductionEnvironment(appEnv);syncDatabaseQueryHooks();updateQueryBuilderConfig();_dbInstance=null;_replicaInstances=new Map}function getEnv(){return appEnv}function isProductionEnvironment(value){return value==="production"||value==="prod"}function getDriver(){return dbDriver}function getDatabaseConfig(){return dbConfig}function getDialect(){return queryBuilderDialect}export{getDialect as getDatabaseDialect};function getDbConfig(){const driver=getDriver(),database=getDatabaseConfig(),env=getEnv();if(driver==="sqlite"){const defaultName=env!=="testing"?"database/stacks.sqlite":"database/stacks_testing.sqlite";return{database:database.connections?.sqlite?.database??defaultName}}if(driver==="mysql")return{database:database.connections?.mysql?.name||"stacks",host:database.connections?.mysql?.host??"127.0.0.1",username:database.connections?.mysql?.username??"root",password:database.connections?.mysql?.password??"",port:database.connections?.mysql?.port??3306};if(driver==="singlestore")return{database:database.connections?.singlestore?.name||"stacks",host:database.connections?.singlestore?.host??"127.0.0.1",username:database.connections?.singlestore?.username??"root",password:database.connections?.singlestore?.password??"",port:database.connections?.singlestore?.port??3306};if(driver==="vitess")return{database:database.connections?.vitess?.name||"stacks",host:database.connections?.vitess?.host??"127.0.0.1",username:database.connections?.vitess?.username??"root",password:database.connections?.vitess?.password??"",port:database.connections?.vitess?.port??15306};if(driver==="postgres"){const dbName=database.connections?.postgres?.name??"stacks";return{database:env==="testing"?`${dbName}_testing`:dbName,host:database.connections?.postgres?.host??"127.0.0.1",username:database.connections?.postgres?.username??"",password:database.connections?.postgres?.password??"",port:database.connections?.postgres?.port??5432}}return{database:":memory:"}}export const QB_SNAPSHOT_DIR="storage/framework/database";export function qbSnapshotDir(){return snapshotDirForQueryBuilder()}export const RAW_QUERY_SOFT_DELETE_CONFIG={enabled:!1,column:"deleted_at",defaultFilter:!0};export function createDatabaseQueryHooks(dispatch){function forward(event){try{Promise.resolve(dispatch(event)).catch(()=>{})}catch{}}return{onQueryEnd:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs}),onQueryError:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs,error:event.error})}}let queryLoggerModule;function forwardDatabaseQuery(event){transactionConnection.exit(()=>{queryLoggerModule??=import("./query-logger").catch((error)=>{queryLoggerModule=void 0;throw error});queryLoggerModule.then(({logQuery})=>logQuery(event)).catch(()=>{})})}let unregisterDatabaseQueryHooks;function syncDatabaseQueryHooks(){const shouldInstall=!isProductionEnvironment(appEnv)||queryLoggingEnabled;if(shouldInstall&&!unregisterDatabaseQueryHooks)unregisterDatabaseQueryHooks=registerPersistentQueryHooks(createDatabaseQueryHooks(forwardDatabaseQuery));else if(!shouldInstall&&unregisterDatabaseQueryHooks){unregisterDatabaseQueryHooks();unregisterDatabaseQueryHooks=void 0}}syncDatabaseQueryHooks();function getPoolConfig(){const driver=getDriver();if(driver==="sqlite")return;return getDatabaseConfig().connections?.[driver]?.pool}const EMPTY_REPLICAS=[];function getReplicas(){const driver=getDriver();if(driver==="sqlite")return EMPTY_REPLICAS;return getDatabaseConfig().connections?.[driver]?.replicas??EMPTY_REPLICAS}function getReadPolicy(){return getDatabaseConfig().reads??{}}export{runInDatabaseRoutingContext,withDatabaseRoutingContext};function toBunPoolOptions(pool){if(!pool)return{};const options={};if(pool.max!==void 0)options.max=pool.max;if(pool.idleTimeoutMs!==void 0)options.idleTimeout=Math.round(pool.idleTimeoutMs/1000);if(pool.acquireTimeoutMs!==void 0)options.connectionTimeout=Math.round(pool.acquireTimeoutMs/1000);if(pool.maxLifetimeMs!==void 0)options.maxLifetime=Math.round(pool.maxLifetimeMs/1000);return options}function updateQueryBuilderConfig(){const dialect=getDialect(),dbConfigForQb=getDbConfig(),pool=getPoolConfig();setConfig({dialect,vitess:{sharded:isVitessSharded(dbConfig.connections.vitess?.sharded)},database:pool?{...dbConfigForQb,pool}:dbConfigForQb,verbose:getEnv()!=="production",snapshotDir:qbSnapshotDir(),migrationDir:relativeMigrationDirectory(resolveMigrationDirectory(toQueryBuilderDialect(dialect),{snapshotDir:qbSnapshotDir()})),timestamps:{createdAt:"created_at",updatedAt:"updated_at",defaultOrderColumn:"created_at"},softDeletes:RAW_QUERY_SOFT_DELETE_CONFIG})}updateQueryBuilderConfig();let _dbInstance=null,_configInitPromise=null;function ensureConfigLoaded(){if(!_configInitPromise)_configInitPromise=(async()=>{try{const{config,overridesReady}=await import("@stacksjs/config");await overridesReady;if(config){initializeDbConfig(config);_dbInstance=null}}catch{}})();return _configInitPromise}export async function ensureDatabaseConfigLoaded(){await ensureConfigLoaded()}export{applySqlitePragmas,SQLITE_BOOTSTRAP_PRAGMAS}from"@stacksjs/query-builder";const sqliteTxOwner=new AsyncLocalStorage;let sqliteTxTail=Promise.resolve();function runOwnedSqliteTransaction(run,parent){const owner={parent,accepting:!0,transactions:0,completionTail:Promise.resolve()};return sqliteTxOwner.run(owner,async()=>{try{return await run()}finally{owner.accepting=!1;await owner.completionTail}})}function serializeSqliteTransaction(run){let owner=sqliteTxOwner.getStore();while(owner&&!owner.accepting)owner=owner.parent;if(owner?.accepting&&owner.transactions>0)return sqliteTxOwner.run(owner,run);const completionOwner=owner?.accepting?owner:void 0,result=(completionOwner?.completionTail??sqliteTxTail).then(()=>runOwnedSqliteTransaction(run,completionOwner)),tail=result.then(()=>{return},()=>{return});if(completionOwner)completionOwner.completionTail=tail;else sqliteTxTail=tail;return result}function applySqliteTransactionSerialization(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>serializeSqliteTransaction(()=>original(...args))}const dispatchScopedBuilders=new WeakSet,transactionConnection=new AsyncLocalStorage;function activeTransactionConnection(){const scope=transactionConnection.getStore();if(!scope)return;assertTransactionConnection(scope);return scope.connection}function assertTransactionConnection(scope){for(let owner=scope;owner;owner=owner.parent)if(!owner.active)throw Error("Database work cannot continue from a closed transaction callback.");if(scope.nested)throw Error("Await the active nested transaction before issuing more database work.")}function guardTransactionQuery(value){const scope=transactionConnection.getStore();if(!scope||!value||typeof value!=="object"||typeof value.execute!=="function")return value;const wrappers=new WeakMap,wrap=(target)=>{const cached=wrappers.get(target);if(cached)return cached;const proxy=new Proxy(target,{get(object,property){const member=Reflect.get(object,property,object);if(typeof member!=="function")return member;return(...args)=>{assertTransactionConnection(scope);if(transactionConnection.getStore()!==scope)throw Error("A transaction query must execute in the callback that created it.");const guardedArgs=args.map((arg)=>typeof arg==="function"?function(...values){return Reflect.apply(arg,this,values.map((value)=>value&&typeof value==="object"&&typeof Reflect.get(value,"execute")==="function"?wrap(value):value))}:arg),result=Reflect.apply(member,object,guardedArgs);if(result===object)return proxy;if(result&&typeof result==="object"&&!nodeUtilTypes.isPromise(result)&&(typeof Reflect.get(result,"execute")==="function"||typeof Reflect.get(result,"next")==="function"))return wrap(result);return result}}});wrappers.set(target,proxy);return proxy};return wrap(value)}function applyTransactionDispatchScope(instance){if(dispatchScopedBuilders.has(instance))return;dispatchScopedBuilders.add(instance);let defaultOptions={};const setDefaults=instance.setTransactionDefaults.bind(instance);instance.setTransactionDefaults=(defaults)=>{setDefaults(defaults);defaultOptions={...defaultOptions,...defaults}};for(const method of["transaction","savepoint"]){const original=instance[method].bind(instance);instance[method]=async(callback,options)=>{const active=activeTransactionConnection();if(active&&active!==instance)return active[method](callback,options);const parent=transactionConnection.getStore(),effectiveOptions={...defaultOptions,...options},afterCommit=method==="transaction"?effectiveOptions.afterCommit:void 0,parentObserver=(listener)=>listener?(...args)=>{const nested=parent?.nested;if(parent)parent.nested=!1;try{listener(...args)}finally{if(parent)parent.nested=nested}}:void 0,result=await runInTransactionScope(async(runAttempt)=>{const owner=sqliteTxOwner.getStore();if(owner)owner.transactions++;if(parent)parent.nested=!0;try{return await original((tx)=>runAttempt(async()=>{applyTransactionDispatchScope(tx);const scope={connection:tx,parent,active:!0,nested:!1};return transactionConnection.run(scope,async()=>{try{return await callback(tx)}finally{scope.active=!1}})}),method==="transaction"?{...options,afterCommit:void 0,onRollback:parentObserver(effectiveOptions.onRollback),afterRollback:parentObserver(effectiveOptions.afterRollback),onRetry:parentObserver(effectiveOptions.onRetry)}:options)}finally{if(parent)parent.nested=!1;if(owner)owner.transactions--}});await afterCommit?.();return result}}}function applyTransactionRoutingContext(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>withTransactionContext(()=>original(...args))}function getDb(){if(_databaseClosePromise)throw Error("Database connections are shutting down");const active=activeTransactionConnection();if(active)return active;if(!_dbInstance){updateQueryBuilderConfig();_dbInstance=createQueryBuilder();applyTransactionDispatchScope(_dbInstance);if(getDialect()==="sqlite")applySqliteTransactionSerialization(_dbInstance);applyTransactionRoutingContext(_dbInstance)}return _dbInstance}let _replicaInstances=new Map,_databaseClosePromise=null;const _retiredConnectionClosures=new Set;export function resetDatabaseConnection(){const connections=[..._dbInstance?[_dbInstance]:[],..._replicaInstances.values()];if(connections.length>0)retireDatabaseConnections(_retiredConnectionClosures,connections,(error)=>console.error(`[database] Failed to close retired connection: ${error.message}`));resetQueryBuilderConnection();lastGeneralSqliteStatement=void 0;_dbInstance=null;_replicaInstances=new Map}export function closeDatabaseConnection(){if(_databaseClosePromise)return _databaseClosePromise;const connections=[..._dbInstance?[_dbInstance]:[],..._replicaInstances.values()];_dbInstance=null;_replicaInstances=new Map;lastGeneralSqliteStatement=void 0;const closing=(async()=>{try{await closeDatabaseConnectionsAndPending(connections,_retiredConnectionClosures)}finally{resetQueryBuilderConnection()}})();_databaseClosePromise=closing;closing.then(()=>{if(_databaseClosePromise===closing)_databaseClosePromise=null},()=>{if(_databaseClosePromise===closing)_databaseClosePromise=null});return closing}function getReplicaDb(replica){const primary=getDbConfig(),resolved=resolveReplicaConnection(replica,primary),key=`${resolved.host}:${resolved.port??""}`,cached=_replicaInstances.get(key);if(cached)return cached;const scheme=isMysqlWire(getDriver())?"mysql":"postgres",auth=resolved.username?`${encodeURIComponent(resolved.username)}:${encodeURIComponent(resolved.password??"")}@`:"",url=`${scheme}://${auth}${resolved.host}:${resolved.port}/${resolved.database}`,sql=new SQL({url,...toBunPoolOptions(getPoolConfig())}),instance=createQueryBuilder({sql});_replicaInstances.set(key,instance);return instance}function getReadDb(){const active=activeTransactionConnection();if(active)return active;const replicas=getReplicas();if(replicas.length===0)return getDb();const policy=getReadPolicy();if(!shouldRouteToReplica({policy,replicas}))return getDb();const replica=selectReplica(replicas,policy.strategy);return replica?getReplicaDb(replica):getDb()}function getExplicitReadDb(){const active=activeTransactionConnection();if(active)return active;const replicas=getReplicas();if(!replicas.length||contextInTransaction())return getDb();const replica=selectReplica(replicas,getReadPolicy().strategy);return replica?getReplicaDb(replica):getDb()}const WRITE_ENTRY_POINTS=new Set(["insertInto","updateTable","deleteFrom","create","createMany","insertOrIgnore","insertGetId","updateOrInsert","upsert"]),READ_ENTRY_POINTS=new Set(["selectFrom","selectFromSub","select"]);ensureConfigLoaded();export function asRows(rows){return rows}export function asRow(row){return row}const SIMPLE_SQLITE_TABLE=/^[A-Z_][A-Z0-9_]*$/i,SIMPLE_SQLITE_COLUMN=/^[A-Z_][A-Z0-9_]*(?:\.[A-Z_][A-Z0-9_]*)?$/i,SIMPLE_SQLITE_SELECTION=/^[A-Z_][A-Z0-9_]*(?:\.[A-Z_][A-Z0-9_]*)?(?:\s+AS\s+[A-Z_][A-Z0-9_]*)?$/i,SIMPLE_SQLITE_OPERATORS=new Set(["=","!=","<>","<","<=",">",">=","like","not like"]),SQLITE_IDENTIFIER_CACHE_LIMIT=512;function memoizeSqliteIdentifier(pattern){const valid=new Set;return(value)=>{if(valid.has(value))return!0;if(!pattern.test(value))return!1;if(valid.size<SQLITE_IDENTIFIER_CACHE_LIMIT)valid.add(value);return!0}}const isSimpleSqliteTable=memoizeSqliteIdentifier(SIMPLE_SQLITE_TABLE),isSimpleSqliteColumn=memoizeSqliteIdentifier(SIMPLE_SQLITE_COLUMN),isSimpleSqliteSelection=memoizeSqliteIdentifier(SIMPLE_SQLITE_SELECTION);let lastParameterizedSqliteSelect,lastUnparameterizedSqliteSelect,lastSqliteSelection;const GUARDED_SQLITE_SELECT_METHODS=new Set(["select","where","limit","execute","executeSync","executeTakeFirstSync"]);function hasActiveQueryBuilderHooks(){return Boolean(queryBuilderConfig.hooks&&Object.values(queryBuilderConfig.hooks).some((value)=>value!==void 0))}function resolveDeferredSqliteTerminal(target,property,executeStatement,executeFirstStatement,materialize,proxy){if(property==="get"){target.get=target.execute;return target.execute}if(property==="selectAll"){const selectAll=()=>proxy;target.selectAll=selectAll;return selectAll}if(property==="first"){const first=async()=>executeFirstStatement();target.first=first;return first}if(property==="firstOrFail"||property==="executeTakeFirstOrThrow"){const firstOrFail=async()=>{const row=executeFirstStatement();if(row===void 0)throw Error("Record not found");return row};target.firstOrFail=firstOrFail;target.executeTakeFirstOrThrow=firstOrFail;return firstOrFail}if(property==="exists"||property==="doesntExist"){const findsRow=property==="exists",check=async()=>executeFirstStatement()!==void 0===findsRow;target[property]=check;return check}if(property==="value"){const value=async(column)=>executeFirstStatement()?.[column];target.value=value;return value}if(property==="count"||property==="sum"||property==="avg"||property==="min"||property==="max"){const emptyValue=property==="min"||property==="max"?null:0,aggregate=(...args)=>runDeferredSqliteAggregate(property,args,emptyValue,materialize,executeStatement);target[property]=aggregate;return aggregate}if(property==="pluck"){const pluck=(...args)=>{if(args.length!==1){const builder=materialize();return builder.pluck.call(builder,...args)}try{const column=args[0],rows=executeStatement(),values=Array(rows.length);for(let index=0;index<rows.length;index++)values[index]=rows[index]?.[column];return Promise.resolve(values)}catch(error){return Promise.reject(error)}};target.pluck=pluck;return pluck}}function runDeferredSqliteAggregate(name,args,emptyValue,materialize,executeStatement){const column=args[0],acceptsNoColumn=name==="count"&&args.length===0;if(!acceptsNoColumn&&(args.length!==1||typeof column!=="string"||!isSimpleSqliteColumn(column))){const builder=materialize();return builder[name].call(builder,...args)}try{const expression=`${name.toUpperCase()}(${acceptsNoColumn?"*":column}) AS aggregate`,value=executeStatement(!1,expression)[0]?.aggregate??emptyValue;return Promise.resolve(name==="count"||name==="sum"||name==="avg"?Number(value):value)}catch(error){return Promise.reject(error)}}const fastSqliteDatabaseCache=new WeakMap;let lastGeneralSqliteStatement;function fastSqliteDatabase(instance){if(fastSqliteDatabaseCache.has(instance))return fastSqliteDatabaseCache.get(instance)??void 0;const database=instance.sql?._wrapper?.database,resolved=database&&typeof database.query==="function"?database:null;fastSqliteDatabaseCache.set(instance,resolved);return resolved??void 0}function runFastSqliteSql(instance,sqliteDatabase,sql,params){if(sqliteDatabase){let cached=lastGeneralSqliteStatement;if(!cached||cached.database!==sqliteDatabase||cached.sql!==sql){cached={database:sqliteDatabase,sql,statement:sqliteDatabase.query(sql)};lastGeneralSqliteStatement=cached}return params?cached.statement.all(...params):cached.statement.all()}return instance.unsafe(sql,params).executeSync()}let lastSqlitePlaceholders;function renderSqlitePlaceholders(length){const cached=lastSqlitePlaceholders;if(cached&&cached.length===length)return cached.sql;const sql=Array(length).fill("?").join(", ");if(typeof length==="number")lastSqlitePlaceholders={length,sql};return sql}function snapshotSimpleSqliteMembershipValues(values){if(nodeUtilTypes.isProxy(values)||Object.getPrototypeOf(values)!==Array.prototype||Object.hasOwn(values,Symbol.iterator))return;const snapshot=Array(values.length);for(let index=0;index<values.length;index++){const descriptor=Object.getOwnPropertyDescriptor(values,index);if(!descriptor||!("value"in descriptor))return;snapshot[index]=descriptor.value}return snapshot}function createDeferredSqliteSelect(instance,table){const sqliteDatabase=fastSqliteDatabase(instance);let selectKeyword="SELECT",columns,selectedColumnsSql,predicateColumn,predicateOperator,predicateValue,predicateParameterized=!0,predicateValues,additionalPredicates,orderings,rowLimit,rowOffset,materialized;const materialize=()=>{if(materialized)return materialized;let builder=instance.selectFrom(table);if(columns)builder=builder.select.call(builder,columns);if(selectKeyword==="SELECT DISTINCT")builder=builder.distinct.call(builder);if(predicateColumn!==void 0){if(predicateValues)if(predicateOperator==="LIKE LOWER"||predicateOperator==="NOT LIKE LOWER")builder=builder[predicateOperator==="LIKE LOWER"?"whereILike":"whereNotILike"].call(builder,predicateColumn.slice(6,-1),predicateValues[0]);else builder=builder[predicateOperator==="IN"?"whereIn":"whereNotIn"].call(builder,predicateColumn,predicateValues);else if(predicateParameterized)builder=builder.where.call(builder,predicateColumn,predicateOperator,predicateValue);else builder=builder[predicateOperator==="IS NULL"?"whereNull":"whereNotNull"].call(builder,predicateColumn);if(additionalPredicates)for(const predicate of additionalPredicates)if(predicate.values)if(predicate.operator==="LIKE LOWER"||predicate.operator==="NOT LIKE LOWER"){const method=predicate.operator==="LIKE LOWER"?"whereILike":"whereNotILike";builder=builder[method].call(builder,predicate.column.slice(6,-1),predicate.values[0])}else{const method=predicate.operator==="IN"?"whereIn":"whereNotIn";builder=builder[method].call(builder,predicate.column,predicate.values)}else if(predicate.parameterized)builder=builder.where.call(builder,predicate.column,predicate.operator,predicate.value);else{const method=predicate.operator==="IS NULL"?"whereNull":"whereNotNull";builder=builder[method].call(builder,predicate.column)}}if(orderings){const apply=builder.orderBy;for(const ordering of orderings)builder=apply.call(builder,ordering.column,ordering.direction)}if(rowLimit!==void 0)builder=builder.limit.call(builder,rowLimit);if(rowOffset!==void 0)builder=builder.offset.call(builder,rowOffset);materialized=builder;return builder};let proxy;const executeStatement=(firstOnly=!1,selection)=>{const selected=selection??selectedColumnsSql??"*",effectiveLimit=rowLimit??(firstOnly&&rowOffset===void 0?1:void 0);if(predicateColumn===void 0&&orderings===void 0&&rowOffset===void 0){const cached=lastUnparameterizedSqliteSelect;if(cached&&cached.instance===instance&&cached.selectKeyword===selectKeyword&&cached.selected===selected&&cached.table===table&&cached.limit===effectiveLimit){if(sqliteDatabase){const statement=cached.statement??=sqliteDatabase.query(cached.sql);if(effectiveLimit===1){const row=statement.get();return row===null?[]:[row]}return statement.all()}return runFastSqliteSql(instance,sqliteDatabase,cached.sql)}const limit=effectiveLimit===void 0?"":` LIMIT ${effectiveLimit}`,sql=`${selectKeyword} ${selected} FROM ${table}${limit}`;lastUnparameterizedSqliteSelect={instance,selectKeyword,selected,table,limit:effectiveLimit,sql,statement:sqliteDatabase?.query(sql)};if(lastUnparameterizedSqliteSelect.statement){if(effectiveLimit===1){const row=lastUnparameterizedSqliteSelect.statement.get();return row===null?[]:[row]}return lastUnparameterizedSqliteSelect.statement.all()}return runFastSqliteSql(instance,sqliteDatabase,sql)}if(predicateColumn!==void 0&&predicateValues===void 0&&predicateParameterized&&additionalPredicates===void 0&&orderings===void 0&&rowOffset===void 0){const cached=lastParameterizedSqliteSelect;if(cached&&cached.instance===instance&&cached.selectKeyword===selectKeyword&&cached.selected===selected&&cached.table===table&&cached.predicateColumn===predicateColumn&&cached.predicateOperator===predicateOperator&&cached.limit===effectiveLimit){if(sqliteDatabase){const statement=cached.statement??=sqliteDatabase.query(cached.sql);if(effectiveLimit===1){const row=statement.get(predicateValue);return row===null?[]:[row]}return statement.all(predicateValue)}return runFastSqliteSql(instance,sqliteDatabase,cached.sql,[predicateValue])}const sql=`${selectKeyword} ${selected} FROM ${table} WHERE ${predicateColumn} ${predicateOperator} ?${effectiveLimit===void 0?"":` LIMIT ${effectiveLimit}`}`;lastParameterizedSqliteSelect={instance,selectKeyword,selected,table,predicateColumn,predicateOperator,limit:effectiveLimit,sql,statement:sqliteDatabase?.query(sql)};if(lastParameterizedSqliteSelect.statement){if(effectiveLimit===1){const row=lastParameterizedSqliteSelect.statement.get(predicateValue);return row===null?[]:[row]}return lastParameterizedSqliteSelect.statement.all(predicateValue)}return runFastSqliteSql(instance,sqliteDatabase,sql,[predicateValue])}let query=`${selectKeyword} ${selected} FROM ${table}`;const params=[];if(predicateColumn!==void 0){query+=` WHERE ${predicateColumn} ${predicateOperator}`;if(predicateValues){query+=` (${renderSqlitePlaceholders(predicateValues.length)})`;params.push(...predicateValues)}else if(predicateParameterized){query+=" ?";params.push(predicateValue)}if(additionalPredicates){const additionalCount=additionalPredicates.length;query+=" AND ";for(let index=0;index<additionalCount;index++){if(index>0)query+=" AND ";const predicate=additionalPredicates[index];if(predicate.values){params.push(...predicate.values);query+=`${predicate.column} ${predicate.operator} (${renderSqlitePlaceholders(predicate.values.length)})`;continue}if(predicate.parameterized){params.push(predicate.value);query+=`${predicate.column} ${predicate.operator} ?`;continue}query+=`${predicate.column} ${predicate.operator}`}}}if(orderings)query+=` ORDER BY ${orderings.map((ordering)=>`${ordering.column} ${ordering.direction.toUpperCase()}`).join(", ")}`;if(effectiveLimit!==void 0)query+=` LIMIT ${effectiveLimit}`;if(rowOffset!==void 0)query+=` OFFSET ${rowOffset}`;return runFastSqliteSql(instance,sqliteDatabase,query,params)},executeFirstStatement=()=>{if(rowLimit===0)return;const selected=selectedColumnsSql??"*";if(predicateColumn===void 0&&orderings===void 0&&rowOffset===void 0){const cached=lastUnparameterizedSqliteSelect;if(cached&&cached.instance===instance&&cached.selectKeyword===selectKeyword&&cached.selected===selected&&cached.table===table&&cached.limit===1){if(sqliteDatabase)return(cached.statement??=sqliteDatabase.query(cached.sql)).get()??void 0;return runFastSqliteSql(instance,sqliteDatabase,cached.sql)[0]}const sql=`${selectKeyword} ${selected} FROM ${table} LIMIT 1`,statement=sqliteDatabase?.query(sql);lastUnparameterizedSqliteSelect={instance,selectKeyword,selected,table,limit:1,sql,statement};return statement?statement.get()??void 0:runFastSqliteSql(instance,sqliteDatabase,sql)[0]}if(predicateColumn!==void 0&&predicateValues===void 0&&predicateParameterized&&additionalPredicates===void 0&&orderings===void 0&&rowOffset===void 0){const cached=lastParameterizedSqliteSelect;if(cached&&cached.instance===instance&&cached.selectKeyword===selectKeyword&&cached.selected===selected&&cached.table===table&&cached.predicateColumn===predicateColumn&&cached.predicateOperator===predicateOperator&&cached.limit===1){if(sqliteDatabase)return(cached.statement??=sqliteDatabase.query(cached.sql)).get(predicateValue)??void 0;return runFastSqliteSql(instance,sqliteDatabase,cached.sql,[predicateValue])[0]}const sql=`${selectKeyword} ${selected} FROM ${table} WHERE ${predicateColumn} ${predicateOperator} ? LIMIT 1`,statement=sqliteDatabase?.query(sql);lastParameterizedSqliteSelect={instance,selectKeyword,selected,table,predicateColumn,predicateOperator,limit:1,sql,statement};return statement?statement.get(predicateValue)??void 0:runFastSqliteSql(instance,sqliteDatabase,sql,[predicateValue])[0]}return executeStatement(!0)[0]},createExtensions=()=>({distinct(){if(selectKeyword==="SELECT DISTINCT")return materialize().distinct();selectKeyword="SELECT DISTINCT";return proxy},where(column,operator,value){if(column!==null&&typeof column==="object"&&!Array.isArray(column)&&operator===void 0&&value===void 0){const prototype=Object.getPrototypeOf(column),entries=prototype===Object.prototype||prototype===null?Object.entries(column):[];if(entries.length>0&&entries.every(([key])=>isSimpleSqliteColumn(key))){for(const[key,entryValue]of entries)if(predicateColumn===void 0){predicateColumn=key;predicateOperator="=";predicateValue=entryValue;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column:key,operator:"=",value:entryValue,parameterized:!0});return proxy}}const builder=materialize();return builder.where.call(builder,column,operator,value)},whereNull(column){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereNull.call(builder,column)}if(predicateColumn===void 0){predicateColumn=column;predicateOperator="IS NULL";predicateValue=void 0;predicateParameterized=!1;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:"IS NULL",value:void 0,parameterized:!1});return proxy},whereNotNull(column){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereNotNull.call(builder,column)}if(predicateColumn===void 0){predicateColumn=column;predicateOperator="IS NOT NULL";predicateValue=void 0;predicateParameterized=!1;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:"IS NOT NULL",value:void 0,parameterized:!1});return proxy},whereLike(column,value,caseSensitive=!1){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereLike.call(builder,column,value,caseSensitive)}if(!caseSensitive)return proxy.whereILike(column,value);if(predicateColumn===void 0){predicateColumn=column;predicateOperator="LIKE";predicateValue=value;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:"LIKE",value,parameterized:!0});return proxy},whereNotLike(column,value,caseSensitive=!1){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereNotLike.call(builder,column,value,caseSensitive)}if(!caseSensitive)return proxy.whereNotILike(column,value);if(predicateColumn===void 0){predicateColumn=column;predicateOperator="NOT LIKE";predicateValue=value;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:"NOT LIKE",value,parameterized:!0});return proxy},whereILike(column,value){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereILike.call(builder,column,value)}const sqlColumn=`LOWER(${column})`;if(predicateColumn===void 0){predicateColumn=sqlColumn;predicateOperator="LIKE LOWER";predicateValue=void 0;predicateParameterized=!1;predicateValues=[value]}else(additionalPredicates??=[]).push({column:sqlColumn,operator:"LIKE LOWER",value:void 0,parameterized:!1,values:[value]});return proxy},whereNotILike(column,value){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereNotILike.call(builder,column,value)}const sqlColumn=`LOWER(${column})`;if(predicateColumn===void 0){predicateColumn=sqlColumn;predicateOperator="NOT LIKE LOWER";predicateValue=void 0;predicateParameterized=!1;predicateValues=[value]}else(additionalPredicates??=[]).push({column:sqlColumn,operator:"NOT LIKE LOWER",value:void 0,parameterized:!1,values:[value]});return proxy},whereIn(column,values){if(typeof column!=="string"||!isSimpleSqliteColumn(column)||!Array.isArray(values)){const builder=materialize();return builder.whereIn.call(builder,column,values)}const snapshot=values.slice();if(predicateColumn===void 0){predicateColumn=column;predicateOperator="IN";predicateValue=void 0;predicateParameterized=!1;predicateValues=snapshot}else(additionalPredicates??=[]).push({column,operator:"IN",value:void 0,parameterized:!1,values:snapshot});return proxy},whereNotIn(column,values){if(typeof column!=="string"||!isSimpleSqliteColumn(column)||!Array.isArray(values)){const builder=materialize();return builder.whereNotIn.call(builder,column,values)}const snapshot=values.slice();if(predicateColumn===void 0){predicateColumn=column;predicateOperator="NOT IN";predicateValue=void 0;predicateParameterized=!1;predicateValues=snapshot}else(additionalPredicates??=[]).push({column,operator:"NOT IN",value:void 0,parameterized:!1,values:snapshot});return proxy},whereBetween(...args){const[column,startOrValues,end]=args,values=Array.isArray(startOrValues)?startOrValues:args.length>=3?[startOrValues,end]:void 0;if(typeof column!=="string"||!isSimpleSqliteColumn(column)||!values||values.length<2){const builder=materialize();return builder.whereBetween.call(builder,...args)}const lower=values[0],upper=values[1];if(predicateColumn===void 0){predicateColumn=column;predicateOperator=">=";predicateValue=lower;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:">=",value:lower,parameterized:!0});(additionalPredicates??=[]).push({column,operator:"<=",value:upper,parameterized:!0});return proxy},whereDate(...args){const[column,operator,date]=args;if(typeof column!=="string"||!isSimpleSqliteColumn(column)||typeof operator!=="string"||!SIMPLE_SQLITE_OPERATORS.has(operator.toLowerCase())||typeof date!=="string"&&!(date instanceof Date)){const builder=materialize();return builder.whereDate.call(builder,...args)}const normalizedDate=date instanceof Date?date.toISOString():date;if(predicateColumn===void 0){predicateColumn=column;predicateOperator=operator;predicateValue=normalizedDate;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator,value:normalizedDate,parameterized:!0});return proxy},orderBy(column,direction="asc"){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.orderBy.call(builder,column,direction)}(orderings??=[]).push({column,direction:direction==="asc"?"asc":"desc"});return proxy},orderByDesc(column){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.orderByDesc.call(builder,column)}(orderings??=[]).push({column,direction:"desc"});return proxy},latest(column){const resolvedColumn=column??queryBuilderConfig.timestamps.defaultOrderColumn;if(typeof resolvedColumn!=="string"||!isSimpleSqliteColumn(resolvedColumn)){const builder=materialize();return builder.latest.call(builder,column)}(orderings??=[]).push({column:resolvedColumn,direction:"desc"});return proxy},oldest(column){const resolvedColumn=column??queryBuilderConfig.timestamps.defaultOrderColumn;if(typeof resolvedColumn!=="string"||!isSimpleSqliteColumn(resolvedColumn)){const builder=materialize();return builder.oldest.call(builder,column)}(orderings??=[]).push({column:resolvedColumn,direction:"asc"});return proxy},offset(value){if(typeof value!=="number"||value<0||!Number.isInteger(value)){const builder=materialize();return builder.offset.call(builder,value)}rowOffset=value;return proxy}});let extensions;const base={select(value){if(materialized)return Reflect.apply(materialized.select,materialized,arguments);if(typeof value==="string"){if(value!=="*"&&!isSimpleSqliteSelection(value)){const builder=materialize();return builder.select.call(builder,value)}columns=value;selectedColumnsSql=value;return proxy}const selected=Array.isArray(value)?value:[value];let simple=!1,selection;const cached=lastSqliteSelection;if(cached&&cached.columns.length===selected.length){simple=!0;for(let index=0;simple&&index<selected.length;index++)simple=cached.columns[index]===selected[index];if(simple)selection=cached.sql}if(!simple){simple=selected.length>0;selection="";for(let index=0;simple&&index<selected.length;index++){const column=selected[index];simple=typeof column==="string"&&(column==="*"||isSimpleSqliteSelection(column));if(simple)selection+=index===0?column:`, ${column}`}if(simple)lastSqliteSelection={columns:selected.slice(),sql:selection}}if(!simple){const builder=materialize();return builder.select.call(builder,value)}columns=selected;selectedColumnsSql=selection;return proxy},where(column,operator,value){if(materialized)return Reflect.apply(materialized.where,materialized,arguments);if(typeof column!=="string"||!isSimpleSqliteColumn(column)||typeof operator!=="string"||operator!=="="&&!SIMPLE_SQLITE_OPERATORS.has(operator)&&!SIMPLE_SQLITE_OPERATORS.has(operator.toLowerCase())){if(typeof column==="string"&&isSimpleSqliteColumn(column)&&typeof operator==="string"){const normalized=operator.toLowerCase();if(normalized==="in"||normalized==="not in"){const values=Array.isArray(value)?snapshotSimpleSqliteMembershipValues(value):[value];if(values!==void 0){const membershipOperator=normalized==="in"?"IN":"NOT IN";if(predicateColumn===void 0){predicateColumn=column;predicateOperator=membershipOperator;predicateValue=void 0;predicateParameterized=!1;predicateValues=values}else(additionalPredicates??=[]).push({column,operator:membershipOperator,value:void 0,parameterized:!1,values});return proxy}}}return(extensions??=createExtensions()).where(column,operator,value)}if(predicateColumn===void 0){predicateColumn=column;predicateOperator=operator;predicateValue=value;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator,value,parameterized:!0});return proxy},limit(value){if(materialized)return Reflect.apply(materialized.limit,materialized,arguments);if(typeof value!=="number"||value<0||!Number.isInteger(value)){const builder=materialize();return builder.limit.call(builder,value)}rowLimit=value;return proxy},execute(){if(materialized)return Reflect.apply(materialized.execute,materialized,arguments);try{return Promise.resolve(executeStatement())}catch(error){return Promise.reject(error)}},executeSync(){if(materialized)return Reflect.apply(materialized.executeSync,materialized,arguments);return executeStatement()},executeTakeFirstSync(){if(materialized)return Reflect.apply(materialized.executeTakeFirstSync,materialized,arguments);return executeFirstStatement()}};let forwardedMethods;proxy=new Proxy(base,{get(target,property){if(materialized){const builder=materialized,value=builder[property];return typeof value==="function"?value.bind(builder):value}if(property==="executeTakeFirst")return target.executeTakeFirst??=function(){if(materialized)return Reflect.apply(materialized.executeTakeFirst,materialized,arguments);try{return Promise.resolve(executeFirstStatement())}catch(error){return Promise.reject(error)}};if(GUARDED_SQLITE_SELECT_METHODS.has(property))return target[property];const cached=forwardedMethods?.[property];if(cached!==void 0)return cached;const value=target[property]??resolveDeferredSqliteTerminal(target,property,executeStatement,executeFirstStatement,materialize,proxy)??(extensions??=createExtensions())[property];if(value===void 0){const builder=materialize(),fallback=builder[property];return typeof fallback==="function"?fallback.bind(builder):fallback}if(typeof value!=="function")return value;const forward=(...args)=>{const builder=materialized;return builder?Reflect.apply(builder[property],builder,args):Reflect.apply(value,proxy,args)};(forwardedMethods??=Object.create(null))[property]=forward;return forward}});return proxy}function selectFromDatabase(table){const dialect=getDialect(),instance=dialect==="sqlite"?getDb():getReadDb();if(dialect==="sqlite"&&!queryBuilderConfig.softDeletes?.enabled&&!hasActiveQueryBuilderHooks()&&isSimpleSqliteTable(table))return guardTransactionQuery(createDeferredSqliteSelect(instance,table));return guardTransactionQuery(instance.selectFrom(table))}function selectFromExplicitReadDatabase(table){const dialect=getDialect(),instance=getExplicitReadDb();if(dialect==="sqlite"&&!queryBuilderConfig.softDeletes?.enabled&&!hasActiveQueryBuilderHooks()&&isSimpleSqliteTable(table))return guardTransactionQuery(createDeferredSqliteSelect(instance,table));return guardTransactionQuery(instance.selectFrom(table))}function unsafeDatabase(query,params){return guardTransactionQuery(getDb().unsafe(query,params))}function unsafeExplicitReadDatabase(query,params){return guardTransactionQuery(getExplicitReadDb().unsafe(query,params))}function insertIntoDatabase(table){markContextWrote();return guardTransactionQuery(getDb().insertInto(table))}function updateTableDatabase(table){markContextWrote();return guardTransactionQuery(getDb().updateTable(table))}function deleteFromDatabase(table){markContextWrote();return guardTransactionQuery(getDb().deleteFrom(table))}function tableDatabase(table){return guardTransactionQuery(getDb().table(table))}function selectDatabase(table,...columns){return guardTransactionQuery(getReadDb().select(table,...columns))}function selectFromSubDatabase(subquery,alias){return guardTransactionQuery(getReadDb().selectFromSub(subquery,alias))}function tableExplicitReadDatabase(table){return guardTransactionQuery(getExplicitReadDb().table(table))}function selectExplicitReadDatabase(table,...columns){return guardTransactionQuery(getExplicitReadDb().select(table,...columns))}function selectFromSubExplicitReadDatabase(subquery,alias){return guardTransactionQuery(getExplicitReadDb().selectFromSub(subquery,alias))}function transactionalDatabase(callback,options){return(...args)=>getDb().transaction((tx)=>callback(tx,...args),options)}const dbFallback=new Proxy({},{get(_target,prop){const resolveInstance=typeof prop==="string"&&READ_ENTRY_POINTS.has(prop)?getReadDb:getDb,value=resolveInstance()[prop];if(typeof value==="function")return forwardDatabaseMethod(prop,resolveInstance);return value}});function forwardDatabaseMethod(prop,resolveInstance){const scope=transactionConnection.getStore();return(...args)=>{if(scope){assertTransactionConnection(scope);if(transactionConnection.getStore()!==scope)throw Error("A transaction query must execute in the callback that created it.")}const instance=resolveInstance();if(typeof prop==="string"&&WRITE_ENTRY_POINTS.has(prop))markContextWrote();return guardTransactionQuery(Reflect.apply(Reflect.get(instance,prop),instance,args))}}export const db=Object.create(dbFallback);Object.defineProperties(db,{deleteFrom:{value:deleteFromDatabase},fn:{value:aggregateFunctions},insertInto:{value:insertIntoDatabase},read:{get:()=>readDb},select:{value:selectDatabase},selectFrom:{value:selectFromDatabase},selectFromSub:{value:selectFromSubDatabase},table:{value:tableDatabase},transactional:{value:transactionalDatabase},unsafe:{value:unsafeDatabase},updateTable:{value:updateTableDatabase}});const readDbFallback=new Proxy({},{get(_target,prop){const value=getExplicitReadDb()[prop];if(typeof value==="function")return forwardDatabaseMethod(prop,getExplicitReadDb);return value}});export const readDb=Object.create(readDbFallback);Object.defineProperties(readDb,{fn:{value:aggregateFunctions},select:{value:selectExplicitReadDatabase},selectFrom:{value:selectFromExplicitReadDatabase},selectFromSub:{value:selectFromSubExplicitReadDatabase},table:{value:tableExplicitReadDatabase},transactional:{value:transactionalDatabase},unsafe:{value:unsafeExplicitReadDatabase}});export{setConfig};
1
+ import{AsyncLocalStorage}from"node:async_hooks";import process from"node:process";import{types as nodeUtilTypes}from"node:util";import{config as queryBuilderConfig,createQueryBuilder,registerPersistentQueryHooks,resetConnection as resetQueryBuilderConnection,setConfig}from"@stacksjs/query-builder";import{SQL}from"bun";import{recordDatabaseQueryError}from"./broken-pool";import{closeDatabaseConnectionsAndPending,retireDatabaseConnections}from"./connection-lifecycle";import{runInTransactionScope}from"./transaction-context";import{env as envVars}from"@stacksjs/env/runtime";import{getConnectionDefaults}from"./defaults";import{isMysqlWire,isPostgresWire,isVitessSharded,toQueryBuilderDialect}from"./dialect";import{relativeMigrationDirectory,resolveMigrationDirectory,snapshotDirForQueryBuilder}from"./migration-path";import{configureDatabaseRoutingContext,contextInTransaction,markContextWrote,resolveReplicaConnection,runInDatabaseRoutingContext,selectReplica,shouldRouteToReplica,withDatabaseRoutingContext,withTransactionContext}from"./replicas";import{aggregateFunctions}from"./types";const sqliteDefaults=getConnectionDefaults("sqlite",envVars),mysqlDefaults=getConnectionDefaults("mysql",envVars),postgresDefaults=getConnectionDefaults("postgres",envVars);let appEnv=envVars.APP_ENV||"local",dbDriver=envVars.DB_CONNECTION||"sqlite",queryBuilderDialect=toQueryBuilderDialect(dbDriver),queryLoggingEnabled=envVars.DB_QUERY_LOGGING_ENABLED??!isProductionEnvironment(appEnv),dbConfig={connections:{sqlite:{database:sqliteDefaults.database,prefix:""},mysql:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},singlestore:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},vitess:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:15306,prefix:"",sharded:isVitessSharded()},postgres:{name:postgresDefaults.database,host:postgresDefaults.host,username:postgresDefaults.username,password:postgresDefaults.password,port:postgresDefaults.port,prefix:""}}},dbConfigLockTail=Promise.resolve();const DB_CONFIG_LOCK_MAX_HOLD_MS=60000;export function acquireDbConfigLock(){let release=()=>{};const held=new Promise((resolve)=>{release=resolve}),acquired=dbConfigLockTail.then(()=>{const watchdog=setTimeout(()=>{console.warn(`[database] the db config lock was held for over ${DB_CONFIG_LOCK_MAX_HOLD_MS/1000}s. Releasing it so the queue can proceed - a test file most likely failed before its afterAll ran.`);release()},DB_CONFIG_LOCK_MAX_HOLD_MS);watchdog.unref?.();held.then(()=>clearTimeout(watchdog));return release});dbConfigLockTail=dbConfigLockTail.then(()=>held).catch(()=>{});return acquired}export function initializeDbConfig(config){if(config?.app?.env)appEnv=config.app.env;if(config?.database?.default)dbDriver=config.database.default;queryBuilderDialect=toQueryBuilderDialect(dbDriver);if(config?.database?.connections)dbConfig=config.database;configureDatabaseRoutingContext(getReplicas().length>0);queryLoggingEnabled=config?.database?.queryLogging?.enabled??envVars.DB_QUERY_LOGGING_ENABLED??!isProductionEnvironment(appEnv);syncDatabaseQueryHooks();syncBrokenPoolDetection();updateQueryBuilderConfig();_dbInstance=null;_replicaInstances=new Map}function getEnv(){return appEnv}function isProductionEnvironment(value){return value==="production"||value==="prod"}function getDriver(){return dbDriver}function getDatabaseConfig(){return dbConfig}function getDialect(){return queryBuilderDialect}export{getDialect as getDatabaseDialect};function getDbConfig(){const driver=getDriver(),database=getDatabaseConfig(),env=getEnv();if(driver==="sqlite"){const defaultName=env!=="testing"?"database/stacks.sqlite":"database/stacks_testing.sqlite";return{database:database.connections?.sqlite?.database??defaultName}}if(driver==="mysql")return{database:database.connections?.mysql?.name||"stacks",host:database.connections?.mysql?.host??"127.0.0.1",username:database.connections?.mysql?.username??"root",password:database.connections?.mysql?.password??"",port:database.connections?.mysql?.port??3306};if(driver==="singlestore")return{database:database.connections?.singlestore?.name||"stacks",host:database.connections?.singlestore?.host??"127.0.0.1",username:database.connections?.singlestore?.username??"root",password:database.connections?.singlestore?.password??"",port:database.connections?.singlestore?.port??3306};if(driver==="vitess")return{database:database.connections?.vitess?.name||"stacks",host:database.connections?.vitess?.host??"127.0.0.1",username:database.connections?.vitess?.username??"root",password:database.connections?.vitess?.password??"",port:database.connections?.vitess?.port??15306};if(driver==="postgres"){const dbName=database.connections?.postgres?.name??"stacks";return{database:env==="testing"?`${dbName}_testing`:dbName,host:database.connections?.postgres?.host??"127.0.0.1",username:database.connections?.postgres?.username??"",password:database.connections?.postgres?.password??"",port:database.connections?.postgres?.port??5432}}return{database:":memory:"}}export const QB_SNAPSHOT_DIR="storage/framework/database";export function qbSnapshotDir(){return snapshotDirForQueryBuilder()}export const RAW_QUERY_SOFT_DELETE_CONFIG={enabled:!1,column:"deleted_at",defaultFilter:!0};export function createDatabaseQueryHooks(dispatch){function forward(event){try{Promise.resolve(dispatch(event)).catch(()=>{})}catch{}}return{onQueryEnd:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs}),onQueryError:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs,error:event.error})}}let queryLoggerModule;function forwardDatabaseQuery(event){transactionConnection.exit(()=>{queryLoggerModule??=import("./query-logger").catch((error)=>{queryLoggerModule=void 0;throw error});queryLoggerModule.then(({logQuery})=>logQuery(event)).catch(()=>{})})}let unregisterDatabaseQueryHooks;function syncDatabaseQueryHooks(){const shouldInstall=!isProductionEnvironment(appEnv)||queryLoggingEnabled;if(shouldInstall&&!unregisterDatabaseQueryHooks)unregisterDatabaseQueryHooks=registerPersistentQueryHooks(createDatabaseQueryHooks(forwardDatabaseQuery));else if(!shouldInstall&&unregisterDatabaseQueryHooks){unregisterDatabaseQueryHooks();unregisterDatabaseQueryHooks=void 0}}syncDatabaseQueryHooks();function primaryPoolIdentity(){const dialect=String(queryBuilderConfig.dialect),configured=queryBuilderConfig.database??{},env=process.env.DB_CONNECTION===dialect?process.env:{};return{driver:dialect,host:env.DB_HOST||configured.host||"localhost",port:Number(env.DB_PORT||configured.port)||void 0,database:String(env.DB_DATABASE||configured.database||"").replace(/^['"]|['"]$/g,"")}}export function recordPrimaryDatabaseQueryError(error){recordDatabaseQueryError(error,primaryPoolIdentity())}let unregisterBrokenPoolDetection;function detectsBrokenPools(){return isPostgresWire(getDriver())||isMysqlWire(getDriver())}function syncBrokenPoolDetection(){const shouldInstall=detectsBrokenPools();if(shouldInstall&&!unregisterBrokenPoolDetection)unregisterBrokenPoolDetection=registerPersistentQueryHooks({onQueryError:(event)=>recordPrimaryDatabaseQueryError(event.error)});else if(!shouldInstall&&unregisterBrokenPoolDetection){unregisterBrokenPoolDetection();unregisterBrokenPoolDetection=void 0}}syncBrokenPoolDetection();function replicaQueryHooks(pool){return{onQueryError:(event)=>{try{recordDatabaseQueryError(event.error,pool);queryBuilderConfig.hooks?.onQueryError?.(event)}catch{}}}}function getPoolConfig(){const driver=getDriver();if(driver==="sqlite")return;return getDatabaseConfig().connections?.[driver]?.pool}const EMPTY_REPLICAS=[];function getReplicas(){const driver=getDriver();if(driver==="sqlite")return EMPTY_REPLICAS;return getDatabaseConfig().connections?.[driver]?.replicas??EMPTY_REPLICAS}function getReadPolicy(){return getDatabaseConfig().reads??{}}export{runInDatabaseRoutingContext,withDatabaseRoutingContext};function toBunPoolOptions(pool){if(!pool)return{};const options={};if(pool.max!==void 0)options.max=pool.max;if(pool.idleTimeoutMs!==void 0)options.idleTimeout=Math.round(pool.idleTimeoutMs/1000);if(pool.acquireTimeoutMs!==void 0)options.connectionTimeout=Math.round(pool.acquireTimeoutMs/1000);if(pool.maxLifetimeMs!==void 0)options.maxLifetime=Math.round(pool.maxLifetimeMs/1000);return options}function updateQueryBuilderConfig(){const dialect=getDialect(),dbConfigForQb=getDbConfig(),pool=getPoolConfig();setConfig({dialect,vitess:{sharded:isVitessSharded(dbConfig.connections.vitess?.sharded)},database:pool?{...dbConfigForQb,pool}:dbConfigForQb,verbose:getEnv()!=="production",snapshotDir:qbSnapshotDir(),migrationDir:relativeMigrationDirectory(resolveMigrationDirectory(toQueryBuilderDialect(dialect),{snapshotDir:qbSnapshotDir()})),timestamps:{createdAt:"created_at",updatedAt:"updated_at",defaultOrderColumn:"created_at"},softDeletes:RAW_QUERY_SOFT_DELETE_CONFIG})}updateQueryBuilderConfig();let _dbInstance=null,_configInitPromise=null;function ensureConfigLoaded(){if(!_configInitPromise)_configInitPromise=(async()=>{try{const{config,overridesReady}=await import("@stacksjs/config");await overridesReady;if(config){initializeDbConfig(config);_dbInstance=null}}catch{}})();return _configInitPromise}export async function ensureDatabaseConfigLoaded(){await ensureConfigLoaded()}export{applySqlitePragmas,SQLITE_BOOTSTRAP_PRAGMAS}from"@stacksjs/query-builder";const sqliteTxOwner=new AsyncLocalStorage;let sqliteTxTail=Promise.resolve();function runOwnedSqliteTransaction(run,parent){const owner={parent,accepting:!0,transactions:0,completionTail:Promise.resolve()};return sqliteTxOwner.run(owner,async()=>{try{return await run()}finally{owner.accepting=!1;await owner.completionTail}})}function serializeSqliteTransaction(run){let owner=sqliteTxOwner.getStore();while(owner&&!owner.accepting)owner=owner.parent;if(owner?.accepting&&owner.transactions>0)return sqliteTxOwner.run(owner,run);const completionOwner=owner?.accepting?owner:void 0,result=(completionOwner?.completionTail??sqliteTxTail).then(()=>runOwnedSqliteTransaction(run,completionOwner)),tail=result.then(()=>{return},()=>{return});if(completionOwner)completionOwner.completionTail=tail;else sqliteTxTail=tail;return result}function applySqliteTransactionSerialization(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>serializeSqliteTransaction(()=>original(...args))}const dispatchScopedBuilders=new WeakSet,transactionConnection=new AsyncLocalStorage;function activeTransactionConnection(){const scope=transactionConnection.getStore();if(!scope)return;assertTransactionConnection(scope);return scope.connection}function assertTransactionConnection(scope){for(let owner=scope;owner;owner=owner.parent)if(!owner.active)throw Error("Database work cannot continue from a closed transaction callback.");if(scope.nested)throw Error("Await the active nested transaction before issuing more database work.")}function guardTransactionQuery(value){const scope=transactionConnection.getStore();if(!scope||!value||typeof value!=="object"||typeof value.execute!=="function")return value;const wrappers=new WeakMap,wrap=(target)=>{const cached=wrappers.get(target);if(cached)return cached;const proxy=new Proxy(target,{get(object,property){const member=Reflect.get(object,property,object);if(typeof member!=="function")return member;return(...args)=>{assertTransactionConnection(scope);if(transactionConnection.getStore()!==scope)throw Error("A transaction query must execute in the callback that created it.");const guardedArgs=args.map((arg)=>typeof arg==="function"?function(...values){return Reflect.apply(arg,this,values.map((value)=>value&&typeof value==="object"&&typeof Reflect.get(value,"execute")==="function"?wrap(value):value))}:arg),result=Reflect.apply(member,object,guardedArgs);if(result===object)return proxy;if(result&&typeof result==="object"&&!nodeUtilTypes.isPromise(result)&&(typeof Reflect.get(result,"execute")==="function"||typeof Reflect.get(result,"next")==="function"))return wrap(result);return result}}});wrappers.set(target,proxy);return proxy};return wrap(value)}function applyTransactionDispatchScope(instance){if(dispatchScopedBuilders.has(instance))return;dispatchScopedBuilders.add(instance);let defaultOptions={};const setDefaults=instance.setTransactionDefaults.bind(instance);instance.setTransactionDefaults=(defaults)=>{setDefaults(defaults);defaultOptions={...defaultOptions,...defaults}};for(const method of["transaction","savepoint"]){const original=instance[method].bind(instance);instance[method]=async(callback,options)=>{const active=activeTransactionConnection();if(active&&active!==instance)return active[method](callback,options);const parent=transactionConnection.getStore(),effectiveOptions={...defaultOptions,...options},afterCommit=method==="transaction"?effectiveOptions.afterCommit:void 0,parentObserver=(listener)=>listener?(...args)=>{const nested=parent?.nested;if(parent)parent.nested=!1;try{listener(...args)}finally{if(parent)parent.nested=nested}}:void 0,result=await runInTransactionScope(async(runAttempt)=>{const owner=sqliteTxOwner.getStore();if(owner)owner.transactions++;if(parent)parent.nested=!0;try{return await original((tx)=>runAttempt(async()=>{applyTransactionDispatchScope(tx);const scope={connection:tx,parent,active:!0,nested:!1};return transactionConnection.run(scope,async()=>{try{return await callback(tx)}finally{scope.active=!1}})}),method==="transaction"?{...options,afterCommit:void 0,onRollback:parentObserver(effectiveOptions.onRollback),afterRollback:parentObserver(effectiveOptions.afterRollback),onRetry:parentObserver(effectiveOptions.onRetry)}:options)}finally{if(parent)parent.nested=!1;if(owner)owner.transactions--}});await afterCommit?.();return result}}}function applyTransactionRoutingContext(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>withTransactionContext(()=>original(...args))}function getDb(){if(_databaseClosePromise)throw Error("Database connections are shutting down");const active=activeTransactionConnection();if(active)return active;if(!_dbInstance){updateQueryBuilderConfig();_dbInstance=createQueryBuilder();applyTransactionDispatchScope(_dbInstance);if(getDialect()==="sqlite")applySqliteTransactionSerialization(_dbInstance);applyTransactionRoutingContext(_dbInstance)}return _dbInstance}let _replicaInstances=new Map,_databaseClosePromise=null;const _retiredConnectionClosures=new Set;export function resetDatabaseConnection(){const connections=[..._dbInstance?[_dbInstance]:[],..._replicaInstances.values()];if(connections.length>0)retireDatabaseConnections(_retiredConnectionClosures,connections,(error)=>console.error(`[database] Failed to close retired connection: ${error.message}`));resetQueryBuilderConnection();lastGeneralSqliteStatement=void 0;_dbInstance=null;_replicaInstances=new Map}export function closeDatabaseConnection(){if(_databaseClosePromise)return _databaseClosePromise;const connections=[..._dbInstance?[_dbInstance]:[],..._replicaInstances.values()];_dbInstance=null;_replicaInstances=new Map;lastGeneralSqliteStatement=void 0;const closing=(async()=>{try{await closeDatabaseConnectionsAndPending(connections,_retiredConnectionClosures)}finally{resetQueryBuilderConnection()}})();_databaseClosePromise=closing;closing.then(()=>{if(_databaseClosePromise===closing)_databaseClosePromise=null},()=>{if(_databaseClosePromise===closing)_databaseClosePromise=null});return closing}function getReplicaDb(replica){const primary=getDbConfig(),resolved=resolveReplicaConnection(replica,primary),key=`${resolved.host}:${resolved.port??""}`,cached=_replicaInstances.get(key);if(cached)return cached;const scheme=isMysqlWire(getDriver())?"mysql":"postgres",auth=resolved.username?`${encodeURIComponent(resolved.username)}:${encodeURIComponent(resolved.password??"")}@`:"",url=`${scheme}://${auth}${resolved.host}:${resolved.port}/${resolved.database}`,sql=new SQL({url,...toBunPoolOptions(getPoolConfig())}),instance=detectsBrokenPools()?createQueryBuilder({sql,hooks:replicaQueryHooks({driver:getDriver(),host:resolved.host,port:resolved.port,database:resolved.database})}):createQueryBuilder({sql});_replicaInstances.set(key,instance);return instance}function getReadDb(){const active=activeTransactionConnection();if(active)return active;const replicas=getReplicas();if(replicas.length===0)return getDb();const policy=getReadPolicy();if(!shouldRouteToReplica({policy,replicas}))return getDb();const replica=selectReplica(replicas,policy.strategy);return replica?getReplicaDb(replica):getDb()}function getExplicitReadDb(){const active=activeTransactionConnection();if(active)return active;const replicas=getReplicas();if(!replicas.length||contextInTransaction())return getDb();const replica=selectReplica(replicas,getReadPolicy().strategy);return replica?getReplicaDb(replica):getDb()}const WRITE_ENTRY_POINTS=new Set(["insertInto","updateTable","deleteFrom","create","createMany","insertOrIgnore","insertGetId","updateOrInsert","upsert"]),READ_ENTRY_POINTS=new Set(["selectFrom","selectFromSub","select"]);ensureConfigLoaded();export function asRows(rows){return rows}export function asRow(row){return row}const SIMPLE_SQLITE_TABLE=/^[A-Z_][A-Z0-9_]*$/i,SIMPLE_SQLITE_COLUMN=/^[A-Z_][A-Z0-9_]*(?:\.[A-Z_][A-Z0-9_]*)?$/i,SIMPLE_SQLITE_SELECTION=/^[A-Z_][A-Z0-9_]*(?:\.[A-Z_][A-Z0-9_]*)?(?:\s+AS\s+[A-Z_][A-Z0-9_]*)?$/i,SIMPLE_SQLITE_OPERATORS=new Set(["=","!=","<>","<","<=",">",">=","like","not like"]),SQLITE_IDENTIFIER_CACHE_LIMIT=512;function memoizeSqliteIdentifier(pattern){const valid=new Set;return(value)=>{if(valid.has(value))return!0;if(!pattern.test(value))return!1;if(valid.size<SQLITE_IDENTIFIER_CACHE_LIMIT)valid.add(value);return!0}}const isSimpleSqliteTable=memoizeSqliteIdentifier(SIMPLE_SQLITE_TABLE),isSimpleSqliteColumn=memoizeSqliteIdentifier(SIMPLE_SQLITE_COLUMN),isSimpleSqliteSelection=memoizeSqliteIdentifier(SIMPLE_SQLITE_SELECTION);let lastParameterizedSqliteSelect,lastUnparameterizedSqliteSelect,lastSqliteSelection;const GUARDED_SQLITE_SELECT_METHODS=new Set(["select","where","limit","execute","executeSync","executeTakeFirstSync"]);function hasActiveQueryBuilderHooks(){return Boolean(queryBuilderConfig.hooks&&Object.values(queryBuilderConfig.hooks).some((value)=>value!==void 0))}function resolveDeferredSqliteTerminal(target,property,executeStatement,executeFirstStatement,materialize,proxy){if(property==="get"){target.get=target.execute;return target.execute}if(property==="selectAll"){const selectAll=()=>proxy;target.selectAll=selectAll;return selectAll}if(property==="first"){const first=async()=>executeFirstStatement();target.first=first;return first}if(property==="firstOrFail"||property==="executeTakeFirstOrThrow"){const firstOrFail=async()=>{const row=executeFirstStatement();if(row===void 0)throw Error("Record not found");return row};target.firstOrFail=firstOrFail;target.executeTakeFirstOrThrow=firstOrFail;return firstOrFail}if(property==="exists"||property==="doesntExist"){const findsRow=property==="exists",check=async()=>executeFirstStatement()!==void 0===findsRow;target[property]=check;return check}if(property==="value"){const value=async(column)=>executeFirstStatement()?.[column];target.value=value;return value}if(property==="count"||property==="sum"||property==="avg"||property==="min"||property==="max"){const emptyValue=property==="min"||property==="max"?null:0,aggregate=(...args)=>runDeferredSqliteAggregate(property,args,emptyValue,materialize,executeStatement);target[property]=aggregate;return aggregate}if(property==="pluck"){const pluck=(...args)=>{if(args.length!==1){const builder=materialize();return builder.pluck.call(builder,...args)}try{const column=args[0],rows=executeStatement(),values=Array(rows.length);for(let index=0;index<rows.length;index++)values[index]=rows[index]?.[column];return Promise.resolve(values)}catch(error){return Promise.reject(error)}};target.pluck=pluck;return pluck}}function runDeferredSqliteAggregate(name,args,emptyValue,materialize,executeStatement){const column=args[0],acceptsNoColumn=name==="count"&&args.length===0;if(!acceptsNoColumn&&(args.length!==1||typeof column!=="string"||!isSimpleSqliteColumn(column))){const builder=materialize();return builder[name].call(builder,...args)}try{const expression=`${name.toUpperCase()}(${acceptsNoColumn?"*":column}) AS aggregate`,value=executeStatement(!1,expression)[0]?.aggregate??emptyValue;return Promise.resolve(name==="count"||name==="sum"||name==="avg"?Number(value):value)}catch(error){return Promise.reject(error)}}const fastSqliteDatabaseCache=new WeakMap;let lastGeneralSqliteStatement;function fastSqliteDatabase(instance){if(fastSqliteDatabaseCache.has(instance))return fastSqliteDatabaseCache.get(instance)??void 0;const database=instance.sql?._wrapper?.database,resolved=database&&typeof database.query==="function"?database:null;fastSqliteDatabaseCache.set(instance,resolved);return resolved??void 0}function runFastSqliteSql(instance,sqliteDatabase,sql,params){if(sqliteDatabase){let cached=lastGeneralSqliteStatement;if(!cached||cached.database!==sqliteDatabase||cached.sql!==sql){cached={database:sqliteDatabase,sql,statement:sqliteDatabase.query(sql)};lastGeneralSqliteStatement=cached}return params?cached.statement.all(...params):cached.statement.all()}return instance.unsafe(sql,params).executeSync()}let lastSqlitePlaceholders;function renderSqlitePlaceholders(length){const cached=lastSqlitePlaceholders;if(cached&&cached.length===length)return cached.sql;const sql=Array(length).fill("?").join(", ");if(typeof length==="number")lastSqlitePlaceholders={length,sql};return sql}function snapshotSimpleSqliteMembershipValues(values){if(nodeUtilTypes.isProxy(values)||Object.getPrototypeOf(values)!==Array.prototype||Object.hasOwn(values,Symbol.iterator))return;const snapshot=Array(values.length);for(let index=0;index<values.length;index++){const descriptor=Object.getOwnPropertyDescriptor(values,index);if(!descriptor||!("value"in descriptor))return;snapshot[index]=descriptor.value}return snapshot}function createDeferredSqliteSelect(instance,table){const sqliteDatabase=fastSqliteDatabase(instance);let selectKeyword="SELECT",columns,selectedColumnsSql,predicateColumn,predicateOperator,predicateValue,predicateParameterized=!0,predicateValues,additionalPredicates,orderings,rowLimit,rowOffset,materialized;const materialize=()=>{if(materialized)return materialized;let builder=instance.selectFrom(table);if(columns)builder=builder.select.call(builder,columns);if(selectKeyword==="SELECT DISTINCT")builder=builder.distinct.call(builder);if(predicateColumn!==void 0){if(predicateValues)if(predicateOperator==="LIKE LOWER"||predicateOperator==="NOT LIKE LOWER")builder=builder[predicateOperator==="LIKE LOWER"?"whereILike":"whereNotILike"].call(builder,predicateColumn.slice(6,-1),predicateValues[0]);else builder=builder[predicateOperator==="IN"?"whereIn":"whereNotIn"].call(builder,predicateColumn,predicateValues);else if(predicateParameterized)builder=builder.where.call(builder,predicateColumn,predicateOperator,predicateValue);else builder=builder[predicateOperator==="IS NULL"?"whereNull":"whereNotNull"].call(builder,predicateColumn);if(additionalPredicates)for(const predicate of additionalPredicates)if(predicate.values)if(predicate.operator==="LIKE LOWER"||predicate.operator==="NOT LIKE LOWER"){const method=predicate.operator==="LIKE LOWER"?"whereILike":"whereNotILike";builder=builder[method].call(builder,predicate.column.slice(6,-1),predicate.values[0])}else{const method=predicate.operator==="IN"?"whereIn":"whereNotIn";builder=builder[method].call(builder,predicate.column,predicate.values)}else if(predicate.parameterized)builder=builder.where.call(builder,predicate.column,predicate.operator,predicate.value);else{const method=predicate.operator==="IS NULL"?"whereNull":"whereNotNull";builder=builder[method].call(builder,predicate.column)}}if(orderings){const apply=builder.orderBy;for(const ordering of orderings)builder=apply.call(builder,ordering.column,ordering.direction)}if(rowLimit!==void 0)builder=builder.limit.call(builder,rowLimit);if(rowOffset!==void 0)builder=builder.offset.call(builder,rowOffset);materialized=builder;return builder};let proxy;const executeStatement=(firstOnly=!1,selection)=>{const selected=selection??selectedColumnsSql??"*",effectiveLimit=rowLimit??(firstOnly&&rowOffset===void 0?1:void 0);if(predicateColumn===void 0&&orderings===void 0&&rowOffset===void 0){const cached=lastUnparameterizedSqliteSelect;if(cached&&cached.instance===instance&&cached.selectKeyword===selectKeyword&&cached.selected===selected&&cached.table===table&&cached.limit===effectiveLimit){if(sqliteDatabase){const statement=cached.statement??=sqliteDatabase.query(cached.sql);if(effectiveLimit===1){const row=statement.get();return row===null?[]:[row]}return statement.all()}return runFastSqliteSql(instance,sqliteDatabase,cached.sql)}const limit=effectiveLimit===void 0?"":` LIMIT ${effectiveLimit}`,sql=`${selectKeyword} ${selected} FROM ${table}${limit}`;lastUnparameterizedSqliteSelect={instance,selectKeyword,selected,table,limit:effectiveLimit,sql,statement:sqliteDatabase?.query(sql)};if(lastUnparameterizedSqliteSelect.statement){if(effectiveLimit===1){const row=lastUnparameterizedSqliteSelect.statement.get();return row===null?[]:[row]}return lastUnparameterizedSqliteSelect.statement.all()}return runFastSqliteSql(instance,sqliteDatabase,sql)}if(predicateColumn!==void 0&&predicateValues===void 0&&predicateParameterized&&additionalPredicates===void 0&&orderings===void 0&&rowOffset===void 0){const cached=lastParameterizedSqliteSelect;if(cached&&cached.instance===instance&&cached.selectKeyword===selectKeyword&&cached.selected===selected&&cached.table===table&&cached.predicateColumn===predicateColumn&&cached.predicateOperator===predicateOperator&&cached.limit===effectiveLimit){if(sqliteDatabase){const statement=cached.statement??=sqliteDatabase.query(cached.sql);if(effectiveLimit===1){const row=statement.get(predicateValue);return row===null?[]:[row]}return statement.all(predicateValue)}return runFastSqliteSql(instance,sqliteDatabase,cached.sql,[predicateValue])}const sql=`${selectKeyword} ${selected} FROM ${table} WHERE ${predicateColumn} ${predicateOperator} ?${effectiveLimit===void 0?"":` LIMIT ${effectiveLimit}`}`;lastParameterizedSqliteSelect={instance,selectKeyword,selected,table,predicateColumn,predicateOperator,limit:effectiveLimit,sql,statement:sqliteDatabase?.query(sql)};if(lastParameterizedSqliteSelect.statement){if(effectiveLimit===1){const row=lastParameterizedSqliteSelect.statement.get(predicateValue);return row===null?[]:[row]}return lastParameterizedSqliteSelect.statement.all(predicateValue)}return runFastSqliteSql(instance,sqliteDatabase,sql,[predicateValue])}let query=`${selectKeyword} ${selected} FROM ${table}`;const params=[];if(predicateColumn!==void 0){query+=` WHERE ${predicateColumn} ${predicateOperator}`;if(predicateValues){query+=` (${renderSqlitePlaceholders(predicateValues.length)})`;params.push(...predicateValues)}else if(predicateParameterized){query+=" ?";params.push(predicateValue)}if(additionalPredicates){const additionalCount=additionalPredicates.length;query+=" AND ";for(let index=0;index<additionalCount;index++){if(index>0)query+=" AND ";const predicate=additionalPredicates[index];if(predicate.values){params.push(...predicate.values);query+=`${predicate.column} ${predicate.operator} (${renderSqlitePlaceholders(predicate.values.length)})`;continue}if(predicate.parameterized){params.push(predicate.value);query+=`${predicate.column} ${predicate.operator} ?`;continue}query+=`${predicate.column} ${predicate.operator}`}}}if(orderings)query+=` ORDER BY ${orderings.map((ordering)=>`${ordering.column} ${ordering.direction.toUpperCase()}`).join(", ")}`;if(effectiveLimit!==void 0)query+=` LIMIT ${effectiveLimit}`;if(rowOffset!==void 0)query+=` OFFSET ${rowOffset}`;return runFastSqliteSql(instance,sqliteDatabase,query,params)},executeFirstStatement=()=>{if(rowLimit===0)return;const selected=selectedColumnsSql??"*";if(predicateColumn===void 0&&orderings===void 0&&rowOffset===void 0){const cached=lastUnparameterizedSqliteSelect;if(cached&&cached.instance===instance&&cached.selectKeyword===selectKeyword&&cached.selected===selected&&cached.table===table&&cached.limit===1){if(sqliteDatabase)return(cached.statement??=sqliteDatabase.query(cached.sql)).get()??void 0;return runFastSqliteSql(instance,sqliteDatabase,cached.sql)[0]}const sql=`${selectKeyword} ${selected} FROM ${table} LIMIT 1`,statement=sqliteDatabase?.query(sql);lastUnparameterizedSqliteSelect={instance,selectKeyword,selected,table,limit:1,sql,statement};return statement?statement.get()??void 0:runFastSqliteSql(instance,sqliteDatabase,sql)[0]}if(predicateColumn!==void 0&&predicateValues===void 0&&predicateParameterized&&additionalPredicates===void 0&&orderings===void 0&&rowOffset===void 0){const cached=lastParameterizedSqliteSelect;if(cached&&cached.instance===instance&&cached.selectKeyword===selectKeyword&&cached.selected===selected&&cached.table===table&&cached.predicateColumn===predicateColumn&&cached.predicateOperator===predicateOperator&&cached.limit===1){if(sqliteDatabase)return(cached.statement??=sqliteDatabase.query(cached.sql)).get(predicateValue)??void 0;return runFastSqliteSql(instance,sqliteDatabase,cached.sql,[predicateValue])[0]}const sql=`${selectKeyword} ${selected} FROM ${table} WHERE ${predicateColumn} ${predicateOperator} ? LIMIT 1`,statement=sqliteDatabase?.query(sql);lastParameterizedSqliteSelect={instance,selectKeyword,selected,table,predicateColumn,predicateOperator,limit:1,sql,statement};return statement?statement.get(predicateValue)??void 0:runFastSqliteSql(instance,sqliteDatabase,sql,[predicateValue])[0]}return executeStatement(!0)[0]},createExtensions=()=>({distinct(){if(selectKeyword==="SELECT DISTINCT")return materialize().distinct();selectKeyword="SELECT DISTINCT";return proxy},where(column,operator,value){if(column!==null&&typeof column==="object"&&!Array.isArray(column)&&operator===void 0&&value===void 0){const prototype=Object.getPrototypeOf(column),entries=prototype===Object.prototype||prototype===null?Object.entries(column):[];if(entries.length>0&&entries.every(([key])=>isSimpleSqliteColumn(key))){for(const[key,entryValue]of entries)if(predicateColumn===void 0){predicateColumn=key;predicateOperator="=";predicateValue=entryValue;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column:key,operator:"=",value:entryValue,parameterized:!0});return proxy}}const builder=materialize();return builder.where.call(builder,column,operator,value)},whereNull(column){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereNull.call(builder,column)}if(predicateColumn===void 0){predicateColumn=column;predicateOperator="IS NULL";predicateValue=void 0;predicateParameterized=!1;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:"IS NULL",value:void 0,parameterized:!1});return proxy},whereNotNull(column){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereNotNull.call(builder,column)}if(predicateColumn===void 0){predicateColumn=column;predicateOperator="IS NOT NULL";predicateValue=void 0;predicateParameterized=!1;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:"IS NOT NULL",value:void 0,parameterized:!1});return proxy},whereLike(column,value,caseSensitive=!1){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereLike.call(builder,column,value,caseSensitive)}if(!caseSensitive)return proxy.whereILike(column,value);if(predicateColumn===void 0){predicateColumn=column;predicateOperator="LIKE";predicateValue=value;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:"LIKE",value,parameterized:!0});return proxy},whereNotLike(column,value,caseSensitive=!1){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereNotLike.call(builder,column,value,caseSensitive)}if(!caseSensitive)return proxy.whereNotILike(column,value);if(predicateColumn===void 0){predicateColumn=column;predicateOperator="NOT LIKE";predicateValue=value;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:"NOT LIKE",value,parameterized:!0});return proxy},whereILike(column,value){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereILike.call(builder,column,value)}const sqlColumn=`LOWER(${column})`;if(predicateColumn===void 0){predicateColumn=sqlColumn;predicateOperator="LIKE LOWER";predicateValue=void 0;predicateParameterized=!1;predicateValues=[value]}else(additionalPredicates??=[]).push({column:sqlColumn,operator:"LIKE LOWER",value:void 0,parameterized:!1,values:[value]});return proxy},whereNotILike(column,value){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.whereNotILike.call(builder,column,value)}const sqlColumn=`LOWER(${column})`;if(predicateColumn===void 0){predicateColumn=sqlColumn;predicateOperator="NOT LIKE LOWER";predicateValue=void 0;predicateParameterized=!1;predicateValues=[value]}else(additionalPredicates??=[]).push({column:sqlColumn,operator:"NOT LIKE LOWER",value:void 0,parameterized:!1,values:[value]});return proxy},whereIn(column,values){if(typeof column!=="string"||!isSimpleSqliteColumn(column)||!Array.isArray(values)){const builder=materialize();return builder.whereIn.call(builder,column,values)}const snapshot=values.slice();if(predicateColumn===void 0){predicateColumn=column;predicateOperator="IN";predicateValue=void 0;predicateParameterized=!1;predicateValues=snapshot}else(additionalPredicates??=[]).push({column,operator:"IN",value:void 0,parameterized:!1,values:snapshot});return proxy},whereNotIn(column,values){if(typeof column!=="string"||!isSimpleSqliteColumn(column)||!Array.isArray(values)){const builder=materialize();return builder.whereNotIn.call(builder,column,values)}const snapshot=values.slice();if(predicateColumn===void 0){predicateColumn=column;predicateOperator="NOT IN";predicateValue=void 0;predicateParameterized=!1;predicateValues=snapshot}else(additionalPredicates??=[]).push({column,operator:"NOT IN",value:void 0,parameterized:!1,values:snapshot});return proxy},whereBetween(...args){const[column,startOrValues,end]=args,values=Array.isArray(startOrValues)?startOrValues:args.length>=3?[startOrValues,end]:void 0;if(typeof column!=="string"||!isSimpleSqliteColumn(column)||!values||values.length<2){const builder=materialize();return builder.whereBetween.call(builder,...args)}const lower=values[0],upper=values[1];if(predicateColumn===void 0){predicateColumn=column;predicateOperator=">=";predicateValue=lower;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator:">=",value:lower,parameterized:!0});(additionalPredicates??=[]).push({column,operator:"<=",value:upper,parameterized:!0});return proxy},whereDate(...args){const[column,operator,date]=args;if(typeof column!=="string"||!isSimpleSqliteColumn(column)||typeof operator!=="string"||!SIMPLE_SQLITE_OPERATORS.has(operator.toLowerCase())||typeof date!=="string"&&!(date instanceof Date)){const builder=materialize();return builder.whereDate.call(builder,...args)}const normalizedDate=date instanceof Date?date.toISOString():date;if(predicateColumn===void 0){predicateColumn=column;predicateOperator=operator;predicateValue=normalizedDate;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator,value:normalizedDate,parameterized:!0});return proxy},orderBy(column,direction="asc"){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.orderBy.call(builder,column,direction)}(orderings??=[]).push({column,direction:direction==="asc"?"asc":"desc"});return proxy},orderByDesc(column){if(typeof column!=="string"||!isSimpleSqliteColumn(column)){const builder=materialize();return builder.orderByDesc.call(builder,column)}(orderings??=[]).push({column,direction:"desc"});return proxy},latest(column){const resolvedColumn=column??queryBuilderConfig.timestamps.defaultOrderColumn;if(typeof resolvedColumn!=="string"||!isSimpleSqliteColumn(resolvedColumn)){const builder=materialize();return builder.latest.call(builder,column)}(orderings??=[]).push({column:resolvedColumn,direction:"desc"});return proxy},oldest(column){const resolvedColumn=column??queryBuilderConfig.timestamps.defaultOrderColumn;if(typeof resolvedColumn!=="string"||!isSimpleSqliteColumn(resolvedColumn)){const builder=materialize();return builder.oldest.call(builder,column)}(orderings??=[]).push({column:resolvedColumn,direction:"asc"});return proxy},offset(value){if(typeof value!=="number"||value<0||!Number.isInteger(value)){const builder=materialize();return builder.offset.call(builder,value)}rowOffset=value;return proxy}});let extensions;const base={select(value){if(materialized)return Reflect.apply(materialized.select,materialized,arguments);if(typeof value==="string"){if(value!=="*"&&!isSimpleSqliteSelection(value)){const builder=materialize();return builder.select.call(builder,value)}columns=value;selectedColumnsSql=value;return proxy}const selected=Array.isArray(value)?value:[value];let simple=!1,selection;const cached=lastSqliteSelection;if(cached&&cached.columns.length===selected.length){simple=!0;for(let index=0;simple&&index<selected.length;index++)simple=cached.columns[index]===selected[index];if(simple)selection=cached.sql}if(!simple){simple=selected.length>0;selection="";for(let index=0;simple&&index<selected.length;index++){const column=selected[index];simple=typeof column==="string"&&(column==="*"||isSimpleSqliteSelection(column));if(simple)selection+=index===0?column:`, ${column}`}if(simple)lastSqliteSelection={columns:selected.slice(),sql:selection}}if(!simple){const builder=materialize();return builder.select.call(builder,value)}columns=selected;selectedColumnsSql=selection;return proxy},where(column,operator,value){if(materialized)return Reflect.apply(materialized.where,materialized,arguments);if(typeof column!=="string"||!isSimpleSqliteColumn(column)||typeof operator!=="string"||operator!=="="&&!SIMPLE_SQLITE_OPERATORS.has(operator)&&!SIMPLE_SQLITE_OPERATORS.has(operator.toLowerCase())){if(typeof column==="string"&&isSimpleSqliteColumn(column)&&typeof operator==="string"){const normalized=operator.toLowerCase();if(normalized==="in"||normalized==="not in"){const values=Array.isArray(value)?snapshotSimpleSqliteMembershipValues(value):[value];if(values!==void 0){const membershipOperator=normalized==="in"?"IN":"NOT IN";if(predicateColumn===void 0){predicateColumn=column;predicateOperator=membershipOperator;predicateValue=void 0;predicateParameterized=!1;predicateValues=values}else(additionalPredicates??=[]).push({column,operator:membershipOperator,value:void 0,parameterized:!1,values});return proxy}}}return(extensions??=createExtensions()).where(column,operator,value)}if(predicateColumn===void 0){predicateColumn=column;predicateOperator=operator;predicateValue=value;predicateParameterized=!0;predicateValues=void 0}else(additionalPredicates??=[]).push({column,operator,value,parameterized:!0});return proxy},limit(value){if(materialized)return Reflect.apply(materialized.limit,materialized,arguments);if(typeof value!=="number"||value<0||!Number.isInteger(value)){const builder=materialize();return builder.limit.call(builder,value)}rowLimit=value;return proxy},execute(){if(materialized)return Reflect.apply(materialized.execute,materialized,arguments);try{return Promise.resolve(executeStatement())}catch(error){return Promise.reject(error)}},executeSync(){if(materialized)return Reflect.apply(materialized.executeSync,materialized,arguments);return executeStatement()},executeTakeFirstSync(){if(materialized)return Reflect.apply(materialized.executeTakeFirstSync,materialized,arguments);return executeFirstStatement()}};let forwardedMethods;proxy=new Proxy(base,{get(target,property){if(materialized){const builder=materialized,value=builder[property];return typeof value==="function"?value.bind(builder):value}if(property==="executeTakeFirst")return target.executeTakeFirst??=function(){if(materialized)return Reflect.apply(materialized.executeTakeFirst,materialized,arguments);try{return Promise.resolve(executeFirstStatement())}catch(error){return Promise.reject(error)}};if(GUARDED_SQLITE_SELECT_METHODS.has(property))return target[property];const cached=forwardedMethods?.[property];if(cached!==void 0)return cached;const value=target[property]??resolveDeferredSqliteTerminal(target,property,executeStatement,executeFirstStatement,materialize,proxy)??(extensions??=createExtensions())[property];if(value===void 0){const builder=materialize(),fallback=builder[property];return typeof fallback==="function"?fallback.bind(builder):fallback}if(typeof value!=="function")return value;const forward=(...args)=>{const builder=materialized;return builder?Reflect.apply(builder[property],builder,args):Reflect.apply(value,proxy,args)};(forwardedMethods??=Object.create(null))[property]=forward;return forward}});return proxy}function selectFromDatabase(table){const dialect=getDialect(),instance=dialect==="sqlite"?getDb():getReadDb();if(dialect==="sqlite"&&!queryBuilderConfig.softDeletes?.enabled&&!hasActiveQueryBuilderHooks()&&isSimpleSqliteTable(table))return guardTransactionQuery(createDeferredSqliteSelect(instance,table));return guardTransactionQuery(instance.selectFrom(table))}function selectFromExplicitReadDatabase(table){const dialect=getDialect(),instance=getExplicitReadDb();if(dialect==="sqlite"&&!queryBuilderConfig.softDeletes?.enabled&&!hasActiveQueryBuilderHooks()&&isSimpleSqliteTable(table))return guardTransactionQuery(createDeferredSqliteSelect(instance,table));return guardTransactionQuery(instance.selectFrom(table))}function unsafeDatabase(query,params){return guardTransactionQuery(getDb().unsafe(query,params))}function unsafeExplicitReadDatabase(query,params){return guardTransactionQuery(getExplicitReadDb().unsafe(query,params))}function insertIntoDatabase(table){markContextWrote();return guardTransactionQuery(getDb().insertInto(table))}function updateTableDatabase(table){markContextWrote();return guardTransactionQuery(getDb().updateTable(table))}function deleteFromDatabase(table){markContextWrote();return guardTransactionQuery(getDb().deleteFrom(table))}function tableDatabase(table){return guardTransactionQuery(getDb().table(table))}function selectDatabase(table,...columns){return guardTransactionQuery(getReadDb().select(table,...columns))}function selectFromSubDatabase(subquery,alias){return guardTransactionQuery(getReadDb().selectFromSub(subquery,alias))}function tableExplicitReadDatabase(table){return guardTransactionQuery(getExplicitReadDb().table(table))}function selectExplicitReadDatabase(table,...columns){return guardTransactionQuery(getExplicitReadDb().select(table,...columns))}function selectFromSubExplicitReadDatabase(subquery,alias){return guardTransactionQuery(getExplicitReadDb().selectFromSub(subquery,alias))}function transactionalDatabase(callback,options){return(...args)=>getDb().transaction((tx)=>callback(tx,...args),options)}const dbFallback=new Proxy({},{get(_target,prop){const resolveInstance=typeof prop==="string"&&READ_ENTRY_POINTS.has(prop)?getReadDb:getDb,value=resolveInstance()[prop];if(typeof value==="function")return forwardDatabaseMethod(prop,resolveInstance);return value}});function forwardDatabaseMethod(prop,resolveInstance){const scope=transactionConnection.getStore();return(...args)=>{if(scope){assertTransactionConnection(scope);if(transactionConnection.getStore()!==scope)throw Error("A transaction query must execute in the callback that created it.")}const instance=resolveInstance();if(typeof prop==="string"&&WRITE_ENTRY_POINTS.has(prop))markContextWrote();return guardTransactionQuery(Reflect.apply(Reflect.get(instance,prop),instance,args))}}export const db=Object.create(dbFallback);Object.defineProperties(db,{deleteFrom:{value:deleteFromDatabase},fn:{value:aggregateFunctions},insertInto:{value:insertIntoDatabase},read:{get:()=>readDb},select:{value:selectDatabase},selectFrom:{value:selectFromDatabase},selectFromSub:{value:selectFromSubDatabase},table:{value:tableDatabase},transactional:{value:transactionalDatabase},unsafe:{value:unsafeDatabase},updateTable:{value:updateTableDatabase}});const readDbFallback=new Proxy({},{get(_target,prop){const value=getExplicitReadDb()[prop];if(typeof value==="function")return forwardDatabaseMethod(prop,getExplicitReadDb);return value}});export const readDb=Object.create(readDbFallback);Object.defineProperties(readDb,{fn:{value:aggregateFunctions},select:{value:selectExplicitReadDatabase},selectFrom:{value:selectFromExplicitReadDatabase},selectFromSub:{value:selectFromSubExplicitReadDatabase},table:{value:tableExplicitReadDatabase},transactional:{value:transactionalDatabase},unsafe:{value:unsafeExplicitReadDatabase}});export{setConfig};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.51",
5
+ "version": "0.74.53",
6
6
  "description": "The Stacks database integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -66,26 +66,26 @@
66
66
  "prepublishOnly": "bun run build"
67
67
  },
68
68
  "dependencies": {
69
- "@stacksjs/config": "0.74.51",
70
- "@stacksjs/env": "0.74.51",
71
- "@stacksjs/error-handling": "0.74.51",
72
- "@stacksjs/faker": "^0.74.51",
73
- "@stacksjs/features": "0.74.51",
74
- "@stacksjs/logging": "0.74.51",
75
- "@stacksjs/model-meta": "0.74.51",
76
- "@stacksjs/path": "0.74.51",
77
- "@stacksjs/query-builder": "^0.74.51",
78
- "@stacksjs/security": "0.74.51",
79
- "@stacksjs/storage": "0.74.51",
80
- "@stacksjs/strings": "0.74.51",
69
+ "@stacksjs/config": "0.74.53",
70
+ "@stacksjs/env": "0.74.53",
71
+ "@stacksjs/error-handling": "0.74.53",
72
+ "@stacksjs/faker": "^0.74.53",
73
+ "@stacksjs/features": "0.74.53",
74
+ "@stacksjs/logging": "0.74.53",
75
+ "@stacksjs/model-meta": "0.74.53",
76
+ "@stacksjs/path": "0.74.53",
77
+ "@stacksjs/query-builder": "^0.74.53",
78
+ "@stacksjs/security": "0.74.53",
79
+ "@stacksjs/storage": "0.74.53",
80
+ "@stacksjs/strings": "0.74.53",
81
81
  "@stacksjs/ts-validation": "^0.5.6",
82
82
  "bun-query-builder": "^0.2.70",
83
83
  "dynamodb-tooling": "^0.3.2"
84
84
  },
85
85
  "devDependencies": {
86
- "@stacksjs/cli": "0.74.51",
87
- "@stacksjs/router": "0.74.51",
88
- "@stacksjs/utils": "0.74.51",
86
+ "@stacksjs/cli": "0.74.53",
87
+ "@stacksjs/router": "0.74.53",
88
+ "@stacksjs/utils": "0.74.53",
89
89
  "better-dx": "^0.2.24"
90
90
  }
91
91
  }