@happyvertical/sql 0.74.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/AGENT.md +33 -0
  2. package/LICENSE +7 -0
  3. package/README.md +287 -0
  4. package/dist/backup/index.d.ts +119 -0
  5. package/dist/backup/index.d.ts.map +1 -0
  6. package/dist/backup/index.js +212 -0
  7. package/dist/backup/index.js.map +1 -0
  8. package/dist/backup/manifest.d.ts +51 -0
  9. package/dist/backup/manifest.d.ts.map +1 -0
  10. package/dist/chunks/duckdb-Djx8WKZE.js +924 -0
  11. package/dist/chunks/duckdb-Djx8WKZE.js.map +1 -0
  12. package/dist/chunks/index-DL-5nB7l.js +3468 -0
  13. package/dist/chunks/index-DL-5nB7l.js.map +1 -0
  14. package/dist/chunks/postgres-cli-Cr6bBZ3q.js +209 -0
  15. package/dist/chunks/postgres-cli-Cr6bBZ3q.js.map +1 -0
  16. package/dist/chunks/sqlite-DkCGjXmu.js +937 -0
  17. package/dist/chunks/sqlite-DkCGjXmu.js.map +1 -0
  18. package/dist/chunks/sqlite-native-B6q-0nLs.js +1129 -0
  19. package/dist/chunks/sqlite-native-B6q-0nLs.js.map +1 -0
  20. package/dist/cli/claude-context.d.ts +3 -0
  21. package/dist/cli/claude-context.d.ts.map +1 -0
  22. package/dist/cli/claude-context.js +21 -0
  23. package/dist/cli/claude-context.js.map +1 -0
  24. package/dist/doctor.d.ts +69 -0
  25. package/dist/doctor.d.ts.map +1 -0
  26. package/dist/duckdb.d.ts +9 -0
  27. package/dist/duckdb.d.ts.map +1 -0
  28. package/dist/index.d.ts +109 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +34 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/json.d.ts +30 -0
  33. package/dist/json.d.ts.map +1 -0
  34. package/dist/postgres-cli.d.ts +135 -0
  35. package/dist/postgres-cli.d.ts.map +1 -0
  36. package/dist/postgres.d.ts +77 -0
  37. package/dist/postgres.d.ts.map +1 -0
  38. package/dist/schema-manager.d.ts +86 -0
  39. package/dist/schema-manager.d.ts.map +1 -0
  40. package/dist/shared/alter-utils.d.ts +83 -0
  41. package/dist/shared/alter-utils.d.ts.map +1 -0
  42. package/dist/shared/duckdb-schema-utils.d.ts +86 -0
  43. package/dist/shared/duckdb-schema-utils.d.ts.map +1 -0
  44. package/dist/shared/types.d.ts +1016 -0
  45. package/dist/shared/types.d.ts.map +1 -0
  46. package/dist/shared/utils.d.ts +104 -0
  47. package/dist/shared/utils.d.ts.map +1 -0
  48. package/dist/sqlite-native.d.ts +4 -0
  49. package/dist/sqlite-native.d.ts.map +1 -0
  50. package/dist/sqlite.d.ts +54 -0
  51. package/dist/sqlite.d.ts.map +1 -0
  52. package/metadata.json +38 -0
  53. package/package.json +84 -0
@@ -0,0 +1,3468 @@
1
+ import { DatabaseError, loadEnvConfig } from "@happyvertical/utils";
2
+ import { r as redactDatabaseUrl } from "./postgres-cli-Cr6bBZ3q.js";
3
+ import { mkdir, readdir } from "node:fs/promises";
4
+ import { join, extname, basename } from "node:path";
5
+ import { Pool } from "pg";
6
+ function formatDbError(error) {
7
+ const dbError = error;
8
+ const parts = [];
9
+ if (dbError.message) parts.push(dbError.message);
10
+ if (dbError.code) parts.push(`code=${dbError.code}`);
11
+ if (dbError.detail) parts.push(`detail=${dbError.detail}`);
12
+ if (dbError.hint) parts.push(`hint=${dbError.hint}`);
13
+ if (dbError.severity) parts.push(`severity=${dbError.severity}`);
14
+ if (dbError.errno !== void 0) parts.push(`errno=${dbError.errno}`);
15
+ return parts.length > 0 ? parts.join(", ") : String(error);
16
+ }
17
+ const VALID_OPERATORS = {
18
+ "=": "=",
19
+ ">": ">",
20
+ ">=": ">=",
21
+ "<": "<",
22
+ "<=": "<=",
23
+ "!=": "!=",
24
+ like: "LIKE",
25
+ in: "IN"
26
+ };
27
+ const buildCondition = (fullKey, value, currIndex, adapterType) => {
28
+ const [field, operator = "="] = fullKey.split(" ");
29
+ const sqlOperator = VALID_OPERATORS[operator] || "=";
30
+ const values = [];
31
+ let sql;
32
+ if (value === null) {
33
+ sql = `${field} IS ${sqlOperator === "=" ? "NULL" : "NOT NULL"}`;
34
+ } else if (sqlOperator === "IN" && Array.isArray(value)) {
35
+ const placeholders = value.map(() => `$${currIndex.value++}`).join(", ");
36
+ sql = `${field} IN (${placeholders})`;
37
+ values.push(...value);
38
+ } else if (value instanceof Date) {
39
+ const needsCast = adapterType === "duckdb" || adapterType === "json";
40
+ if (needsCast) {
41
+ sql = `${field} ${sqlOperator} CAST($${currIndex.value++} AS TIMESTAMP)`;
42
+ } else {
43
+ sql = `${field} ${sqlOperator} $${currIndex.value++}`;
44
+ }
45
+ values.push(value.toISOString());
46
+ } else {
47
+ sql = `${field} ${sqlOperator} $${currIndex.value++}`;
48
+ values.push(value);
49
+ }
50
+ return { sql, values };
51
+ };
52
+ const buildWhere = (where, startIndex = 1, adapterType) => {
53
+ let sql = "";
54
+ const values = [];
55
+ const currIndex = { value: startIndex };
56
+ if (Array.isArray(where)) {
57
+ if (where.length === 0) {
58
+ return { sql: "", values: [] };
59
+ }
60
+ const orGroups = [];
61
+ for (const andGroup of where) {
62
+ if (!Array.isArray(andGroup) || andGroup.length === 0) {
63
+ continue;
64
+ }
65
+ const andConditions = [];
66
+ for (const conditionObj of andGroup) {
67
+ for (const [fullKey, value] of Object.entries(conditionObj)) {
68
+ const result = buildCondition(fullKey, value, currIndex, adapterType);
69
+ andConditions.push(result.sql);
70
+ values.push(...result.values);
71
+ }
72
+ }
73
+ if (andConditions.length > 0) {
74
+ orGroups.push(`(${andConditions.join(" AND ")})`);
75
+ }
76
+ }
77
+ if (orGroups.length > 0) {
78
+ sql = `WHERE ${orGroups.join(" OR ")}`;
79
+ }
80
+ return { sql, values };
81
+ }
82
+ if (where && Object.keys(where).length > 0) {
83
+ sql = "WHERE ";
84
+ for (const [fullKey, value] of Object.entries(where)) {
85
+ const result = buildCondition(fullKey, value, currIndex, adapterType);
86
+ if (sql !== "WHERE ") {
87
+ sql += " AND ";
88
+ }
89
+ sql += result.sql;
90
+ values.push(...result.values);
91
+ }
92
+ }
93
+ return { sql, values };
94
+ };
95
+ function parseIndexStatement(indexSQL) {
96
+ const trimmed = indexSQL.trim();
97
+ const match = trimmed.match(
98
+ /CREATE\s+(UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s+ON\s+(?:"?\w+"?)\s*\(([^)]+)\)/i
99
+ );
100
+ if (!match) {
101
+ return null;
102
+ }
103
+ const [, uniqueKeyword, name, columnsStr] = match;
104
+ const columns = columnsStr.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
105
+ return {
106
+ name,
107
+ columns,
108
+ unique: Boolean(uniqueKeyword),
109
+ sql: trimmed
110
+ };
111
+ }
112
+ function convertUniqueIndexesToInlineConstraints(ddl, indexes) {
113
+ if (!indexes || indexes.length === 0) {
114
+ return { ddl, indexes: [] };
115
+ }
116
+ const parsedIndexes = indexes.map(parseIndexStatement).filter((idx) => idx !== null);
117
+ const uniqueIndexes = parsedIndexes.filter((idx) => idx.unique);
118
+ const regularIndexes = parsedIndexes.filter((idx) => !idx.unique);
119
+ if (uniqueIndexes.length === 0) {
120
+ return { ddl, indexes };
121
+ }
122
+ const lines = ddl.split("\n");
123
+ let closingParenIndex = -1;
124
+ for (let i = lines.length - 1; i >= 0; i--) {
125
+ const trimmed = lines[i].trim();
126
+ if (trimmed === ")" || trimmed.startsWith(");")) {
127
+ closingParenIndex = i;
128
+ break;
129
+ }
130
+ }
131
+ if (closingParenIndex === -1) {
132
+ console.warn(
133
+ "[duckdb-schema-utils] Could not find closing parenthesis in CREATE TABLE statement"
134
+ );
135
+ return { ddl, indexes };
136
+ }
137
+ const uniqueConstraints = uniqueIndexes.map((idx) => {
138
+ const columns = idx.columns.map((col) => `"${col}"`).join(", ");
139
+ return ` UNIQUE(${columns})`;
140
+ });
141
+ const modifiedLines = [...lines];
142
+ modifiedLines[closingParenIndex].trim();
143
+ if (closingParenIndex > 0) {
144
+ const prevLine = modifiedLines[closingParenIndex - 1].trim();
145
+ if (!prevLine.endsWith(",")) {
146
+ modifiedLines[closingParenIndex - 1] = modifiedLines[closingParenIndex - 1].replace(/\s*$/, ",");
147
+ }
148
+ }
149
+ const constraintLines = uniqueConstraints.map((constraint, idx) => {
150
+ return idx < uniqueConstraints.length - 1 ? `${constraint},` : constraint;
151
+ });
152
+ modifiedLines.splice(closingParenIndex, 0, ...constraintLines);
153
+ const transformedDDL = modifiedLines.join("\n");
154
+ const remainingIndexes = regularIndexes.map((idx) => idx.sql);
155
+ return {
156
+ ddl: transformedDDL,
157
+ indexes: remainingIndexes
158
+ };
159
+ }
160
+ async function runDoctor(options) {
161
+ assertPostgresAdapter(options.db);
162
+ const tables = await listPublicTables(options.db);
163
+ const issues = [];
164
+ for (const check of options.checks) {
165
+ const checkIssues = await check({ db: options.db, tables });
166
+ issues.push(...checkIssues);
167
+ }
168
+ return {
169
+ failures: issues.filter((issue) => issue.level === "fail"),
170
+ warnings: issues.filter((issue) => issue.level === "warn"),
171
+ issues
172
+ };
173
+ }
174
+ function assertPostgresAdapter(db) {
175
+ const url = String(db.url ?? "");
176
+ if (!url.startsWith("postgres://") && !url.startsWith("postgresql://")) {
177
+ throw new Error(
178
+ `runDoctor requires a Postgres database connection. Got URL: ${url ? redactDatabaseUrl(url) : "(empty)"}. The bundled check builders use Postgres-specific syntax (pg_tables, ::int/::text casts).`
179
+ );
180
+ }
181
+ }
182
+ function checkExpectedTables(expected) {
183
+ return async ({ tables }) => {
184
+ const issues = [];
185
+ for (const table of expected) {
186
+ if (!tables.has(table)) {
187
+ issues.push({
188
+ level: "warn",
189
+ message: `Missing expected table: ${table}`
190
+ });
191
+ }
192
+ }
193
+ return issues;
194
+ };
195
+ }
196
+ function checkUniqueColumn(spec) {
197
+ return async ({ db, tables }) => {
198
+ if (!tables.has(spec.table)) return [];
199
+ const result = await db.query(`
200
+ SELECT ${quoteIdentifier(spec.column)} AS value, count(*)::text AS count
201
+ FROM ${quoteIdentifier(spec.table)}
202
+ WHERE NULLIF(trim(${quoteIdentifier(spec.column)}::text), '') IS NOT NULL
203
+ GROUP BY ${quoteIdentifier(spec.column)}
204
+ HAVING count(*) > 1
205
+ ORDER BY count DESC, ${quoteIdentifier(spec.column)}
206
+ `);
207
+ return result.rows.map((row) => ({
208
+ level: "fail",
209
+ message: `Duplicate ${spec.label} "${String(row.value)}" in ${spec.table} (${countValueFromRow(
210
+ row.count
211
+ )} rows).`
212
+ }));
213
+ };
214
+ }
215
+ function checkRelationship(spec) {
216
+ return async ({ db, tables }) => {
217
+ if (!tables.has(spec.childTable) || !tables.has(spec.parentTable))
218
+ return [];
219
+ const parentColumn = spec.parentColumn ?? "id";
220
+ const result = await db.query(`
221
+ SELECT count(*)::text AS count
222
+ FROM ${quoteIdentifier(spec.childTable)} child
223
+ LEFT JOIN ${quoteIdentifier(spec.parentTable)} parent
224
+ ON parent.${quoteIdentifier(parentColumn)} = child.${quoteIdentifier(spec.childColumn)}
225
+ WHERE NULLIF(trim(child.${quoteIdentifier(spec.childColumn)}::text), '') IS NOT NULL
226
+ AND parent.${quoteIdentifier(parentColumn)} IS NULL
227
+ `);
228
+ const orphanCount = countValueFromRow(result.rows[0]?.count);
229
+ if (orphanCount === "0") return [];
230
+ return [
231
+ {
232
+ level: "fail",
233
+ message: `${spec.label}: ${orphanCount} orphaned row${orphanCount === "1" ? "" : "s"}.`
234
+ }
235
+ ];
236
+ };
237
+ }
238
+ async function listPublicTables(db) {
239
+ const result = await db.query(
240
+ "SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename"
241
+ );
242
+ return new Set(result.rows.map((row) => String(row.tablename ?? "")));
243
+ }
244
+ function quoteIdentifier(value) {
245
+ return `"${value.replace(/"/gu, '""')}"`;
246
+ }
247
+ function countValueFromRow(value) {
248
+ if (typeof value === "string") return value;
249
+ if (typeof value === "number" || typeof value === "bigint") {
250
+ return String(value);
251
+ }
252
+ return "0";
253
+ }
254
+ class DatabaseSchemaManager {
255
+ static initializationLock = /* @__PURE__ */ new Map();
256
+ initializedSchemas = /* @__PURE__ */ new Set();
257
+ schemaVersions = /* @__PURE__ */ new Map();
258
+ /**
259
+ * Initializes database schemas from a JSON manifest with dependency resolution
260
+ *
261
+ * Schemas are topologically sorted by their declared dependencies and
262
+ * initialized in order. Supports legacy raw-SQL schemas via the `schema` option,
263
+ * JSON manifests via the `manifest` option, and schema overrides for extending
264
+ * base schemas.
265
+ *
266
+ * @param db - Database interface to execute DDL against
267
+ * @param options - Schema initialization options (manifest, overrides, force, debug)
268
+ * @returns Result with lists of initialized/skipped schemas, errors, and execution time
269
+ */
270
+ async initializeSchemas(db, options) {
271
+ const startTime = Date.now();
272
+ const result = {
273
+ initialized: [],
274
+ skipped: [],
275
+ errors: [],
276
+ executionTime: 0
277
+ };
278
+ try {
279
+ let schemas = {};
280
+ if (options.schema) {
281
+ console.log(
282
+ "[schema] Legacy SQL schema provided, converting to manifest format"
283
+ );
284
+ if (db.syncSchema) {
285
+ await db.syncSchema(options.schema);
286
+ result.initialized.push("legacy-sql");
287
+ }
288
+ result.executionTime = Date.now() - startTime;
289
+ return result;
290
+ }
291
+ if (options.manifest) {
292
+ schemas = { ...options.manifest.schemas };
293
+ }
294
+ if (options.overrides) {
295
+ schemas = { ...schemas, ...options.overrides };
296
+ }
297
+ if (Object.keys(schemas).length === 0) {
298
+ console.warn("[schema] No schemas provided for initialization");
299
+ result.executionTime = Date.now() - startTime;
300
+ return result;
301
+ }
302
+ const dependencyGraph = this.buildDependencyGraph(schemas);
303
+ const initializationOrder = this.resolveDependencies(dependencyGraph);
304
+ if (options.debug) {
305
+ console.log("[schema] Initialization order:", initializationOrder);
306
+ }
307
+ for (const schemaName of initializationOrder) {
308
+ const schema = schemas[schemaName];
309
+ if (!schema) continue;
310
+ try {
311
+ await this.initializeSchema(db, schemaName, schema, {
312
+ force: options.force,
313
+ debug: options.debug
314
+ });
315
+ result.initialized.push(schemaName);
316
+ } catch (error) {
317
+ const errorMessage = error instanceof Error ? error.message : String(error);
318
+ result.errors.push({ schema: schemaName, error: errorMessage });
319
+ if (options.debug) {
320
+ console.error(
321
+ `[schema] Failed to initialize ${schemaName}:`,
322
+ error
323
+ );
324
+ }
325
+ }
326
+ }
327
+ result.executionTime = Date.now() - startTime;
328
+ if (options.debug) {
329
+ console.log("[schema] Initialization complete:", result);
330
+ }
331
+ return result;
332
+ } catch (error) {
333
+ result.executionTime = Date.now() - startTime;
334
+ const errorMessage = error instanceof Error ? error.message : String(error);
335
+ result.errors.push({
336
+ schema: "dependency-resolution",
337
+ error: errorMessage
338
+ });
339
+ return result;
340
+ }
341
+ }
342
+ /**
343
+ * Initialize a single schema
344
+ */
345
+ async initializeSchema(db, schemaName, schema, options) {
346
+ const { tableName, version } = schema;
347
+ const lockKey = `${schemaName}:${tableName}`;
348
+ if (DatabaseSchemaManager.initializationLock.has(lockKey)) {
349
+ await DatabaseSchemaManager.initializationLock.get(lockKey);
350
+ return;
351
+ }
352
+ if (!options.force && this.isSchemaUpToDate(schemaName, version)) {
353
+ if (options.debug) {
354
+ console.log(`[schema] Skipping ${schemaName} - already up to date`);
355
+ }
356
+ return;
357
+ }
358
+ const initPromise = this.performSchemaInitialization(
359
+ db,
360
+ schemaName,
361
+ schema,
362
+ options
363
+ );
364
+ DatabaseSchemaManager.initializationLock.set(lockKey, initPromise);
365
+ try {
366
+ await initPromise;
367
+ this.markSchemaInitialized(schemaName, version);
368
+ } finally {
369
+ DatabaseSchemaManager.initializationLock.delete(lockKey);
370
+ }
371
+ }
372
+ /**
373
+ * Perform the actual schema initialization
374
+ */
375
+ async performSchemaInitialization(db, schemaName, schema, options) {
376
+ const { tableName, columns, indexes, triggers } = schema;
377
+ if (options.debug) {
378
+ console.log(`[schema] Initializing ${schemaName} (${tableName})`);
379
+ }
380
+ const tableExists2 = await db.tableExists(tableName);
381
+ if (!tableExists2) {
382
+ await this.createTable(db, schema);
383
+ for (const index2 of indexes) {
384
+ await this.createIndex(db, tableName, index2);
385
+ }
386
+ for (const trigger of triggers) {
387
+ await this.createTrigger(db, trigger);
388
+ }
389
+ } else if (options.force) {
390
+ await db.query(`DROP TABLE IF EXISTS ${tableName}`);
391
+ await this.createTable(db, schema);
392
+ for (const index2 of indexes) {
393
+ await this.createIndex(db, tableName, index2);
394
+ }
395
+ for (const trigger of triggers) {
396
+ await this.createTrigger(db, trigger);
397
+ }
398
+ } else {
399
+ await this.updateSchemaIfNeeded(db, schema);
400
+ }
401
+ }
402
+ /**
403
+ * Create table from schema definition
404
+ */
405
+ async createTable(db, schema) {
406
+ const { tableName, columns, foreignKeys } = schema;
407
+ const columnDefinitions = [];
408
+ for (const [columnName, columnDef] of Object.entries(columns)) {
409
+ let def = `${columnName} ${columnDef.type}`;
410
+ if (columnDef.primaryKey) def += " PRIMARY KEY";
411
+ if (columnDef.unique && !columnDef.primaryKey) def += " UNIQUE";
412
+ if (columnDef.notNull) def += " NOT NULL";
413
+ if (columnDef.defaultValue !== void 0) {
414
+ def += ` DEFAULT ${columnDef.defaultValue}`;
415
+ }
416
+ if (columnDef.check) def += ` CHECK (${columnDef.check})`;
417
+ columnDefinitions.push(def);
418
+ }
419
+ for (const fk of foreignKeys) {
420
+ let fkDef = `FOREIGN KEY (${fk.column}) REFERENCES ${fk.referencesTable}(${fk.referencesColumn})`;
421
+ if (fk.onDelete) fkDef += ` ON DELETE ${fk.onDelete}`;
422
+ if (fk.onUpdate) fkDef += ` ON UPDATE ${fk.onUpdate}`;
423
+ columnDefinitions.push(fkDef);
424
+ }
425
+ const createTableSQL = `CREATE TABLE ${tableName} (
426
+ ${columnDefinitions.join(",\n ")}
427
+ )`;
428
+ await db.query(createTableSQL);
429
+ }
430
+ /**
431
+ * Create index from definition
432
+ */
433
+ async createIndex(db, tableName, index2) {
434
+ const uniqueClause = index2.unique ? "UNIQUE " : "";
435
+ const whereClause = index2.where ? ` WHERE ${index2.where}` : "";
436
+ const createIndexSQL = `CREATE ${uniqueClause}INDEX ${index2.name} ON ${tableName} (${index2.columns.join(", ")})${whereClause}`;
437
+ await db.query(createIndexSQL);
438
+ }
439
+ /**
440
+ * Create trigger from definition
441
+ */
442
+ async createTrigger(db, trigger) {
443
+ const conditionClause = trigger.condition ? ` WHEN ${trigger.condition}` : "";
444
+ const createTriggerSQL = `CREATE TRIGGER ${trigger.name} ${trigger.when} ${trigger.event} ON ${trigger.table}${conditionClause} BEGIN ${trigger.body} END`;
445
+ await db.query(createTriggerSQL);
446
+ }
447
+ /**
448
+ * Update schema if changes are detected
449
+ */
450
+ async updateSchemaIfNeeded(_db, schema) {
451
+ console.log(
452
+ `[schema] Schema update logic not yet implemented for ${schema.tableName}`
453
+ );
454
+ }
455
+ /**
456
+ * Build dependency graph from schemas
457
+ */
458
+ buildDependencyGraph(schemas) {
459
+ const graph = /* @__PURE__ */ new Map();
460
+ for (const [schemaName, schema] of Object.entries(schemas)) {
461
+ const dependencies = schema.dependencies.filter(
462
+ (dep) => Object.values(schemas).some((s) => s.tableName === dep)
463
+ );
464
+ graph.set(
465
+ schemaName,
466
+ dependencies.map(
467
+ (dep) => Object.entries(schemas).find(
468
+ ([_, s]) => s.tableName === dep
469
+ )?.[0] || dep
470
+ )
471
+ );
472
+ }
473
+ return graph;
474
+ }
475
+ /**
476
+ * Resolve dependencies using topological sort
477
+ */
478
+ resolveDependencies(graph) {
479
+ const resolved = [];
480
+ const visited = /* @__PURE__ */ new Set();
481
+ const visiting = /* @__PURE__ */ new Set();
482
+ const visit = (node) => {
483
+ if (visiting.has(node)) {
484
+ throw new Error(`Circular dependency detected involving ${node}`);
485
+ }
486
+ if (visited.has(node)) return;
487
+ visiting.add(node);
488
+ const dependencies = graph.get(node) || [];
489
+ for (const dep of dependencies) {
490
+ if (graph.has(dep)) {
491
+ visit(dep);
492
+ }
493
+ }
494
+ visiting.delete(node);
495
+ visited.add(node);
496
+ resolved.push(node);
497
+ };
498
+ for (const node of graph.keys()) {
499
+ if (!visited.has(node)) {
500
+ visit(node);
501
+ }
502
+ }
503
+ return resolved;
504
+ }
505
+ /**
506
+ * Check if schema is up to date
507
+ */
508
+ isSchemaUpToDate(schemaName, version) {
509
+ return this.initializedSchemas.has(schemaName) && this.schemaVersions.get(schemaName) === version;
510
+ }
511
+ /**
512
+ * Mark schema as initialized
513
+ */
514
+ markSchemaInitialized(schemaName, version) {
515
+ this.initializedSchemas.add(schemaName);
516
+ this.schemaVersions.set(schemaName, version);
517
+ }
518
+ /**
519
+ * Reset initialization state (for testing)
520
+ */
521
+ reset() {
522
+ this.initializedSchemas.clear();
523
+ this.schemaVersions.clear();
524
+ DatabaseSchemaManager.initializationLock.clear();
525
+ }
526
+ }
527
+ function resolveSchemas(schemas) {
528
+ if (!schemas) return void 0;
529
+ if (typeof schemas === "function") return schemas();
530
+ return schemas;
531
+ }
532
+ globalThis.__haveSqlMemoryConnectionCache ??= /* @__PURE__ */ new Map();
533
+ const memoryConnectionCache = globalThis.__haveSqlMemoryConnectionCache;
534
+ globalThis.__haveSqlPendingConnections ??= /* @__PURE__ */ new Map();
535
+ const pendingConnections$1 = globalThis.__haveSqlPendingConnections;
536
+ function clearConnectionCache() {
537
+ memoryConnectionCache.clear();
538
+ pendingConnections$1.clear();
539
+ }
540
+ async function createJSONConnection(options) {
541
+ const { url, autoRegister = true, eagerLoadTables = true } = options;
542
+ const schemas = resolveSchemas(options.schemas) ?? {};
543
+ if (!url) {
544
+ throw new DatabaseError("url is required for JSON adapter", {
545
+ options
546
+ });
547
+ }
548
+ const pendingJSONFiles = /* @__PURE__ */ new Map();
549
+ const inferredSchemaTables = /* @__PURE__ */ new Set();
550
+ try {
551
+ const duckdbModule = "@duckdb/node-api";
552
+ const { DuckDBInstance } = await import(
553
+ /* @vite-ignore */
554
+ duckdbModule
555
+ );
556
+ const instance = await DuckDBInstance.create(":memory:");
557
+ const connection = await instance.connect();
558
+ try {
559
+ await mkdir(url, { recursive: true });
560
+ } catch (_error) {
561
+ }
562
+ if (autoRegister) {
563
+ await loadJSONTables(
564
+ connection,
565
+ url,
566
+ schemas,
567
+ pendingJSONFiles,
568
+ eagerLoadTables,
569
+ inferredSchemaTables
570
+ );
571
+ }
572
+ return { connection, pendingJSONFiles, inferredSchemaTables };
573
+ } catch (error) {
574
+ const errorMessage = error instanceof Error ? error.message : String(error);
575
+ throw new DatabaseError(
576
+ `Failed to create JSON database connection: ${errorMessage}`,
577
+ {
578
+ url,
579
+ originalError: errorMessage
580
+ }
581
+ );
582
+ }
583
+ }
584
+ async function loadJSONData(connection, tableName, filePath) {
585
+ try {
586
+ const { readFile } = await import("node:fs/promises");
587
+ const jsonContent = await readFile(filePath, "utf-8");
588
+ const records = JSON.parse(jsonContent);
589
+ if (Array.isArray(records) && records.length > 0) {
590
+ await insertRecordsWithCast(connection, tableName, records);
591
+ console.log(
592
+ `[json-adapter] Loaded ${records.length} records into ${tableName} from JSON`
593
+ );
594
+ }
595
+ } catch (error) {
596
+ console.warn(
597
+ `[json-adapter] Could not load data for ${tableName}: ${error instanceof Error ? error.message : String(error)}`
598
+ );
599
+ }
600
+ }
601
+ async function loadJSONTables(connection, dataDir, providedSchemas = {}, pendingJSONFiles = /* @__PURE__ */ new Map(), eagerLoadTables = true, inferredSchemaTables = /* @__PURE__ */ new Set()) {
602
+ try {
603
+ const files = await readdir(dataDir);
604
+ const jsonFiles = files.filter(
605
+ (file) => extname(file).toLowerCase() === ".json"
606
+ );
607
+ for (const file of jsonFiles) {
608
+ const filePath = join(dataDir, file);
609
+ const tableName = basename(file, ".json");
610
+ const schema = providedSchemas[tableName];
611
+ if (schema) {
612
+ console.log(
613
+ `[json-adapter] Creating ${tableName} with provided schema definition`
614
+ );
615
+ await createTableFromSchema(connection, tableName, schema);
616
+ await loadJSONData(connection, tableName, filePath);
617
+ } else {
618
+ const schemaPath = join(dataDir, `${tableName}.schema.sql`);
619
+ const { readFile, access } = await import("node:fs/promises");
620
+ const { constants } = await import("node:fs");
621
+ let schemaExists = false;
622
+ try {
623
+ await access(schemaPath, constants.F_OK);
624
+ schemaExists = true;
625
+ } catch {
626
+ schemaExists = false;
627
+ }
628
+ if (schemaExists) {
629
+ try {
630
+ const schemaDDL = await readFile(schemaPath, "utf-8");
631
+ await connection.run(schemaDDL);
632
+ await loadJSONData(connection, tableName, filePath);
633
+ } catch (error) {
634
+ console.warn(
635
+ `[json-adapter] Could not load ${tableName} with schema file: ${error instanceof Error ? error.message : String(error)}`
636
+ );
637
+ }
638
+ } else {
639
+ if (eagerLoadTables) {
640
+ try {
641
+ await connection.run(
642
+ `CREATE TABLE IF NOT EXISTS "${tableName}" AS SELECT * FROM read_json_auto('${filePath}')`
643
+ );
644
+ inferredSchemaTables.add(tableName);
645
+ console.log(
646
+ `[json-adapter] Eagerly created ${tableName} with inferred schema (no UNIQUE constraints)`
647
+ );
648
+ } catch (error) {
649
+ console.warn(
650
+ `[json-adapter] Could not eagerly load ${tableName}: ${error instanceof Error ? error.message : String(error)}`
651
+ );
652
+ }
653
+ } else {
654
+ console.log(
655
+ `[json-adapter] Deferring ${tableName} - will load after syncSchema() creates table`
656
+ );
657
+ }
658
+ pendingJSONFiles.set(tableName, filePath);
659
+ }
660
+ }
661
+ }
662
+ } catch (error) {
663
+ if (error.code !== "ENOENT") {
664
+ console.error("[json-adapter] Error loading JSON tables:", error);
665
+ throw new DatabaseError(`Failed to load JSON tables from ${dataDir}`, {
666
+ dataDir,
667
+ originalError: error instanceof Error ? error.message : String(error),
668
+ stack: error instanceof Error ? error.stack : void 0
669
+ });
670
+ }
671
+ }
672
+ }
673
+ function convertBigInts(obj) {
674
+ if (obj === null || obj === void 0) return obj;
675
+ if (typeof obj === "bigint") return Number(obj);
676
+ if (typeof obj === "object" && !Array.isArray(obj) && "hugeint" in obj && Object.keys(obj).length === 1) {
677
+ return String(obj.hugeint);
678
+ }
679
+ if (typeof obj === "object" && !Array.isArray(obj) && "micros" in obj && Object.keys(obj).length === 1 && (typeof obj.micros === "number" || typeof obj.micros === "bigint")) {
680
+ const micros = typeof obj.micros === "bigint" ? Number(obj.micros) : obj.micros;
681
+ const milliseconds = micros / 1e3;
682
+ return new Date(milliseconds);
683
+ }
684
+ if (Array.isArray(obj)) return obj.map(convertBigInts);
685
+ if (typeof obj === "object") {
686
+ const result = {};
687
+ for (const key in obj) {
688
+ result[key] = convertBigInts(obj[key]);
689
+ }
690
+ return result;
691
+ }
692
+ return obj;
693
+ }
694
+ async function createTableFromSchema(connection, tableName, schema) {
695
+ const ddlLines = schema.ddl.split("\n");
696
+ const createTableEnd = ddlLines.findIndex((line) => {
697
+ const trimmed = line.trim();
698
+ return trimmed === ")" || trimmed.startsWith(");");
699
+ });
700
+ if (createTableEnd === -1) {
701
+ throw new DatabaseError("Invalid schema DDL - no closing parenthesis", {
702
+ tableName,
703
+ ddl: schema.ddl
704
+ });
705
+ }
706
+ let createTableSQL = ddlLines.slice(0, createTableEnd + 1).join("\n");
707
+ createTableSQL = createTableSQL.replace(
708
+ /\b(\w+)\s+(TEXT|VARCHAR)\s+DEFAULT\s+''/g,
709
+ "$1 $2 DEFAULT CAST('' AS $2)"
710
+ );
711
+ createTableSQL = createTableSQL.replace(
712
+ /\b(\w+)\s+(TEXT|VARCHAR)\s+DEFAULT\s+NULL/g,
713
+ "$1 $2 DEFAULT CAST(NULL AS $2)"
714
+ );
715
+ const transformed = convertUniqueIndexesToInlineConstraints(
716
+ createTableSQL,
717
+ schema.indexes
718
+ );
719
+ createTableSQL = transformed.ddl;
720
+ const indexesToCreate = transformed.indexes;
721
+ try {
722
+ await connection.run(createTableSQL);
723
+ let existingColumns = /* @__PURE__ */ new Set();
724
+ try {
725
+ const schemaInfo = await connection.runAndReadAll(
726
+ `DESCRIBE ${tableName}`
727
+ );
728
+ const columns = schemaInfo.getRowObjects();
729
+ existingColumns = new Set(
730
+ columns.map((col) => col.column_name.toLowerCase())
731
+ );
732
+ console.log(`[json-adapter] Created ${tableName} with schema:`, {
733
+ columns: columns.map((col) => ({
734
+ name: col.column_name,
735
+ type: col.column_type,
736
+ null: col.null
737
+ }))
738
+ });
739
+ } catch (error) {
740
+ console.warn(
741
+ `[json-adapter] Could not verify schema for ${tableName}: ${error instanceof Error ? error.message : String(error)}`
742
+ );
743
+ }
744
+ if (schema.fields && existingColumns.size > 0) {
745
+ const fieldEntries = schema.fields instanceof Map ? Array.from(schema.fields.entries()) : Object.entries(schema.fields);
746
+ for (const [fieldName, fieldDef] of fieldEntries) {
747
+ const columnName = fieldName.toLowerCase();
748
+ if (!existingColumns.has(columnName)) {
749
+ const sqlType = fieldDef?.type || "TEXT";
750
+ try {
751
+ await connection.run(
752
+ `ALTER TABLE ${tableName} ADD COLUMN "${fieldName}" ${sqlType}`
753
+ );
754
+ console.log(
755
+ `[json-adapter] Added missing column '${fieldName}' (${sqlType}) to ${tableName}`
756
+ );
757
+ } catch (alterError) {
758
+ console.warn(
759
+ `[json-adapter] Could not add column '${fieldName}' to ${tableName}: ${alterError instanceof Error ? alterError.message : String(alterError)}`
760
+ );
761
+ }
762
+ }
763
+ }
764
+ }
765
+ if (indexesToCreate && indexesToCreate.length > 0) {
766
+ for (const indexSQL of indexesToCreate) {
767
+ try {
768
+ await connection.run(indexSQL);
769
+ } catch (error) {
770
+ console.warn(
771
+ `[json-adapter] Failed to create index: ${error instanceof Error ? error.message : String(error)}`
772
+ );
773
+ }
774
+ }
775
+ }
776
+ } catch (error) {
777
+ throw new DatabaseError("Failed to create table from schema", {
778
+ tableName,
779
+ sql: createTableSQL,
780
+ originalError: error instanceof Error ? error.message : String(error)
781
+ });
782
+ }
783
+ }
784
+ async function insertRecordsWithCast(connection, tableName, records) {
785
+ if (records.length === 0) return;
786
+ const keys = Object.keys(records[0]);
787
+ const values = [];
788
+ let paramIdx = 1;
789
+ const placeholders = records.map((record) => {
790
+ const rowPlaceholders = keys.map((key) => {
791
+ const value = record[key];
792
+ if (value === null) {
793
+ return "NULL";
794
+ } else if (value === "" && typeof value === "string") {
795
+ values.push(value);
796
+ return `CAST($${paramIdx++} AS TEXT)`;
797
+ } else if (value instanceof Date) {
798
+ values.push(value.toISOString());
799
+ return `CAST($${paramIdx++} AS TIMESTAMP)`;
800
+ } else if (Array.isArray(value)) {
801
+ values.push(JSON.stringify(value));
802
+ return `CAST($${paramIdx++} AS JSON)`;
803
+ } else if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
804
+ values.push(JSON.stringify(value));
805
+ return `CAST($${paramIdx++} AS JSON)`;
806
+ } else {
807
+ values.push(value);
808
+ return `$${paramIdx++}`;
809
+ }
810
+ });
811
+ return `(${rowPlaceholders.join(", ")})`;
812
+ }).join(", ");
813
+ const sql = `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES ${placeholders}`;
814
+ await connection.run(sql, values);
815
+ }
816
+ function validateTableName$1(table) {
817
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
818
+ throw new DatabaseError(`Invalid table name: ${table}`, { table });
819
+ }
820
+ }
821
+ async function getDatabase$2(options) {
822
+ const cacheKey = options.dbid || `json:${options.url}:${options.writeStrategy || "immediate"}:${options.autoRegister !== false}`;
823
+ if (options.clearCache) {
824
+ memoryConnectionCache.delete(cacheKey);
825
+ pendingConnections$1.delete(cacheKey);
826
+ }
827
+ const cached = memoryConnectionCache.get(cacheKey);
828
+ if (cached) {
829
+ return cached;
830
+ }
831
+ const pending = pendingConnections$1.get(cacheKey);
832
+ if (pending) {
833
+ return pending;
834
+ }
835
+ const connectionPromise = (async () => {
836
+ const { connection, pendingJSONFiles, inferredSchemaTables } = await createJSONConnection(options);
837
+ const writeStrategy = options.writeStrategy || "immediate";
838
+ const { url } = options;
839
+ const insert = async (table2, data) => {
840
+ validateTableName$1(table2);
841
+ if (writeStrategy === "none") {
842
+ throw new DatabaseError(
843
+ "Cannot insert: write strategy is set to none (read-only mode)",
844
+ { table: table2, writeStrategy }
845
+ );
846
+ }
847
+ const records = Array.isArray(data) ? data : [data];
848
+ if (records.length === 0) {
849
+ return { operation: "insert", affected: 0 };
850
+ }
851
+ const keys = Object.keys(records[0]);
852
+ const values = [];
853
+ let paramIdx = 1;
854
+ const placeholders = records.map((record) => {
855
+ const rowPlaceholders = keys.map((key) => {
856
+ const value = record[key];
857
+ if (value === null) {
858
+ return "NULL";
859
+ } else if (value === "" && typeof value === "string") {
860
+ values.push(value);
861
+ return `CAST($${paramIdx++} AS TEXT)`;
862
+ } else if (value instanceof Date) {
863
+ values.push(value.toISOString());
864
+ return `CAST($${paramIdx++} AS TIMESTAMP)`;
865
+ } else if (Array.isArray(value)) {
866
+ values.push(JSON.stringify(value));
867
+ return `CAST($${paramIdx++} AS JSON)`;
868
+ } else if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
869
+ values.push(JSON.stringify(value));
870
+ return `CAST($${paramIdx++} AS JSON)`;
871
+ } else {
872
+ values.push(value);
873
+ return `$${paramIdx++}`;
874
+ }
875
+ });
876
+ return `(${rowPlaceholders.join(", ")})`;
877
+ }).join(", ");
878
+ const sql = `INSERT INTO "${table2}" (${keys.join(", ")}) VALUES ${placeholders}`;
879
+ try {
880
+ await connection.run(sql, values);
881
+ const affected = records.length;
882
+ if (writeStrategy === "immediate") {
883
+ await exportTableToJSON(connection, table2, url);
884
+ }
885
+ return { operation: "insert", affected };
886
+ } catch (e) {
887
+ throw new DatabaseError("Failed to insert records into table", {
888
+ table: table2,
889
+ sql,
890
+ values,
891
+ originalError: formatDbError(e)
892
+ });
893
+ }
894
+ };
895
+ const get = async (table2, where) => {
896
+ validateTableName$1(table2);
897
+ const { sql: whereClause, values } = buildWhere(where, 1, "json");
898
+ const sql = `SELECT * FROM "${table2}" ${whereClause} LIMIT 1`;
899
+ try {
900
+ const reader = await connection.runAndReadAll(sql, values);
901
+ const rows = reader.getRowObjects();
902
+ return rows.length > 0 ? convertBigInts(rows[0]) : null;
903
+ } catch (e) {
904
+ throw new DatabaseError("Failed to retrieve record from table", {
905
+ table: table2,
906
+ sql,
907
+ values,
908
+ originalError: formatDbError(e)
909
+ });
910
+ }
911
+ };
912
+ const list = async (table2, where) => {
913
+ validateTableName$1(table2);
914
+ const { sql: whereClause, values } = buildWhere(where, 1, "json");
915
+ const sql = `SELECT * FROM "${table2}" ${whereClause}`;
916
+ try {
917
+ const reader = await connection.runAndReadAll(sql, values);
918
+ return convertBigInts(reader.getRowObjects());
919
+ } catch (e) {
920
+ throw new DatabaseError("Failed to list records from table", {
921
+ table: table2,
922
+ sql,
923
+ values,
924
+ originalError: formatDbError(e)
925
+ });
926
+ }
927
+ };
928
+ const update = async (table2, where, data) => {
929
+ validateTableName$1(table2);
930
+ if (writeStrategy === "none") {
931
+ throw new DatabaseError(
932
+ "Cannot update: write strategy is set to none (read-only mode)",
933
+ { table: table2, writeStrategy }
934
+ );
935
+ }
936
+ const keys = Object.keys(data);
937
+ const setClause = keys.map((key, idx) => `${key} = $${idx + 1}`).join(", ");
938
+ const { sql: whereClause, values: whereValues } = buildWhere(
939
+ where,
940
+ keys.length + 1,
941
+ "json"
942
+ );
943
+ const dataValues = Object.values(data).map(
944
+ (value) => value instanceof Date ? value.toISOString() : value
945
+ );
946
+ const sql = `UPDATE "${table2}" SET ${setClause} ${whereClause}`;
947
+ const values = [...dataValues, ...whereValues];
948
+ try {
949
+ await connection.run(sql, values);
950
+ if (writeStrategy === "immediate") {
951
+ await exportTableToJSON(connection, table2, url);
952
+ }
953
+ return { operation: "update", affected: 1 };
954
+ } catch (e) {
955
+ throw new DatabaseError("Failed to update records in table", {
956
+ table: table2,
957
+ sql,
958
+ values,
959
+ originalError: formatDbError(e)
960
+ });
961
+ }
962
+ };
963
+ const buildJSONValueExpression = (value, values, paramIdx) => {
964
+ if (value === null) {
965
+ return "NULL";
966
+ }
967
+ if (value === "" && typeof value === "string") {
968
+ values.push(value);
969
+ return `CAST($${paramIdx.value++} AS TEXT)`;
970
+ }
971
+ if (value instanceof Date) {
972
+ values.push(value.toISOString());
973
+ return `CAST($${paramIdx.value++} AS TIMESTAMP)`;
974
+ }
975
+ if (Array.isArray(value)) {
976
+ values.push(JSON.stringify(value));
977
+ return `CAST($${paramIdx.value++} AS JSON)`;
978
+ }
979
+ if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
980
+ values.push(JSON.stringify(value));
981
+ return `CAST($${paramIdx.value++} AS JSON)`;
982
+ }
983
+ values.push(value);
984
+ return `$${paramIdx.value++}`;
985
+ };
986
+ const executeNullAwareJSONUpsert = async (table2, conflictColumns, data) => {
987
+ const conflictWhere = Object.fromEntries(
988
+ conflictColumns.map((col) => [col, data[col]])
989
+ );
990
+ const existing = await get(table2, conflictWhere);
991
+ if (!existing) {
992
+ return insert(table2, data);
993
+ }
994
+ const keys = Object.keys(data);
995
+ const values = [];
996
+ const paramIdx = { value: 1 };
997
+ const assignments = keys.map((key) => {
998
+ const valueExpr = buildJSONValueExpression(data[key], values, paramIdx);
999
+ return `"${key}" = ${valueExpr}`;
1000
+ });
1001
+ const { sql: whereClause, values: whereValues } = buildWhere(
1002
+ conflictWhere,
1003
+ paramIdx.value,
1004
+ "json"
1005
+ );
1006
+ const sql = `UPDATE "${table2}" SET ${assignments.join(", ")} ${whereClause}`;
1007
+ try {
1008
+ await connection.run(sql, [...values, ...whereValues]);
1009
+ if (writeStrategy === "immediate") {
1010
+ await exportTableToJSON(connection, table2, url);
1011
+ }
1012
+ return { operation: "upsert", affected: 1 };
1013
+ } catch (e) {
1014
+ throw new DatabaseError("Failed to upsert record into table", {
1015
+ table: table2,
1016
+ sql,
1017
+ values: [...values, ...whereValues],
1018
+ conflictColumns,
1019
+ originalError: formatDbError(e)
1020
+ });
1021
+ }
1022
+ };
1023
+ const upsert = async (table2, conflictColumns, data, options2) => {
1024
+ validateTableName$1(table2);
1025
+ if (writeStrategy === "none") {
1026
+ throw new DatabaseError(
1027
+ "Cannot upsert: write strategy is set to none (read-only mode)",
1028
+ { table: table2, writeStrategy }
1029
+ );
1030
+ }
1031
+ const missingColumns = conflictColumns.filter((col) => !(col in data));
1032
+ if (missingColumns.length > 0) {
1033
+ throw new DatabaseError("Conflict columns missing from data", {
1034
+ table: table2,
1035
+ conflictColumns,
1036
+ missingColumns,
1037
+ availableColumns: Object.keys(data),
1038
+ hint: "All columns specified in ON CONFLICT must be present in the data being inserted. Undefined values should be replaced with null or an appropriate default."
1039
+ });
1040
+ }
1041
+ if (!options2?.nullsDistinct && conflictColumns.some((col) => data[col] === null)) {
1042
+ return executeNullAwareJSONUpsert(table2, conflictColumns, data);
1043
+ }
1044
+ const keys = Object.keys(data);
1045
+ const dataValues = Object.values(data);
1046
+ const placeholders = [];
1047
+ const values = [];
1048
+ let paramIdx = 1;
1049
+ for (const value of dataValues) {
1050
+ if (value === null) {
1051
+ placeholders.push("NULL");
1052
+ } else if (value === "" && typeof value === "string") {
1053
+ placeholders.push(`CAST($${paramIdx} AS TEXT)`);
1054
+ values.push(value);
1055
+ paramIdx++;
1056
+ } else if (value instanceof Date) {
1057
+ placeholders.push(`CAST($${paramIdx} AS TIMESTAMP)`);
1058
+ values.push(value.toISOString());
1059
+ paramIdx++;
1060
+ } else if (Array.isArray(value)) {
1061
+ placeholders.push(`CAST($${paramIdx} AS JSON)`);
1062
+ values.push(JSON.stringify(value));
1063
+ paramIdx++;
1064
+ } else if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
1065
+ placeholders.push(`CAST($${paramIdx} AS JSON)`);
1066
+ values.push(JSON.stringify(value));
1067
+ paramIdx++;
1068
+ } else {
1069
+ placeholders.push(`$${paramIdx}`);
1070
+ values.push(value);
1071
+ paramIdx++;
1072
+ }
1073
+ }
1074
+ const updateSetParts = [];
1075
+ for (let i = 0; i < keys.length; i++) {
1076
+ const key = keys[i];
1077
+ const value = dataValues[i];
1078
+ if (value === null) {
1079
+ updateSetParts.push(`${key} = NULL`);
1080
+ } else if (value === "" && typeof value === "string") {
1081
+ updateSetParts.push(`${key} = CAST($${paramIdx} AS TEXT)`);
1082
+ values.push(value);
1083
+ paramIdx++;
1084
+ } else if (value instanceof Date) {
1085
+ updateSetParts.push(`${key} = CAST($${paramIdx} AS TIMESTAMP)`);
1086
+ values.push(value.toISOString());
1087
+ paramIdx++;
1088
+ } else if (Array.isArray(value)) {
1089
+ updateSetParts.push(`${key} = CAST($${paramIdx} AS JSON)`);
1090
+ values.push(JSON.stringify(value));
1091
+ paramIdx++;
1092
+ } else if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
1093
+ updateSetParts.push(`${key} = CAST($${paramIdx} AS JSON)`);
1094
+ values.push(JSON.stringify(value));
1095
+ paramIdx++;
1096
+ } else {
1097
+ updateSetParts.push(`${key} = $${paramIdx}`);
1098
+ values.push(value);
1099
+ paramIdx++;
1100
+ }
1101
+ }
1102
+ const conflict = conflictColumns.map((col) => `"${col}"`).join(", ");
1103
+ const quotedKeys = keys.map((key) => `"${key}"`).join(", ");
1104
+ const quotedUpdateSetParts = keys.map((key, i) => {
1105
+ const part = updateSetParts[i];
1106
+ const valueExpr = part.substring(part.indexOf("=") + 1).trim();
1107
+ return `"${key}" = ${valueExpr}`;
1108
+ });
1109
+ const sql = `INSERT INTO "${table2}" (${quotedKeys}) VALUES (${placeholders.join(", ")}) ON CONFLICT(${conflict}) DO UPDATE SET ${quotedUpdateSetParts.join(", ")}`;
1110
+ try {
1111
+ await connection.run(sql, values);
1112
+ if (writeStrategy === "immediate") {
1113
+ await exportTableToJSON(connection, table2, url);
1114
+ }
1115
+ return { operation: "upsert", affected: 1 };
1116
+ } catch (e) {
1117
+ console.error("UPSERT failed:", {
1118
+ table: table2,
1119
+ sql,
1120
+ values,
1121
+ valueTypes: values.map((v) => `${typeof v} (${v})`),
1122
+ conflictColumns,
1123
+ error: formatDbError(e)
1124
+ });
1125
+ throw new DatabaseError("Failed to upsert record into table", {
1126
+ table: table2,
1127
+ sql,
1128
+ values,
1129
+ conflictColumns,
1130
+ originalError: formatDbError(e)
1131
+ });
1132
+ }
1133
+ };
1134
+ const getOrInsert = async (table2, where, data) => {
1135
+ const result = await get(table2, where);
1136
+ if (result) return result;
1137
+ await insert(table2, data);
1138
+ const inserted = await get(table2, where);
1139
+ if (!inserted) {
1140
+ throw new DatabaseError("Failed to insert and retrieve record", {
1141
+ table: table2,
1142
+ where,
1143
+ data
1144
+ });
1145
+ }
1146
+ return inserted;
1147
+ };
1148
+ const deleteRecords = async (table2, where) => {
1149
+ validateTableName$1(table2);
1150
+ if (writeStrategy === "none") {
1151
+ throw new DatabaseError(
1152
+ "Cannot delete: write strategy is set to none (read-only mode)",
1153
+ { table: table2, writeStrategy }
1154
+ );
1155
+ }
1156
+ const keys = Object.keys(where);
1157
+ if (keys.length === 0) {
1158
+ throw new DatabaseError(
1159
+ "DELETE requires at least one WHERE condition to prevent accidental deletion of all records",
1160
+ { table: table2 }
1161
+ );
1162
+ }
1163
+ const { sql: whereClause, values } = buildWhere(where, 1, "json");
1164
+ const sql = `DELETE FROM "${table2}" ${whereClause}`;
1165
+ try {
1166
+ await connection.run(sql, values);
1167
+ if (writeStrategy === "immediate") {
1168
+ await exportTableToJSON(connection, table2, url);
1169
+ }
1170
+ return { operation: "delete", affected: 1 };
1171
+ } catch (e) {
1172
+ throw new DatabaseError("Failed to delete records from table", {
1173
+ table: table2,
1174
+ sql,
1175
+ values,
1176
+ originalError: formatDbError(e)
1177
+ });
1178
+ }
1179
+ };
1180
+ const count = async (table2, where) => {
1181
+ validateTableName$1(table2);
1182
+ try {
1183
+ if (!where || Object.keys(where).length === 0) {
1184
+ const result2 = await connection.runAndReadAll(
1185
+ `SELECT COUNT(*) as count FROM "${table2}"`
1186
+ );
1187
+ const rows2 = result2.getRowObjects();
1188
+ return Number(rows2[0]?.count) || 0;
1189
+ }
1190
+ const { sql: whereClause, values } = buildWhere(where, 1, "json");
1191
+ const sql = `SELECT COUNT(*) as count FROM "${table2}" ${whereClause}`;
1192
+ const result = await connection.runAndReadAll(sql, values);
1193
+ const rows = result.getRowObjects();
1194
+ return Number(rows[0]?.count) || 0;
1195
+ } catch (e) {
1196
+ throw new DatabaseError("Failed to count records in table", {
1197
+ table: table2,
1198
+ where,
1199
+ originalError: formatDbError(e)
1200
+ });
1201
+ }
1202
+ };
1203
+ const tableExists2 = async (tableName) => {
1204
+ try {
1205
+ await connection.runAndReadAll(`SELECT * FROM ${tableName} LIMIT 1`);
1206
+ return true;
1207
+ } catch {
1208
+ return false;
1209
+ }
1210
+ };
1211
+ const exportTableToJSON = async (connection2, table2, dataDir) => {
1212
+ const filePath = join(dataDir, `${table2}.json`);
1213
+ const schemaPath = join(dataDir, `${table2}.schema.sql`);
1214
+ try {
1215
+ const schemaResult = await connection2.runAndReadAll(
1216
+ `SELECT sql FROM sqlite_master WHERE type='table' AND name='${table2}'`
1217
+ );
1218
+ const rows = schemaResult.getRowObjects();
1219
+ if (rows.length > 0 && rows[0].sql) {
1220
+ const { writeFile } = await import("node:fs/promises");
1221
+ await writeFile(schemaPath, rows[0].sql, "utf-8");
1222
+ }
1223
+ } catch (error) {
1224
+ console.warn(
1225
+ `[json-adapter] Could not export schema for ${table2}: ${error instanceof Error ? error.message : String(error)}`
1226
+ );
1227
+ }
1228
+ const columnsResult = await connection2.runAndReadAll(
1229
+ `SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${table2}' ORDER BY ordinal_position`
1230
+ );
1231
+ const columnInfo = columnsResult.getRowObjects();
1232
+ if (columnInfo.length === 0) {
1233
+ await connection2.run(
1234
+ `COPY (SELECT * FROM "${table2}") TO '${filePath}' (FORMAT JSON, ARRAY true)`
1235
+ );
1236
+ return;
1237
+ }
1238
+ const selectList = columnInfo.map(({ column_name, data_type }) => {
1239
+ const upperType = data_type.toUpperCase();
1240
+ if (upperType === "JSON" || upperType.startsWith("STRUCT")) {
1241
+ return `"${column_name}"`;
1242
+ }
1243
+ if ([
1244
+ "DOUBLE",
1245
+ "REAL",
1246
+ "FLOAT",
1247
+ "INTEGER",
1248
+ "BIGINT",
1249
+ "SMALLINT",
1250
+ "TINYINT",
1251
+ "DECIMAL",
1252
+ "NUMERIC",
1253
+ "HUGEINT",
1254
+ "UBIGINT",
1255
+ "UINTEGER",
1256
+ "USMALLINT",
1257
+ "UTINYINT"
1258
+ ].some((t) => upperType.includes(t))) {
1259
+ return `"${column_name}"`;
1260
+ }
1261
+ if (upperType === "BOOLEAN" || upperType === "BOOL") {
1262
+ return `"${column_name}"`;
1263
+ }
1264
+ return `CAST("${column_name}" AS TEXT) AS "${column_name}"`;
1265
+ }).join(", ");
1266
+ const sql = `COPY (SELECT ${selectList} FROM "${table2}") TO '${filePath}' (FORMAT JSON, ARRAY true)`;
1267
+ await connection2.run(sql);
1268
+ };
1269
+ const exportTable = async (table2) => {
1270
+ if (writeStrategy === "none") {
1271
+ throw new DatabaseError(
1272
+ "Cannot export table: write strategy is set to none (read-only mode)",
1273
+ { table: table2, writeStrategy }
1274
+ );
1275
+ }
1276
+ await exportTableToJSON(connection, table2, url);
1277
+ };
1278
+ const inferSchemaFromJSON = async (tableName, jsonPath) => {
1279
+ validateTableName$1(tableName);
1280
+ const filePath = jsonPath || join(url, `${tableName}.json`);
1281
+ try {
1282
+ const { access } = await import("node:fs/promises");
1283
+ const { constants } = await import("node:fs");
1284
+ await access(filePath, constants.F_OK);
1285
+ console.log(
1286
+ `[json-adapter] Inferring schema for ${tableName} from JSON file`
1287
+ );
1288
+ await connection.run(
1289
+ `CREATE TABLE "${tableName}" AS SELECT * FROM read_json_auto('${filePath}')`
1290
+ );
1291
+ pendingJSONFiles.delete(tableName);
1292
+ console.log(
1293
+ `[json-adapter] Created ${tableName} with inferred schema (no UNIQUE constraints)`
1294
+ );
1295
+ } catch (error) {
1296
+ throw new DatabaseError(
1297
+ `Failed to infer schema from JSON for table ${tableName}`,
1298
+ {
1299
+ tableName,
1300
+ jsonPath: filePath,
1301
+ originalError: error instanceof Error ? error.message : String(error)
1302
+ }
1303
+ );
1304
+ }
1305
+ };
1306
+ const table = (tableName) => ({
1307
+ insert: (data) => insert(tableName, data),
1308
+ get: (where) => get(tableName, where),
1309
+ list: (where) => list(tableName, where)
1310
+ });
1311
+ const parseTemplate = (strings, ...vars) => {
1312
+ let sql = strings[0];
1313
+ const values = [];
1314
+ for (let i = 0; i < vars.length; i++) {
1315
+ values.push(vars[i]);
1316
+ sql += `$${i + 1}${strings[i + 1]}`;
1317
+ }
1318
+ return { sql, values };
1319
+ };
1320
+ const many = async (strings, ...vars) => {
1321
+ const { sql, values } = parseTemplate(strings, ...vars);
1322
+ try {
1323
+ const reader = await connection.runAndReadAll(sql, values);
1324
+ return convertBigInts(reader.getRowObjects());
1325
+ } catch (e) {
1326
+ throw new DatabaseError("Failed to execute many query", {
1327
+ sql,
1328
+ values,
1329
+ originalError: formatDbError(e)
1330
+ });
1331
+ }
1332
+ };
1333
+ const single = async (strings, ...vars) => {
1334
+ const { sql, values } = parseTemplate(strings, ...vars);
1335
+ try {
1336
+ const reader = await connection.runAndReadAll(sql, values);
1337
+ const rows = reader.getRowObjects();
1338
+ return rows[0] ? convertBigInts(rows[0]) : null;
1339
+ } catch (e) {
1340
+ throw new DatabaseError("Failed to execute single query", {
1341
+ sql,
1342
+ values,
1343
+ originalError: formatDbError(e)
1344
+ });
1345
+ }
1346
+ };
1347
+ const pluck = async (strings, ...vars) => {
1348
+ const { sql, values } = parseTemplate(strings, ...vars);
1349
+ try {
1350
+ const reader = await connection.runAndReadAll(sql, values);
1351
+ const rows = reader.getRowObjects();
1352
+ if (rows.length === 0) return null;
1353
+ const firstRow = rows[0];
1354
+ const firstKey = Object.keys(firstRow)[0];
1355
+ return convertBigInts(firstRow[firstKey]);
1356
+ } catch (e) {
1357
+ throw new DatabaseError("Failed to execute pluck query", {
1358
+ sql,
1359
+ values,
1360
+ originalError: formatDbError(e)
1361
+ });
1362
+ }
1363
+ };
1364
+ const execute = async (strings, ...vars) => {
1365
+ const { sql, values } = parseTemplate(strings, ...vars);
1366
+ try {
1367
+ await connection.run(sql, values);
1368
+ const createTableMatch = sql.match(
1369
+ /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?/i
1370
+ );
1371
+ if (createTableMatch) {
1372
+ const tableName = createTableMatch[1];
1373
+ const pendingFile = pendingJSONFiles.get(tableName);
1374
+ if (pendingFile) {
1375
+ await loadJSONData(connection, tableName, pendingFile);
1376
+ pendingJSONFiles.delete(tableName);
1377
+ } else if (writeStrategy !== "none") {
1378
+ try {
1379
+ const { access } = await import("node:fs/promises");
1380
+ const jsonFilePath = join(url, `${tableName}.json`);
1381
+ try {
1382
+ await access(jsonFilePath);
1383
+ } catch {
1384
+ await exportTableToJSON(connection, tableName, url);
1385
+ }
1386
+ } catch (error) {
1387
+ console.warn(
1388
+ `[json-adapter] Could not export newly created table ${tableName}: ${error instanceof Error ? error.message : String(error)}`
1389
+ );
1390
+ }
1391
+ }
1392
+ }
1393
+ } catch (e) {
1394
+ throw new DatabaseError("Failed to execute query", {
1395
+ sql,
1396
+ values,
1397
+ originalError: formatDbError(e)
1398
+ });
1399
+ }
1400
+ };
1401
+ const query = async (str, ...values) => {
1402
+ const sql = str;
1403
+ const args = Array.isArray(values[0]) ? values[0] : values;
1404
+ try {
1405
+ const reader = await connection.runAndReadAll(sql, args);
1406
+ const rows = convertBigInts(reader.getRowObjects());
1407
+ return {
1408
+ command: sql.split(" ")[0].toUpperCase(),
1409
+ rowCount: rows.length,
1410
+ oid: null,
1411
+ fields: rows.length > 0 ? Object.keys(rows[0]).map((name) => ({
1412
+ name,
1413
+ tableID: 0,
1414
+ columnID: 0,
1415
+ dataTypeID: 0,
1416
+ dataTypeSize: -1,
1417
+ dataTypeModifier: -1,
1418
+ format: "text"
1419
+ })) : [],
1420
+ rows
1421
+ };
1422
+ } catch (e) {
1423
+ throw new DatabaseError("Failed to execute raw query", {
1424
+ sql,
1425
+ args,
1426
+ originalError: formatDbError(e)
1427
+ });
1428
+ }
1429
+ };
1430
+ const oo = many;
1431
+ const oO = single;
1432
+ const ox = pluck;
1433
+ const xx = execute;
1434
+ const syncSchema2 = async (schema) => {
1435
+ const statements = schema.trim().split(";").filter((stmt) => stmt.trim() !== "");
1436
+ const createTableStatements = [];
1437
+ const indexStatements = [];
1438
+ const otherStatements = [];
1439
+ for (const stmt of statements) {
1440
+ const trimmed = stmt.trim();
1441
+ if (!trimmed) continue;
1442
+ const upperTrimmed = trimmed.toUpperCase();
1443
+ if (upperTrimmed.startsWith("CREATE TRIGGER")) {
1444
+ console.warn(
1445
+ "[json-adapter] Skipping trigger creation - timestamps managed at application level"
1446
+ );
1447
+ continue;
1448
+ }
1449
+ if (upperTrimmed.startsWith("CREATE INDEX") || upperTrimmed.startsWith("CREATE UNIQUE INDEX")) {
1450
+ indexStatements.push(trimmed);
1451
+ } else if (upperTrimmed.startsWith("CREATE TABLE")) {
1452
+ createTableStatements.push(trimmed);
1453
+ } else {
1454
+ otherStatements.push(trimmed);
1455
+ }
1456
+ }
1457
+ for (const ddl of createTableStatements) {
1458
+ const { ddl: transformedDDL, indexes: remainingIndexes } = convertUniqueIndexesToInlineConstraints(ddl, indexStatements);
1459
+ const createTableMatch = transformedDDL.match(
1460
+ /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?/i
1461
+ );
1462
+ const tableName = createTableMatch ? createTableMatch[1] : null;
1463
+ if (tableName && inferredSchemaTables.has(tableName)) {
1464
+ try {
1465
+ await connection.run(`DROP TABLE IF EXISTS "${tableName}"`);
1466
+ inferredSchemaTables.delete(tableName);
1467
+ console.log(
1468
+ `[json-adapter] Dropped inferred-schema table ${tableName} for schema upgrade`
1469
+ );
1470
+ } catch (error) {
1471
+ console.warn(
1472
+ `[json-adapter] Could not drop inferred-schema table ${tableName}: ${error instanceof Error ? error.message : String(error)}`
1473
+ );
1474
+ }
1475
+ }
1476
+ try {
1477
+ await connection.run(transformedDDL);
1478
+ if (tableName) {
1479
+ const pendingFile = pendingJSONFiles.get(tableName);
1480
+ if (pendingFile) {
1481
+ await loadJSONData(connection, tableName, pendingFile);
1482
+ pendingJSONFiles.delete(tableName);
1483
+ } else if (writeStrategy !== "none") {
1484
+ try {
1485
+ const { access } = await import("node:fs/promises");
1486
+ const jsonFilePath = join(url, `${tableName}.json`);
1487
+ try {
1488
+ await access(jsonFilePath);
1489
+ } catch {
1490
+ await exportTableToJSON(connection, tableName, url);
1491
+ }
1492
+ } catch (error) {
1493
+ console.warn(
1494
+ `[json-adapter] Could not export newly created table ${tableName}: ${error instanceof Error ? error.message : String(error)}`
1495
+ );
1496
+ }
1497
+ }
1498
+ }
1499
+ } catch (e) {
1500
+ console.error("Schema sync error (CREATE TABLE):", e);
1501
+ }
1502
+ for (const indexSQL of remainingIndexes) {
1503
+ try {
1504
+ await connection.run(indexSQL);
1505
+ } catch (e) {
1506
+ console.error("Schema sync error (CREATE INDEX):", e);
1507
+ }
1508
+ }
1509
+ }
1510
+ for (const stmt of otherStatements) {
1511
+ try {
1512
+ await connection.run(stmt);
1513
+ } catch (e) {
1514
+ console.error("Schema sync error:", e);
1515
+ }
1516
+ }
1517
+ };
1518
+ const initializeSchemas = async (options2) => {
1519
+ const schemaManager = new DatabaseSchemaManager();
1520
+ const currentDb = {
1521
+ url,
1522
+ client: connection,
1523
+ query,
1524
+ insert,
1525
+ update,
1526
+ upsert,
1527
+ get,
1528
+ list,
1529
+ getOrInsert,
1530
+ delete: deleteRecords,
1531
+ count,
1532
+ table,
1533
+ tableExists: tableExists2,
1534
+ many,
1535
+ single,
1536
+ pluck,
1537
+ execute,
1538
+ oo,
1539
+ oO,
1540
+ ox,
1541
+ xx,
1542
+ syncSchema: syncSchema2
1543
+ };
1544
+ await schemaManager.initializeSchemas(currentDb, options2);
1545
+ };
1546
+ const transaction = async (callback) => {
1547
+ try {
1548
+ await connection.run("BEGIN TRANSACTION");
1549
+ const txDb = {
1550
+ url,
1551
+ client: connection,
1552
+ insert,
1553
+ get,
1554
+ list,
1555
+ update,
1556
+ upsert,
1557
+ getOrInsert,
1558
+ delete: deleteRecords,
1559
+ count,
1560
+ table,
1561
+ many,
1562
+ single,
1563
+ pluck,
1564
+ execute,
1565
+ query,
1566
+ oo,
1567
+ oO,
1568
+ ox,
1569
+ xx,
1570
+ tableExists: tableExists2,
1571
+ syncSchema: syncSchema2,
1572
+ transaction
1573
+ };
1574
+ const result = await callback(txDb);
1575
+ await connection.run("COMMIT");
1576
+ return result;
1577
+ } catch (error) {
1578
+ await connection.run("ROLLBACK");
1579
+ throw error;
1580
+ }
1581
+ };
1582
+ const beginTransaction = async () => {
1583
+ await connection.run("BEGIN TRANSACTION");
1584
+ let active = true;
1585
+ const commit = async () => {
1586
+ if (!active) {
1587
+ throw new DatabaseError("Transaction already ended", {});
1588
+ }
1589
+ await connection.run("COMMIT");
1590
+ active = false;
1591
+ };
1592
+ const rollback = async () => {
1593
+ if (!active) {
1594
+ throw new DatabaseError("Transaction already ended", {});
1595
+ }
1596
+ await connection.run("ROLLBACK");
1597
+ active = false;
1598
+ };
1599
+ const isActive = () => active;
1600
+ const txHandle = {
1601
+ url,
1602
+ client: connection,
1603
+ insert,
1604
+ get,
1605
+ list,
1606
+ update,
1607
+ upsert,
1608
+ getOrInsert,
1609
+ delete: deleteRecords,
1610
+ count,
1611
+ table,
1612
+ many,
1613
+ single,
1614
+ pluck,
1615
+ execute,
1616
+ query,
1617
+ oo,
1618
+ oO,
1619
+ ox,
1620
+ xx,
1621
+ tableExists: tableExists2,
1622
+ syncSchema: syncSchema2,
1623
+ transaction,
1624
+ commit,
1625
+ rollback,
1626
+ isActive
1627
+ };
1628
+ return txHandle;
1629
+ };
1630
+ return {
1631
+ url,
1632
+ requiresSchemaCheck: true,
1633
+ client: connection,
1634
+ query,
1635
+ insert,
1636
+ update,
1637
+ upsert,
1638
+ get,
1639
+ list,
1640
+ getOrInsert,
1641
+ delete: deleteRecords,
1642
+ count,
1643
+ table,
1644
+ tableExists: tableExists2,
1645
+ many,
1646
+ single,
1647
+ pluck,
1648
+ execute,
1649
+ oo,
1650
+ oO,
1651
+ ox,
1652
+ xx,
1653
+ syncSchema: syncSchema2,
1654
+ initializeSchemas,
1655
+ transaction,
1656
+ beginTransaction,
1657
+ // JSON-specific methods
1658
+ exportTable,
1659
+ inferSchemaFromJSON
1660
+ };
1661
+ })();
1662
+ pendingConnections$1.set(cacheKey, connectionPromise);
1663
+ try {
1664
+ const db = await connectionPromise;
1665
+ memoryConnectionCache.set(cacheKey, db);
1666
+ return db;
1667
+ } finally {
1668
+ pendingConnections$1.delete(cacheKey);
1669
+ }
1670
+ }
1671
+ const json = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1672
+ __proto__: null,
1673
+ clearConnectionCache,
1674
+ getDatabase: getDatabase$2
1675
+ }, Symbol.toStringTag, { value: "Module" }));
1676
+ function generateAddColumnStatement(table, column, _dialect) {
1677
+ const parts = [column.name, column.type];
1678
+ if (column.primaryKey) {
1679
+ parts.push("PRIMARY KEY");
1680
+ }
1681
+ if (column.notNull) {
1682
+ parts.push("NOT NULL");
1683
+ }
1684
+ if (column.unique) {
1685
+ parts.push("UNIQUE");
1686
+ }
1687
+ if (column.defaultValue !== void 0) {
1688
+ const defaultClause = generateDefaultClause(
1689
+ column.type,
1690
+ column.defaultValue
1691
+ );
1692
+ parts.push(`DEFAULT ${defaultClause}`);
1693
+ }
1694
+ if (column.check) {
1695
+ parts.push(`CHECK (${column.check})`);
1696
+ }
1697
+ if (column.foreignKey) {
1698
+ const fk = column.foreignKey;
1699
+ const fkParts = [`REFERENCES ${fk.table}(${fk.column})`];
1700
+ if (fk.onDelete) {
1701
+ fkParts.push(`ON DELETE ${fk.onDelete}`);
1702
+ }
1703
+ if (fk.onUpdate) {
1704
+ fkParts.push(`ON UPDATE ${fk.onUpdate}`);
1705
+ }
1706
+ parts.push(fkParts.join(" "));
1707
+ }
1708
+ const columnDef = parts.join(" ");
1709
+ return `ALTER TABLE ${table} ADD COLUMN ${columnDef}`;
1710
+ }
1711
+ function generateDefaultClause(columnType, defaultValue) {
1712
+ if (defaultValue === null) {
1713
+ return "NULL";
1714
+ }
1715
+ const typeUpper = columnType.toUpperCase();
1716
+ if (typeUpper.includes("TEXT") || typeUpper.includes("CHAR") || typeUpper.includes("VARCHAR") || typeUpper.includes("STRING")) {
1717
+ if (defaultValue === "") {
1718
+ return "''";
1719
+ }
1720
+ const escaped = String(defaultValue).replace(/'/g, "''");
1721
+ return `'${escaped}'`;
1722
+ }
1723
+ if (typeUpper.includes("BOOL")) {
1724
+ return defaultValue ? "true" : "false";
1725
+ }
1726
+ if (typeUpper.includes("INT") || typeUpper.includes("REAL") || typeUpper.includes("FLOAT") || typeUpper.includes("DOUBLE") || typeUpper.includes("NUMERIC") || typeUpper.includes("DECIMAL")) {
1727
+ return String(defaultValue);
1728
+ }
1729
+ if (typeUpper.includes("JSON")) {
1730
+ if (typeof defaultValue === "string") {
1731
+ return `'${defaultValue.replace(/'/g, "''")}'`;
1732
+ }
1733
+ return `'${JSON.stringify(defaultValue).replace(/'/g, "''")}'`;
1734
+ }
1735
+ if (typeUpper.includes("DATE") || typeUpper.includes("TIME") || typeUpper.includes("TIMESTAMP")) {
1736
+ if (defaultValue instanceof Date) {
1737
+ return `'${defaultValue.toISOString()}'`;
1738
+ }
1739
+ return `'${String(defaultValue).replace(/'/g, "''")}'`;
1740
+ }
1741
+ if (typeof defaultValue === "string") {
1742
+ return `'${defaultValue.replace(/'/g, "''")}'`;
1743
+ }
1744
+ return String(defaultValue);
1745
+ }
1746
+ function generateCreateIndexStatement(table, index2) {
1747
+ const parts = ["CREATE"];
1748
+ if (index2.unique) {
1749
+ parts.push("UNIQUE");
1750
+ }
1751
+ parts.push("INDEX");
1752
+ parts.push(index2.name);
1753
+ parts.push(`ON ${table}`);
1754
+ const columns = index2.columns.join(", ");
1755
+ parts.push(`(${columns})`);
1756
+ if (index2.where) {
1757
+ parts.push(`WHERE ${index2.where}`);
1758
+ }
1759
+ return parts.join(" ");
1760
+ }
1761
+ function validateTableName(tableName) {
1762
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
1763
+ throw new Error(
1764
+ `Invalid table name: ${tableName}. Table names must start with a letter or underscore and contain only alphanumeric characters and underscores.`
1765
+ );
1766
+ }
1767
+ }
1768
+ function validateColumnName$1(columnName) {
1769
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(columnName)) {
1770
+ throw new Error(
1771
+ `Invalid column name: ${columnName}. Column names must start with a letter or underscore and contain only alphanumeric characters and underscores.`
1772
+ );
1773
+ }
1774
+ }
1775
+ function validateIndexName(indexName) {
1776
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(indexName)) {
1777
+ throw new Error(
1778
+ `Invalid index name: ${indexName}. Index names must start with a letter or underscore and contain only alphanumeric characters and underscores.`
1779
+ );
1780
+ }
1781
+ }
1782
+ const connectionCache = /* @__PURE__ */ new Map();
1783
+ const pendingConnections = /* @__PURE__ */ new Map();
1784
+ function clearPostgresConnectionCache() {
1785
+ connectionCache.clear();
1786
+ pendingConnections.clear();
1787
+ }
1788
+ function vectorOperator(metric) {
1789
+ switch (metric) {
1790
+ case "cosine":
1791
+ return "<=>";
1792
+ case "l2":
1793
+ return "<->";
1794
+ case "ip":
1795
+ return "<#>";
1796
+ default:
1797
+ return "<=>";
1798
+ }
1799
+ }
1800
+ function getMaxPostgresParameterIndex(sql) {
1801
+ let maxIndex = 0;
1802
+ for (const match of sql.matchAll(/\$(\d+)/g)) {
1803
+ const index2 = Number(match[1]);
1804
+ if (Number.isSafeInteger(index2) && index2 > maxIndex) {
1805
+ maxIndex = index2;
1806
+ }
1807
+ }
1808
+ return maxIndex;
1809
+ }
1810
+ function usesSingleArrayParameter(sql) {
1811
+ const identifier = String.raw`(?:"[^"]+"|[a-z_][\w$]*)`;
1812
+ const qualifiedIdentifier = String.raw`${identifier}(?:\s*\.\s*${identifier})?`;
1813
+ const arrayType = String.raw`${qualifiedIdentifier}(?:\s*\([^)]*\))?\s*\[\]`;
1814
+ return new RegExp(String.raw`\$1\s*::\s*${arrayType}`, "i").test(sql) || new RegExp(
1815
+ String.raw`\bCAST\s*\(\s*\$1\s+AS\s+${arrayType}\s*\)`,
1816
+ "i"
1817
+ ).test(sql) || /\b(?:ANY|ALL|SOME)\s*\(\s*\$1\s*\)/i.test(sql);
1818
+ }
1819
+ function normalizeRawQueryValues(sql, values) {
1820
+ if (values.length !== 1 || !Array.isArray(values[0])) {
1821
+ return values;
1822
+ }
1823
+ const valuesArray = values[0];
1824
+ const maxParameterIndex = getMaxPostgresParameterIndex(sql);
1825
+ if (maxParameterIndex === 1 && usesSingleArrayParameter(sql)) {
1826
+ return values;
1827
+ }
1828
+ if (maxParameterIndex === 0 || maxParameterIndex === valuesArray.length) {
1829
+ return valuesArray;
1830
+ }
1831
+ return values;
1832
+ }
1833
+ function rewriteQuestionMarkPlaceholders(sql) {
1834
+ let output = "";
1835
+ let count = 0;
1836
+ let index2 = 0;
1837
+ const copyQuoted = (quote, escaped = false) => {
1838
+ output += quote;
1839
+ index2 += 1;
1840
+ while (index2 < sql.length) {
1841
+ const char = sql[index2];
1842
+ output += char;
1843
+ index2 += 1;
1844
+ if (escaped && char === "\\" && index2 < sql.length) {
1845
+ output += sql[index2];
1846
+ index2 += 1;
1847
+ continue;
1848
+ }
1849
+ if (char === quote) {
1850
+ if (sql[index2] === quote) {
1851
+ output += sql[index2];
1852
+ index2 += 1;
1853
+ continue;
1854
+ }
1855
+ break;
1856
+ }
1857
+ }
1858
+ };
1859
+ const copyUntil = (end) => {
1860
+ const endIndex = sql.indexOf(end, index2 + end.length);
1861
+ if (endIndex === -1) {
1862
+ output += sql.slice(index2);
1863
+ index2 = sql.length;
1864
+ return;
1865
+ }
1866
+ output += sql.slice(index2, endIndex + end.length);
1867
+ index2 = endIndex + end.length;
1868
+ };
1869
+ while (index2 < sql.length) {
1870
+ const char = sql[index2];
1871
+ const next = sql[index2 + 1];
1872
+ if ((char === "E" || char === "e") && next === "'") {
1873
+ output += char;
1874
+ index2 += 1;
1875
+ copyQuoted("'", true);
1876
+ continue;
1877
+ }
1878
+ if (char === "'") {
1879
+ copyQuoted("'");
1880
+ continue;
1881
+ }
1882
+ if (char === '"') {
1883
+ copyQuoted('"');
1884
+ continue;
1885
+ }
1886
+ if (char === "-" && next === "-") {
1887
+ const endIndex = sql.indexOf("\n", index2 + 2);
1888
+ if (endIndex === -1) {
1889
+ output += sql.slice(index2);
1890
+ index2 = sql.length;
1891
+ } else {
1892
+ output += sql.slice(index2, endIndex + 1);
1893
+ index2 = endIndex + 1;
1894
+ }
1895
+ continue;
1896
+ }
1897
+ if (char === "/" && next === "*") {
1898
+ copyUntil("*/");
1899
+ continue;
1900
+ }
1901
+ if (char === "$") {
1902
+ const tag = sql.slice(index2).match(/^\$[A-Za-z_][\w$]*\$|^\$\$/);
1903
+ if (tag) {
1904
+ copyUntil(tag[0]);
1905
+ continue;
1906
+ }
1907
+ }
1908
+ if (char === "?" && next !== "|" && next !== "&") {
1909
+ count += 1;
1910
+ output += `$${count}`;
1911
+ index2 += 1;
1912
+ continue;
1913
+ }
1914
+ output += char;
1915
+ index2 += 1;
1916
+ }
1917
+ return { sql: output, count };
1918
+ }
1919
+ function usesSingleQuestionArrayParameter(sql) {
1920
+ const identifier = String.raw`(?:"[^"]+"|[a-z_][\w$]*)`;
1921
+ const qualifiedIdentifier = String.raw`${identifier}(?:\s*\.\s*${identifier})?`;
1922
+ const arrayType = String.raw`${qualifiedIdentifier}(?:\s*\([^)]*\))?\s*\[\]`;
1923
+ return new RegExp(String.raw`\?\s*::\s*${arrayType}`, "i").test(sql) || new RegExp(String.raw`\bCAST\s*\(\s*\?\s+AS\s+${arrayType}\s*\)`, "i").test(
1924
+ sql
1925
+ ) || /\b(?:ANY|ALL|SOME)\s*\(\s*\?\s*\)/i.test(sql);
1926
+ }
1927
+ function normalizeQuestionMarkQueryValues(sql, values, placeholderCount) {
1928
+ if (values.length !== 1 || !Array.isArray(values[0])) {
1929
+ return values;
1930
+ }
1931
+ if (placeholderCount === 1 && usesSingleQuestionArrayParameter(sql)) {
1932
+ return values;
1933
+ }
1934
+ if (placeholderCount === values[0].length) {
1935
+ return values[0];
1936
+ }
1937
+ return values;
1938
+ }
1939
+ function normalizePostgresRawQuery(sql, values) {
1940
+ if (getMaxPostgresParameterIndex(sql) > 0) {
1941
+ return { sql, values: normalizeRawQueryValues(sql, values) };
1942
+ }
1943
+ const rewritten = rewriteQuestionMarkPlaceholders(sql);
1944
+ const placeholderCount = rewritten.count;
1945
+ const queryValues = normalizeQuestionMarkQueryValues(
1946
+ sql,
1947
+ values,
1948
+ placeholderCount
1949
+ );
1950
+ if (placeholderCount > 0 && placeholderCount === queryValues.length) {
1951
+ return {
1952
+ sql: rewritten.sql,
1953
+ values: queryValues
1954
+ };
1955
+ }
1956
+ return { sql, values: normalizeRawQueryValues(sql, values) };
1957
+ }
1958
+ function vectorOpsClass(metric) {
1959
+ switch (metric) {
1960
+ case "cosine":
1961
+ return "vector_cosine_ops";
1962
+ case "l2":
1963
+ return "vector_l2_ops";
1964
+ case "ip":
1965
+ return "vector_ip_ops";
1966
+ default:
1967
+ return "vector_cosine_ops";
1968
+ }
1969
+ }
1970
+ function toVectorLiteral(embedding) {
1971
+ if (embedding.length === 0) {
1972
+ throw new DatabaseError("Vector embedding must not be empty", {});
1973
+ }
1974
+ for (let i = 0; i < embedding.length; i++) {
1975
+ if (!Number.isFinite(embedding[i])) {
1976
+ throw new DatabaseError(
1977
+ "Vector embedding contains invalid value (NaN or Infinity)",
1978
+ { index: i, value: embedding[i] }
1979
+ );
1980
+ }
1981
+ }
1982
+ return `[${embedding.join(",")}]`;
1983
+ }
1984
+ function createVectorCapabilities(pool) {
1985
+ return {
1986
+ async ensureColumn(table, column, dimensions) {
1987
+ if (!Number.isInteger(dimensions) || dimensions <= 0) {
1988
+ throw new DatabaseError(
1989
+ "Vector dimensions must be a positive integer",
1990
+ { dimensions }
1991
+ );
1992
+ }
1993
+ try {
1994
+ await pool.query("CREATE EXTENSION IF NOT EXISTS vector");
1995
+ await pool.query(
1996
+ `ALTER TABLE "${table}" ADD COLUMN IF NOT EXISTS "${column}" vector(${dimensions})`
1997
+ );
1998
+ } catch (e) {
1999
+ throw new DatabaseError("Failed to ensure vector column", {
2000
+ table,
2001
+ column,
2002
+ dimensions,
2003
+ originalError: formatDbError(e)
2004
+ });
2005
+ }
2006
+ },
2007
+ async ensureIndex(table, column, options) {
2008
+ const metric = options?.metric || "cosine";
2009
+ const indexType = options?.type || "hnsw";
2010
+ const opsClass = vectorOpsClass(metric);
2011
+ const indexName = `idx_${table}_${column}_${metric}_${indexType}`;
2012
+ try {
2013
+ await pool.query(
2014
+ `CREATE INDEX IF NOT EXISTS "${indexName}" ON "${table}" USING ${indexType} ("${column}" ${opsClass})`
2015
+ );
2016
+ } catch (e) {
2017
+ throw new DatabaseError("Failed to ensure vector index", {
2018
+ table,
2019
+ column,
2020
+ indexName,
2021
+ originalError: formatDbError(e)
2022
+ });
2023
+ }
2024
+ },
2025
+ async upsertVector(table, where, column, embedding) {
2026
+ const keys = Object.keys(where);
2027
+ const values = Object.values(where);
2028
+ const conditions = keys.map((key, i) => `"${key}" IS NOT DISTINCT FROM $${i + 1}`).join(" AND ");
2029
+ const vectorParam = `$${values.length + 1}`;
2030
+ try {
2031
+ await pool.query(
2032
+ `UPDATE "${table}" SET "${column}" = ${vectorParam}::vector WHERE ${conditions}`,
2033
+ [...values, toVectorLiteral(embedding)]
2034
+ );
2035
+ } catch (e) {
2036
+ throw new DatabaseError("Failed to upsert vector", {
2037
+ table,
2038
+ column,
2039
+ where,
2040
+ originalError: formatDbError(e)
2041
+ });
2042
+ }
2043
+ },
2044
+ async search(table, column, embedding, options) {
2045
+ const metric = options?.metric || "cosine";
2046
+ const limit = options?.limit || 10;
2047
+ const op = vectorOperator(metric);
2048
+ const queryParams = [toVectorLiteral(embedding)];
2049
+ let whereClause = `"${column}" IS NOT NULL`;
2050
+ if (options?.where) {
2051
+ whereClause += ` AND (${options.where})`;
2052
+ if (options.params) {
2053
+ queryParams.push(...options.params);
2054
+ }
2055
+ }
2056
+ const limitParam = `$${queryParams.length + 1}`;
2057
+ queryParams.push(limit);
2058
+ const sql = `SELECT *, ("${column}" ${op} $1::vector) AS distance FROM "${table}" WHERE ${whereClause} ORDER BY "${column}" ${op} $1::vector LIMIT ${limitParam}`;
2059
+ try {
2060
+ const result = await pool.query(sql, queryParams);
2061
+ return result.rows.map((row) => ({
2062
+ ...row,
2063
+ id: row.id,
2064
+ distance: Number.parseFloat(row.distance)
2065
+ }));
2066
+ } catch (e) {
2067
+ throw new DatabaseError("Failed to execute vector search", {
2068
+ table,
2069
+ column,
2070
+ metric,
2071
+ limit,
2072
+ originalError: formatDbError(e)
2073
+ });
2074
+ }
2075
+ }
2076
+ };
2077
+ }
2078
+ async function getDatabase$1(options = {}) {
2079
+ const envUrl = process.env.HAVE_SQL_URL || process.env.SQLOO_URL || null;
2080
+ const envDatabase = process.env.HAVE_SQL_DATABASE || process.env.SQLOO_DATABASE || null;
2081
+ const envHost = process.env.HAVE_SQL_HOST || process.env.SQLOO_HOST || "localhost";
2082
+ const envUser = process.env.HAVE_SQL_USER || process.env.SQLOO_USER || "";
2083
+ const envPortRaw = process.env.HAVE_SQL_PORT || process.env.SQLOO_PORT;
2084
+ const envPort = envPortRaw ? Number(envPortRaw) || 5432 : 5432;
2085
+ const derivedHost = options.host || envHost;
2086
+ const derivedPort = options.port || envPort;
2087
+ const derivedDatabase = options.database || envDatabase;
2088
+ const derivedUser = options.user || envUser;
2089
+ const cacheKey = options.dbid || options.url || (derivedDatabase ? `pg:${derivedUser ? `${derivedUser}@` : ""}${derivedHost}:${derivedPort}/${derivedDatabase}` : envUrl || null);
2090
+ if (cacheKey) {
2091
+ const cached = connectionCache.get(cacheKey);
2092
+ if (cached) {
2093
+ return cached;
2094
+ }
2095
+ const pending = pendingConnections.get(cacheKey);
2096
+ if (pending) return pending;
2097
+ }
2098
+ const connectionPromise = createDatabase(options, cacheKey);
2099
+ if (cacheKey) {
2100
+ pendingConnections.set(cacheKey, connectionPromise);
2101
+ connectionPromise.finally(() => {
2102
+ if (cacheKey) pendingConnections.delete(cacheKey);
2103
+ });
2104
+ }
2105
+ return connectionPromise;
2106
+ }
2107
+ async function createDatabase(options, cacheKey) {
2108
+ const config = loadEnvConfig(options, {
2109
+ packageName: "sql",
2110
+ schema: {
2111
+ url: "string",
2112
+ database: "string",
2113
+ host: "string",
2114
+ port: "number",
2115
+ user: "string",
2116
+ password: "string"
2117
+ }
2118
+ });
2119
+ if (!config.url && !config.host && !config.database && !config.user) {
2120
+ const legacyConfig = loadEnvConfig(
2121
+ {},
2122
+ {
2123
+ prefix: "SQLOO",
2124
+ schema: {
2125
+ url: "string",
2126
+ database: "string",
2127
+ host: "string",
2128
+ port: "number",
2129
+ user: "string",
2130
+ password: "string"
2131
+ }
2132
+ }
2133
+ );
2134
+ for (const [key, value] of Object.entries(legacyConfig)) {
2135
+ if (config[key] === void 0 && value !== void 0) {
2136
+ config[key] = value;
2137
+ }
2138
+ }
2139
+ }
2140
+ const { database, host = "localhost", user, password, port = 5432 } = config;
2141
+ const url = config.url || `postgresql://${user}${password ? `:${password}` : ""}@${host}:${port}/${database || "postgres"}`;
2142
+ const poolMax = options.max ?? 20;
2143
+ const pool = new Pool(
2144
+ config.url ? { connectionString: config.url, max: poolMax } : {
2145
+ host,
2146
+ user,
2147
+ password,
2148
+ port,
2149
+ database,
2150
+ max: poolMax
2151
+ }
2152
+ );
2153
+ const originalEnd = pool.end.bind(pool);
2154
+ const evictFromCache = () => {
2155
+ for (const [key, cached] of connectionCache.entries()) {
2156
+ if (cached.client === pool) {
2157
+ connectionCache.delete(key);
2158
+ }
2159
+ }
2160
+ };
2161
+ pool.end = (callback) => {
2162
+ evictFromCache();
2163
+ if (callback) {
2164
+ return originalEnd(callback);
2165
+ }
2166
+ return originalEnd();
2167
+ };
2168
+ const client = pool;
2169
+ const serializeValue = (value) => {
2170
+ if (value === null || value === void 0) {
2171
+ return value;
2172
+ }
2173
+ if (value instanceof Date) {
2174
+ return value.toISOString();
2175
+ }
2176
+ if (typeof value === "object") {
2177
+ return JSON.stringify(value);
2178
+ }
2179
+ return value;
2180
+ };
2181
+ const serializeRecord = (record) => {
2182
+ const serialized = {};
2183
+ for (const [key, value] of Object.entries(record)) {
2184
+ if (value === void 0) {
2185
+ continue;
2186
+ }
2187
+ serialized[key] = serializeValue(value);
2188
+ }
2189
+ return serialized;
2190
+ };
2191
+ let serverVersionNumPromise;
2192
+ const nullsNotDistinctIndexCache = /* @__PURE__ */ new Map();
2193
+ const hasNullConflictValue = (conflictColumns, data) => conflictColumns.some((col) => data[col] === null);
2194
+ const validateUpsertConflictColumns = (table2, conflictColumns, serializedData) => {
2195
+ const missingColumns = conflictColumns.filter(
2196
+ (col) => !(col in serializedData)
2197
+ );
2198
+ if (missingColumns.length > 0) {
2199
+ throw new DatabaseError("Conflict columns missing from data", {
2200
+ table: table2,
2201
+ conflictColumns,
2202
+ missingColumns,
2203
+ availableColumns: Object.keys(serializedData),
2204
+ hint: "All columns specified in ON CONFLICT must be present in the data being inserted. Undefined values should be replaced with null or an appropriate default."
2205
+ });
2206
+ }
2207
+ };
2208
+ const getServerVersionNum = async () => {
2209
+ serverVersionNumPromise ??= client.query("SHOW server_version_num").then((result) => Number(result.rows[0]?.server_version_num) || 0);
2210
+ return serverVersionNumPromise;
2211
+ };
2212
+ const hasNativeNullsNotDistinctIndex = async (table2, conflictColumns) => {
2213
+ const versionNum = await getServerVersionNum();
2214
+ if (versionNum < 15e4) return false;
2215
+ const cacheKey2 = `${table2}:${conflictColumns.join(",")}`;
2216
+ let cached = nullsNotDistinctIndexCache.get(cacheKey2);
2217
+ if (!cached) {
2218
+ cached = client.query(
2219
+ `
2220
+ SELECT 1
2221
+ FROM pg_index i
2222
+ WHERE i.indrelid = to_regclass($1)
2223
+ AND i.indisunique
2224
+ AND i.indnullsnotdistinct
2225
+ AND ARRAY(
2226
+ SELECT a.attname
2227
+ FROM unnest(i.indkey) WITH ORDINALITY AS cols(attnum, ord)
2228
+ JOIN pg_attribute a
2229
+ ON a.attrelid = i.indrelid
2230
+ AND a.attnum = cols.attnum
2231
+ ORDER BY cols.ord
2232
+ ) = $2::text[]
2233
+ LIMIT 1
2234
+ `,
2235
+ [table2, conflictColumns]
2236
+ ).then((result) => result.rows.length > 0).catch(() => false);
2237
+ nullsNotDistinctIndexCache.set(cacheKey2, cached);
2238
+ }
2239
+ return cached;
2240
+ };
2241
+ const buildPostgresUpsertParts = (conflictColumns, serializedData) => {
2242
+ const keys = Object.keys(serializedData);
2243
+ const values = Object.values(serializedData);
2244
+ const placeholders = keys.map((_, i) => `$${i + 1}`).join(", ");
2245
+ const updateSet = keys.map((key, i) => `${key} = $${i + 1}`).join(", ");
2246
+ const conflict = conflictColumns.join(", ");
2247
+ return { keys, values, placeholders, updateSet, conflict };
2248
+ };
2249
+ const executeStandardPostgresUpsert = async (executor, table2, conflictColumns, serializedData) => {
2250
+ const { keys, values, placeholders, updateSet, conflict } = buildPostgresUpsertParts(conflictColumns, serializedData);
2251
+ const sql = `INSERT INTO ${table2} (${keys.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${conflict}) DO UPDATE SET ${updateSet}`;
2252
+ const result = await executor.query(sql, values);
2253
+ return { operation: "upsert", affected: result.rowCount ?? 0 };
2254
+ };
2255
+ const executeNullAwarePostgresUpsert = async (executor, table2, conflictColumns, serializedData) => {
2256
+ const { keys, values, placeholders } = buildPostgresUpsertParts(
2257
+ conflictColumns,
2258
+ serializedData
2259
+ );
2260
+ const lockKey = conflictColumns.map((col) => `${col}:${JSON.stringify(serializedData[col])}`).join("|");
2261
+ await executor.query(
2262
+ "SELECT pg_advisory_xact_lock(hashtext($1), hashtext($2))",
2263
+ [`@happyvertical/sql.upsert:${table2}`, lockKey]
2264
+ );
2265
+ const updateSet = keys.map((key, i) => `${key} = $${i + 1}`).join(", ");
2266
+ const whereStart = values.length + 1;
2267
+ const whereClause = conflictColumns.map((col, i) => `${col} IS NOT DISTINCT FROM $${whereStart + i}`).join(" AND ");
2268
+ const whereValues = conflictColumns.map((col) => serializedData[col]);
2269
+ const updateSql = `UPDATE ${table2} SET ${updateSet} WHERE ${whereClause}`;
2270
+ const updateResult = await executor.query(updateSql, [
2271
+ ...values,
2272
+ ...whereValues
2273
+ ]);
2274
+ if ((updateResult.rowCount ?? 0) > 0) {
2275
+ return { operation: "upsert", affected: updateResult.rowCount ?? 0 };
2276
+ }
2277
+ const insertSql = `INSERT INTO ${table2} (${keys.join(", ")}) VALUES (${placeholders})`;
2278
+ const insertResult = await executor.query(insertSql, values);
2279
+ return { operation: "upsert", affected: insertResult.rowCount ?? 0 };
2280
+ };
2281
+ const executeNullAwarePostgresUpsertInTransaction = async (table2, conflictColumns, serializedData) => {
2282
+ const txClient = await client.connect();
2283
+ try {
2284
+ await txClient.query("BEGIN");
2285
+ const result = await executeNullAwarePostgresUpsert(
2286
+ txClient,
2287
+ table2,
2288
+ conflictColumns,
2289
+ serializedData
2290
+ );
2291
+ await txClient.query("COMMIT");
2292
+ return result;
2293
+ } catch (error) {
2294
+ await txClient.query("ROLLBACK");
2295
+ throw error;
2296
+ } finally {
2297
+ txClient.release();
2298
+ }
2299
+ };
2300
+ const executePostgresUpsert = async (executor, table2, conflictColumns, data, options2, acquireTransaction) => {
2301
+ const serializedData = serializeRecord(data);
2302
+ validateUpsertConflictColumns(table2, conflictColumns, serializedData);
2303
+ if (options2?.nullsDistinct || !hasNullConflictValue(conflictColumns, serializedData)) {
2304
+ return executeStandardPostgresUpsert(
2305
+ executor,
2306
+ table2,
2307
+ conflictColumns,
2308
+ serializedData
2309
+ );
2310
+ }
2311
+ if (await hasNativeNullsNotDistinctIndex(table2, conflictColumns)) {
2312
+ return executeStandardPostgresUpsert(
2313
+ executor,
2314
+ table2,
2315
+ conflictColumns,
2316
+ serializedData
2317
+ );
2318
+ }
2319
+ if (!acquireTransaction) {
2320
+ return executeNullAwarePostgresUpsert(
2321
+ executor,
2322
+ table2,
2323
+ conflictColumns,
2324
+ serializedData
2325
+ );
2326
+ }
2327
+ return executeNullAwarePostgresUpsertInTransaction(
2328
+ table2,
2329
+ conflictColumns,
2330
+ serializedData
2331
+ );
2332
+ };
2333
+ const insert = async (table2, data) => {
2334
+ if (Array.isArray(data)) {
2335
+ const serializedRecords = data.map((record) => serializeRecord(record));
2336
+ const keys2 = Object.keys(serializedRecords[0]);
2337
+ const placeholders2 = serializedRecords.map(
2338
+ (_, i) => `(${keys2.map((_2, j) => `$${i * keys2.length + j + 1}`).join(", ")})`
2339
+ ).join(", ");
2340
+ const query3 = `INSERT INTO ${table2} (${keys2.join(
2341
+ ", "
2342
+ )}) VALUES ${placeholders2}`;
2343
+ const values2 = serializedRecords.reduce(
2344
+ (acc, row) => acc.concat(Object.values(row)),
2345
+ []
2346
+ );
2347
+ const result2 = await client.query(query3, values2);
2348
+ return { operation: "insert", affected: result2.rowCount ?? 0 };
2349
+ }
2350
+ const serializedData = serializeRecord(data);
2351
+ const keys = Object.keys(serializedData);
2352
+ const values = Object.values(serializedData);
2353
+ const placeholders = keys.map((_, i) => `$${i + 1}`).join(", ");
2354
+ const query2 = `INSERT INTO ${table2} (${keys.join(
2355
+ ", "
2356
+ )}) VALUES (${placeholders})`;
2357
+ const result = await client.query(query2, values);
2358
+ return { operation: "insert", affected: result.rowCount ?? 0 };
2359
+ };
2360
+ const get = async (table2, where) => {
2361
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2362
+ if (!whereClause) {
2363
+ throw new DatabaseError(
2364
+ "GET requires at least one WHERE condition to prevent returning an arbitrary record",
2365
+ { table: table2 }
2366
+ );
2367
+ }
2368
+ const query2 = `SELECT * FROM ${table2} ${whereClause}`;
2369
+ try {
2370
+ const result = await client.query(query2, values);
2371
+ return result.rows[0] || null;
2372
+ } catch (e) {
2373
+ throw new DatabaseError("Failed to retrieve record from table", {
2374
+ table: table2,
2375
+ sql: query2,
2376
+ values,
2377
+ originalError: formatDbError(e)
2378
+ });
2379
+ }
2380
+ };
2381
+ const list = async (table2, where) => {
2382
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2383
+ const query2 = `SELECT * FROM ${table2} ${whereClause}`;
2384
+ try {
2385
+ const result = await client.query(query2, values);
2386
+ return result.rows;
2387
+ } catch (e) {
2388
+ throw new DatabaseError("Failed to list records from table", {
2389
+ table: table2,
2390
+ sql: query2,
2391
+ values,
2392
+ originalError: formatDbError(e)
2393
+ });
2394
+ }
2395
+ };
2396
+ const update = async (table2, where, data) => {
2397
+ const serializedData = serializeRecord(data);
2398
+ const keys = Object.keys(serializedData);
2399
+ const values = Object.values(serializedData);
2400
+ const setClause = keys.map((key, i) => `${key} = $${i + 1}`).join(", ");
2401
+ const { sql: whereClause, values: whereValues } = buildWhere(
2402
+ where,
2403
+ values.length + 1,
2404
+ "postgres"
2405
+ );
2406
+ if (!whereClause) {
2407
+ throw new DatabaseError(
2408
+ "UPDATE requires at least one WHERE condition to prevent accidental update of all records",
2409
+ { table: table2 }
2410
+ );
2411
+ }
2412
+ const sql = `UPDATE ${table2} SET ${setClause} ${whereClause}`;
2413
+ try {
2414
+ const result = await client.query(sql, [...values, ...whereValues]);
2415
+ return { operation: "update", affected: result.rowCount ?? 0 };
2416
+ } catch (e) {
2417
+ throw new DatabaseError("Failed to update records in table", {
2418
+ table: table2,
2419
+ sql,
2420
+ values: [...values, ...whereValues],
2421
+ originalError: formatDbError(e)
2422
+ });
2423
+ }
2424
+ };
2425
+ const upsert = async (table2, conflictColumns, data, options2) => {
2426
+ try {
2427
+ return await executePostgresUpsert(
2428
+ client,
2429
+ table2,
2430
+ conflictColumns,
2431
+ data,
2432
+ options2,
2433
+ true
2434
+ );
2435
+ } catch (e) {
2436
+ if (e instanceof DatabaseError && e.message === "Conflict columns missing from data") {
2437
+ throw e;
2438
+ }
2439
+ throw new DatabaseError("Failed to upsert record into table", {
2440
+ table: table2,
2441
+ values: data,
2442
+ conflictColumns,
2443
+ originalError: formatDbError(e)
2444
+ });
2445
+ }
2446
+ };
2447
+ const getOrInsert = async (table2, where, data) => {
2448
+ const result = await get(table2, where);
2449
+ if (result) return result;
2450
+ await insert(table2, data);
2451
+ const inserted = await get(table2, where);
2452
+ if (!inserted) {
2453
+ throw new DatabaseError("Failed to insert and retrieve record", {
2454
+ table: table2,
2455
+ where,
2456
+ data
2457
+ });
2458
+ }
2459
+ return inserted;
2460
+ };
2461
+ const deleteRecords = async (table2, where) => {
2462
+ validateTableName(table2);
2463
+ const keys = Object.keys(where);
2464
+ if (keys.length === 0) {
2465
+ throw new DatabaseError(
2466
+ "DELETE requires at least one WHERE condition to prevent accidental deletion of all records",
2467
+ { table: table2 }
2468
+ );
2469
+ }
2470
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2471
+ try {
2472
+ const result = await client.query(
2473
+ `DELETE FROM ${table2} ${whereClause}`,
2474
+ values
2475
+ );
2476
+ return { operation: "delete", affected: result.rowCount ?? 0 };
2477
+ } catch (e) {
2478
+ throw new DatabaseError("Failed to delete records from table", {
2479
+ table: table2,
2480
+ where,
2481
+ originalError: formatDbError(e)
2482
+ });
2483
+ }
2484
+ };
2485
+ const count = async (table2, where) => {
2486
+ validateTableName(table2);
2487
+ try {
2488
+ if (!where || Object.keys(where).length === 0) {
2489
+ const result2 = await client.query(
2490
+ `SELECT COUNT(*) as count FROM ${table2}`
2491
+ );
2492
+ return Number(result2.rows[0]?.count) || 0;
2493
+ }
2494
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2495
+ const result = await client.query(
2496
+ `SELECT COUNT(*) as count FROM ${table2} ${whereClause}`,
2497
+ values
2498
+ );
2499
+ return Number(result.rows[0]?.count) || 0;
2500
+ } catch (e) {
2501
+ throw new DatabaseError("Failed to count records in table", {
2502
+ table: table2,
2503
+ where,
2504
+ originalError: formatDbError(e)
2505
+ });
2506
+ }
2507
+ };
2508
+ const table = (tableName) => {
2509
+ return {
2510
+ insert: (data) => insert(tableName, data),
2511
+ get: (data) => get(tableName, data),
2512
+ list: (data) => list(tableName, data)
2513
+ };
2514
+ };
2515
+ const parseTemplate = (strings, ...vars) => {
2516
+ let sql = strings[0];
2517
+ const values = [];
2518
+ for (let i = 0; i < vars.length; i++) {
2519
+ values.push(vars[i]);
2520
+ sql += `$${i + 1}${strings[i + 1]}`;
2521
+ }
2522
+ return { sql, values };
2523
+ };
2524
+ const pluck = async (strings, ...vars) => {
2525
+ const { sql, values } = parseTemplate(strings, ...vars);
2526
+ try {
2527
+ const result = await client.query(sql, values);
2528
+ const firstRow = result.rows[0];
2529
+ if (!firstRow) return null;
2530
+ return Object.values(firstRow)[0];
2531
+ } catch (e) {
2532
+ throw new DatabaseError("Failed to execute pluck query", {
2533
+ sql,
2534
+ values,
2535
+ originalError: formatDbError(e)
2536
+ });
2537
+ }
2538
+ };
2539
+ const single = async (strings, ...vars) => {
2540
+ const { sql, values } = parseTemplate(strings, ...vars);
2541
+ try {
2542
+ const result = await client.query(sql, values);
2543
+ return result.rows[0] || null;
2544
+ } catch (e) {
2545
+ throw new DatabaseError("Failed to execute single query", {
2546
+ sql,
2547
+ values,
2548
+ originalError: formatDbError(e)
2549
+ });
2550
+ }
2551
+ };
2552
+ const many = async (strings, ...vars) => {
2553
+ const { sql, values } = parseTemplate(strings, ...vars);
2554
+ try {
2555
+ const result = await client.query(sql, values);
2556
+ return result.rows;
2557
+ } catch (e) {
2558
+ throw new DatabaseError("Failed to execute many query", {
2559
+ sql,
2560
+ values,
2561
+ originalError: formatDbError(e)
2562
+ });
2563
+ }
2564
+ };
2565
+ const execute = async (strings, ...vars) => {
2566
+ const { sql, values } = parseTemplate(strings, ...vars);
2567
+ try {
2568
+ await client.query(sql, values);
2569
+ } catch (e) {
2570
+ throw new DatabaseError("Failed to execute query", {
2571
+ sql,
2572
+ values,
2573
+ originalError: formatDbError(e)
2574
+ });
2575
+ }
2576
+ };
2577
+ const query = async (sql, ...values) => {
2578
+ const query2 = normalizePostgresRawQuery(sql, values);
2579
+ try {
2580
+ const result = await client.query(query2.sql, query2.values);
2581
+ return {
2582
+ rows: result.rows,
2583
+ rowCount: result.rowCount ?? 0
2584
+ };
2585
+ } catch (e) {
2586
+ throw new DatabaseError("Failed to execute raw query", {
2587
+ sql: query2.sql,
2588
+ values: query2.values,
2589
+ originalError: formatDbError(e)
2590
+ });
2591
+ }
2592
+ };
2593
+ const acquireSession = async () => {
2594
+ const sessionClient = await pool.connect();
2595
+ let released = false;
2596
+ let lost = false;
2597
+ sessionClient.on("error", () => {
2598
+ lost = true;
2599
+ });
2600
+ return {
2601
+ query: async (sql, ...values) => {
2602
+ if (released) {
2603
+ throw new DatabaseError("Session has been released", {});
2604
+ }
2605
+ if (lost) {
2606
+ throw new DatabaseError("Session connection was lost", {});
2607
+ }
2608
+ const normalized = normalizePostgresRawQuery(sql, values);
2609
+ try {
2610
+ const result = await sessionClient.query(
2611
+ normalized.sql,
2612
+ normalized.values
2613
+ );
2614
+ return {
2615
+ rows: result.rows,
2616
+ rowCount: result.rowCount ?? 0
2617
+ };
2618
+ } catch (e) {
2619
+ throw new DatabaseError("Failed to execute session query", {
2620
+ sql: normalized.sql,
2621
+ values: normalized.values,
2622
+ originalError: formatDbError(e)
2623
+ });
2624
+ }
2625
+ },
2626
+ isActive: () => !released && !lost,
2627
+ release: async () => {
2628
+ if (released) return;
2629
+ released = true;
2630
+ if (!lost) {
2631
+ try {
2632
+ await sessionClient.query("ROLLBACK");
2633
+ } catch {
2634
+ }
2635
+ try {
2636
+ await sessionClient.query("SELECT pg_advisory_unlock_all()");
2637
+ } catch {
2638
+ }
2639
+ }
2640
+ sessionClient.release(true);
2641
+ }
2642
+ };
2643
+ };
2644
+ const tableExists2 = async (tableName) => {
2645
+ const result = await client.query(
2646
+ `SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1 AND table_schema = 'public')`,
2647
+ [tableName]
2648
+ );
2649
+ return result.rows[0].exists;
2650
+ };
2651
+ const syncSchema2 = async (schema) => {
2652
+ const commands = schema.trim().split(";").filter((command) => command.trim() !== "");
2653
+ const createIndexRegex = /CREATE (UNIQUE )?INDEX (IF NOT EXISTS )?"?(\w+)"? ON "?(\w+)"?\s*\(([^)]+)\)/i;
2654
+ const indexNames = [];
2655
+ for (const command of commands) {
2656
+ const indexMatch = command.trim().match(createIndexRegex);
2657
+ if (indexMatch) {
2658
+ indexNames.push(indexMatch[3]);
2659
+ }
2660
+ }
2661
+ const existingIndexes = /* @__PURE__ */ new Set();
2662
+ if (indexNames.length > 0) {
2663
+ const result = await client.query(
2664
+ `SELECT indexname FROM pg_indexes
2665
+ WHERE schemaname = 'public' AND indexname = ANY($1::text[])`,
2666
+ [indexNames]
2667
+ );
2668
+ for (const row of result.rows) {
2669
+ existingIndexes.add(row.indexname);
2670
+ }
2671
+ }
2672
+ for (const command of commands) {
2673
+ const trimmedCommand = command.trim();
2674
+ const createTableRegex = /CREATE TABLE (IF NOT EXISTS )?"?(\w+)"? \(([\s\S]+)\)/i;
2675
+ const tableMatch = trimmedCommand.match(createTableRegex);
2676
+ if (tableMatch) {
2677
+ const tableName = tableMatch[2];
2678
+ const columns = tableMatch[3].trim().split(",\n");
2679
+ const exists = await tableExists2(tableName);
2680
+ if (!exists) {
2681
+ await client.query(trimmedCommand);
2682
+ } else {
2683
+ for (const column of columns) {
2684
+ const columnDef = column.trim();
2685
+ const columnMatch = columnDef.match(/"?(\w+)"?\s+(\w+[^,]*)/);
2686
+ if (columnMatch) {
2687
+ const columnName = columnMatch[1];
2688
+ if (columnName.toUpperCase() === "PRIMARY" || columnName.toUpperCase() === "FOREIGN" || columnName.toUpperCase() === "UNIQUE" || columnName.toUpperCase() === "CHECK" || columnName.toUpperCase() === "CONSTRAINT") {
2689
+ continue;
2690
+ }
2691
+ try {
2692
+ const columnExists = await client.query(
2693
+ `SELECT EXISTS (
2694
+ SELECT 1 FROM information_schema.columns
2695
+ WHERE table_name = $1
2696
+ AND column_name = $2
2697
+ AND table_schema = 'public'
2698
+ )`,
2699
+ [tableName, columnName]
2700
+ );
2701
+ if (!columnExists.rows[0].exists) {
2702
+ const alterCommand = `ALTER TABLE "${tableName}" ADD COLUMN ${columnDef}`;
2703
+ await client.query(alterCommand);
2704
+ }
2705
+ } catch (error) {
2706
+ console.error(
2707
+ `Error adding column ${columnName} to ${tableName}:`,
2708
+ error
2709
+ );
2710
+ }
2711
+ }
2712
+ }
2713
+ }
2714
+ continue;
2715
+ }
2716
+ const indexMatch = trimmedCommand.match(createIndexRegex);
2717
+ if (indexMatch) {
2718
+ const indexName = indexMatch[3];
2719
+ const indexTableName = indexMatch[4];
2720
+ if (!existingIndexes.has(indexName)) {
2721
+ try {
2722
+ await client.query(trimmedCommand);
2723
+ existingIndexes.add(indexName);
2724
+ } catch (error) {
2725
+ console.warn(
2726
+ `Warning: Failed to create index ${indexName} on ${indexTableName}:`,
2727
+ error instanceof Error ? error.message : String(error)
2728
+ );
2729
+ }
2730
+ }
2731
+ continue;
2732
+ }
2733
+ if (trimmedCommand.toUpperCase().startsWith("CREATE ") && !trimmedCommand.toUpperCase().includes("CREATE TABLE")) {
2734
+ try {
2735
+ await client.query(trimmedCommand);
2736
+ } catch (error) {
2737
+ console.warn(
2738
+ `Warning: DDL statement may have failed:`,
2739
+ error instanceof Error ? error.message : String(error)
2740
+ );
2741
+ }
2742
+ }
2743
+ }
2744
+ };
2745
+ const transaction = async (callback) => {
2746
+ const txClient = await client.connect();
2747
+ try {
2748
+ await txClient.query("BEGIN");
2749
+ const txDb = {
2750
+ url,
2751
+ client: txClient,
2752
+ insert: async (table2, data) => {
2753
+ if (Array.isArray(data)) {
2754
+ const keys2 = Object.keys(data[0]);
2755
+ const placeholders2 = data.map(
2756
+ (_, i) => `(${keys2.map((_2, j) => `$${i * keys2.length + j + 1}`).join(", ")})`
2757
+ ).join(", ");
2758
+ const query3 = `INSERT INTO ${table2} (${keys2.join(", ")}) VALUES ${placeholders2}`;
2759
+ const values2 = data.reduce(
2760
+ (acc, row) => acc.concat(Object.values(row)),
2761
+ []
2762
+ );
2763
+ const result3 = await txClient.query(query3, values2);
2764
+ return { operation: "insert", affected: result3.rowCount ?? 0 };
2765
+ }
2766
+ const keys = Object.keys(data);
2767
+ const values = Object.values(data);
2768
+ const placeholders = keys.map((_, i) => `$${i + 1}`).join(", ");
2769
+ const query2 = `INSERT INTO ${table2} (${keys.join(", ")}) VALUES (${placeholders})`;
2770
+ const result2 = await txClient.query(query2, values);
2771
+ return { operation: "insert", affected: result2.rowCount ?? 0 };
2772
+ },
2773
+ get: async (table2, where) => {
2774
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2775
+ if (!whereClause) {
2776
+ throw new DatabaseError(
2777
+ "GET requires at least one WHERE condition to prevent returning an arbitrary record",
2778
+ { table: table2 }
2779
+ );
2780
+ }
2781
+ const query2 = `SELECT * FROM ${table2} ${whereClause}`;
2782
+ const result2 = await txClient.query(query2, values);
2783
+ return result2.rows[0] || null;
2784
+ },
2785
+ list: async (table2, where) => {
2786
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2787
+ const query2 = `SELECT * FROM ${table2} ${whereClause}`;
2788
+ const result2 = await txClient.query(query2, values);
2789
+ return result2.rows;
2790
+ },
2791
+ update: async (table2, where, data) => {
2792
+ const keys = Object.keys(data);
2793
+ const values = Object.values(data);
2794
+ const setClause = keys.map((key, i) => `${key} = $${i + 1}`).join(", ");
2795
+ const { sql: whereClause, values: whereValues } = buildWhere(
2796
+ where,
2797
+ values.length + 1,
2798
+ "postgres"
2799
+ );
2800
+ if (!whereClause) {
2801
+ throw new DatabaseError(
2802
+ "UPDATE requires at least one WHERE condition to prevent accidental update of all records",
2803
+ { table: table2 }
2804
+ );
2805
+ }
2806
+ const sql = `UPDATE ${table2} SET ${setClause} ${whereClause}`;
2807
+ const result2 = await txClient.query(sql, [...values, ...whereValues]);
2808
+ return { operation: "update", affected: result2.rowCount ?? 0 };
2809
+ },
2810
+ upsert: async (table2, conflictColumns, data, options2) => executePostgresUpsert(
2811
+ txClient,
2812
+ table2,
2813
+ conflictColumns,
2814
+ data,
2815
+ options2,
2816
+ false
2817
+ ),
2818
+ getOrInsert: async (table2, where, data) => {
2819
+ const result2 = await txDb.get(table2, where);
2820
+ if (result2) return result2;
2821
+ await txDb.insert(table2, data);
2822
+ const inserted = await txDb.get(table2, where);
2823
+ if (!inserted) {
2824
+ throw new DatabaseError("Failed to insert and retrieve record", {
2825
+ table: table2,
2826
+ where,
2827
+ data
2828
+ });
2829
+ }
2830
+ return inserted;
2831
+ },
2832
+ delete: async (table2, where) => {
2833
+ validateTableName(table2);
2834
+ const keys = Object.keys(where);
2835
+ if (keys.length === 0) {
2836
+ throw new DatabaseError(
2837
+ "DELETE requires at least one WHERE condition to prevent accidental deletion of all records",
2838
+ { table: table2 }
2839
+ );
2840
+ }
2841
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2842
+ const result2 = await txClient.query(
2843
+ `DELETE FROM ${table2} ${whereClause}`,
2844
+ values
2845
+ );
2846
+ return { operation: "delete", affected: result2.rowCount ?? 0 };
2847
+ },
2848
+ count: async (table2, where) => {
2849
+ validateTableName(table2);
2850
+ if (!where || Object.keys(where).length === 0) {
2851
+ const result3 = await txClient.query(
2852
+ `SELECT COUNT(*) as count FROM ${table2}`
2853
+ );
2854
+ return Number(result3.rows[0]?.count) || 0;
2855
+ }
2856
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2857
+ const result2 = await txClient.query(
2858
+ `SELECT COUNT(*) as count FROM ${table2} ${whereClause}`,
2859
+ values
2860
+ );
2861
+ return Number(result2.rows[0]?.count) || 0;
2862
+ },
2863
+ table: (tableName) => ({
2864
+ insert: (data) => txDb.insert(tableName, data),
2865
+ get: (data) => txDb.get(tableName, data),
2866
+ list: (data) => txDb.list(tableName, data)
2867
+ }),
2868
+ many: async (strings, ...vars) => {
2869
+ const { sql, values } = parseTemplate(strings, ...vars);
2870
+ const result2 = await txClient.query(sql, values);
2871
+ return result2.rows;
2872
+ },
2873
+ single: async (strings, ...vars) => {
2874
+ const { sql, values } = parseTemplate(strings, ...vars);
2875
+ const result2 = await txClient.query(sql, values);
2876
+ return result2.rows[0] || null;
2877
+ },
2878
+ pluck: async (strings, ...vars) => {
2879
+ const { sql, values } = parseTemplate(strings, ...vars);
2880
+ const result2 = await txClient.query(sql, values);
2881
+ const firstRow = result2.rows[0];
2882
+ if (!firstRow) return null;
2883
+ return Object.values(firstRow)[0];
2884
+ },
2885
+ execute: async (strings, ...vars) => {
2886
+ const { sql, values } = parseTemplate(strings, ...vars);
2887
+ await txClient.query(sql, values);
2888
+ },
2889
+ query: async (sql, ...values) => {
2890
+ const query2 = normalizePostgresRawQuery(sql, values);
2891
+ const result2 = await txClient.query(query2.sql, query2.values);
2892
+ return {
2893
+ rows: result2.rows,
2894
+ rowCount: result2.rowCount ?? 0
2895
+ };
2896
+ },
2897
+ oo: async (strings, ...vars) => {
2898
+ const { sql, values } = parseTemplate(strings, ...vars);
2899
+ const result2 = await txClient.query(sql, values);
2900
+ return result2.rows;
2901
+ },
2902
+ oO: async (strings, ...vars) => {
2903
+ const { sql, values } = parseTemplate(strings, ...vars);
2904
+ const result2 = await txClient.query(sql, values);
2905
+ return result2.rows[0] || null;
2906
+ },
2907
+ ox: async (strings, ...vars) => {
2908
+ const { sql, values } = parseTemplate(strings, ...vars);
2909
+ const result2 = await txClient.query(sql, values);
2910
+ const firstRow = result2.rows[0];
2911
+ if (!firstRow) return null;
2912
+ return Object.values(firstRow)[0];
2913
+ },
2914
+ xx: async (strings, ...vars) => {
2915
+ const { sql, values } = parseTemplate(strings, ...vars);
2916
+ await txClient.query(sql, values);
2917
+ },
2918
+ tableExists: tableExists2,
2919
+ syncSchema: syncSchema2,
2920
+ transaction
2921
+ };
2922
+ const result = await callback(txDb);
2923
+ await txClient.query("COMMIT");
2924
+ return result;
2925
+ } catch (error) {
2926
+ await txClient.query("ROLLBACK");
2927
+ throw error;
2928
+ } finally {
2929
+ txClient.release();
2930
+ }
2931
+ };
2932
+ const beginTransaction = async () => {
2933
+ const txClient = await client.connect();
2934
+ await txClient.query("BEGIN");
2935
+ let active = true;
2936
+ const commit = async () => {
2937
+ if (!active) {
2938
+ throw new DatabaseError("Transaction already ended", {});
2939
+ }
2940
+ await txClient.query("COMMIT");
2941
+ active = false;
2942
+ txClient.release();
2943
+ };
2944
+ const rollback = async () => {
2945
+ if (!active) {
2946
+ throw new DatabaseError("Transaction already ended", {});
2947
+ }
2948
+ await txClient.query("ROLLBACK");
2949
+ active = false;
2950
+ txClient.release();
2951
+ };
2952
+ const isActive = () => active;
2953
+ const txHandle = {
2954
+ url,
2955
+ client: txClient,
2956
+ insert: async (table2, data) => {
2957
+ if (Array.isArray(data)) {
2958
+ const keys2 = Object.keys(data[0]);
2959
+ const placeholders2 = data.map(
2960
+ (_, i) => `(${keys2.map((_2, j) => `$${i * keys2.length + j + 1}`).join(", ")})`
2961
+ ).join(", ");
2962
+ const query3 = `INSERT INTO ${table2} (${keys2.join(", ")}) VALUES ${placeholders2}`;
2963
+ const values2 = data.reduce(
2964
+ (acc, row) => acc.concat(Object.values(row)),
2965
+ []
2966
+ );
2967
+ const result2 = await txClient.query(query3, values2);
2968
+ return { operation: "insert", affected: result2.rowCount ?? 0 };
2969
+ }
2970
+ const keys = Object.keys(data);
2971
+ const values = Object.values(data);
2972
+ const placeholders = keys.map((_, i) => `$${i + 1}`).join(", ");
2973
+ const query2 = `INSERT INTO ${table2} (${keys.join(", ")}) VALUES (${placeholders})`;
2974
+ const result = await txClient.query(query2, values);
2975
+ return { operation: "insert", affected: result.rowCount ?? 0 };
2976
+ },
2977
+ get: async (table2, where) => {
2978
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2979
+ if (!whereClause) {
2980
+ throw new DatabaseError(
2981
+ "GET requires at least one WHERE condition to prevent returning an arbitrary record",
2982
+ { table: table2 }
2983
+ );
2984
+ }
2985
+ const query2 = `SELECT * FROM ${table2} ${whereClause}`;
2986
+ const result = await txClient.query(query2, values);
2987
+ return result.rows[0] || null;
2988
+ },
2989
+ list: async (table2, where) => {
2990
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
2991
+ const query2 = `SELECT * FROM ${table2} ${whereClause}`;
2992
+ const result = await txClient.query(query2, values);
2993
+ return result.rows;
2994
+ },
2995
+ update: async (table2, where, data) => {
2996
+ const keys = Object.keys(data);
2997
+ const values = Object.values(data);
2998
+ const setClause = keys.map((key, i) => `${key} = $${i + 1}`).join(", ");
2999
+ const { sql: whereClause, values: whereValues } = buildWhere(
3000
+ where,
3001
+ values.length + 1,
3002
+ "postgres"
3003
+ );
3004
+ if (!whereClause) {
3005
+ throw new DatabaseError(
3006
+ "UPDATE requires at least one WHERE condition to prevent accidental update of all records",
3007
+ { table: table2 }
3008
+ );
3009
+ }
3010
+ const sql = `UPDATE ${table2} SET ${setClause} ${whereClause}`;
3011
+ const result = await txClient.query(sql, [...values, ...whereValues]);
3012
+ return { operation: "update", affected: result.rowCount ?? 0 };
3013
+ },
3014
+ upsert: async (table2, conflictColumns, data, options2) => executePostgresUpsert(
3015
+ txClient,
3016
+ table2,
3017
+ conflictColumns,
3018
+ data,
3019
+ options2,
3020
+ false
3021
+ ),
3022
+ getOrInsert: async (table2, where, data) => {
3023
+ const result = await txHandle.get(table2, where);
3024
+ if (result) return result;
3025
+ await txHandle.insert(table2, data);
3026
+ const inserted = await txHandle.get(table2, where);
3027
+ if (!inserted) {
3028
+ throw new DatabaseError("Failed to insert and retrieve record", {
3029
+ table: table2,
3030
+ where,
3031
+ data
3032
+ });
3033
+ }
3034
+ return inserted;
3035
+ },
3036
+ delete: async (table2, where) => {
3037
+ validateTableName(table2);
3038
+ const keys = Object.keys(where);
3039
+ if (keys.length === 0) {
3040
+ throw new DatabaseError(
3041
+ "DELETE requires at least one WHERE condition to prevent accidental deletion of all records",
3042
+ { table: table2 }
3043
+ );
3044
+ }
3045
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
3046
+ const result = await txClient.query(
3047
+ `DELETE FROM ${table2} ${whereClause}`,
3048
+ values
3049
+ );
3050
+ return { operation: "delete", affected: result.rowCount ?? 0 };
3051
+ },
3052
+ count: async (table2, where) => {
3053
+ validateTableName(table2);
3054
+ if (!where || Object.keys(where).length === 0) {
3055
+ const result2 = await txClient.query(
3056
+ `SELECT COUNT(*) as count FROM ${table2}`
3057
+ );
3058
+ return Number(result2.rows[0]?.count) || 0;
3059
+ }
3060
+ const { sql: whereClause, values } = buildWhere(where, 1, "postgres");
3061
+ const result = await txClient.query(
3062
+ `SELECT COUNT(*) as count FROM ${table2} ${whereClause}`,
3063
+ values
3064
+ );
3065
+ return Number(result.rows[0]?.count) || 0;
3066
+ },
3067
+ table: (tableName) => ({
3068
+ insert: (data) => txHandle.insert(tableName, data),
3069
+ get: (data) => txHandle.get(tableName, data),
3070
+ list: (data) => txHandle.list(tableName, data)
3071
+ }),
3072
+ many: async (strings, ...vars) => {
3073
+ const { sql, values } = parseTemplate(strings, ...vars);
3074
+ const result = await txClient.query(sql, values);
3075
+ return result.rows;
3076
+ },
3077
+ single: async (strings, ...vars) => {
3078
+ const { sql, values } = parseTemplate(strings, ...vars);
3079
+ const result = await txClient.query(sql, values);
3080
+ return result.rows[0] || null;
3081
+ },
3082
+ pluck: async (strings, ...vars) => {
3083
+ const { sql, values } = parseTemplate(strings, ...vars);
3084
+ const result = await txClient.query(sql, values);
3085
+ const firstRow = result.rows[0];
3086
+ if (!firstRow) return null;
3087
+ return Object.values(firstRow)[0];
3088
+ },
3089
+ execute: async (strings, ...vars) => {
3090
+ const { sql, values } = parseTemplate(strings, ...vars);
3091
+ await txClient.query(sql, values);
3092
+ },
3093
+ query: async (sql, ...values) => {
3094
+ const query2 = normalizePostgresRawQuery(sql, values);
3095
+ const result = await txClient.query(query2.sql, query2.values);
3096
+ return {
3097
+ rows: result.rows,
3098
+ rowCount: result.rowCount ?? 0
3099
+ };
3100
+ },
3101
+ oo: async (strings, ...vars) => {
3102
+ const { sql, values } = parseTemplate(strings, ...vars);
3103
+ const result = await txClient.query(sql, values);
3104
+ return result.rows;
3105
+ },
3106
+ oO: async (strings, ...vars) => {
3107
+ const { sql, values } = parseTemplate(strings, ...vars);
3108
+ const result = await txClient.query(sql, values);
3109
+ return result.rows[0] || null;
3110
+ },
3111
+ ox: async (strings, ...vars) => {
3112
+ const { sql, values } = parseTemplate(strings, ...vars);
3113
+ const result = await txClient.query(sql, values);
3114
+ const firstRow = result.rows[0];
3115
+ if (!firstRow) return null;
3116
+ return Object.values(firstRow)[0];
3117
+ },
3118
+ xx: async (strings, ...vars) => {
3119
+ const { sql, values } = parseTemplate(strings, ...vars);
3120
+ await txClient.query(sql, values);
3121
+ },
3122
+ tableExists: tableExists2,
3123
+ syncSchema: syncSchema2,
3124
+ transaction,
3125
+ commit,
3126
+ rollback,
3127
+ isActive
3128
+ };
3129
+ return txHandle;
3130
+ };
3131
+ const oo = many;
3132
+ const oO = single;
3133
+ const ox = pluck;
3134
+ const xx = execute;
3135
+ const initializeSchemas = async (options2) => {
3136
+ const schemaManager = new DatabaseSchemaManager();
3137
+ const currentDb = {
3138
+ url,
3139
+ client,
3140
+ insert,
3141
+ update,
3142
+ upsert,
3143
+ get,
3144
+ getOrInsert,
3145
+ delete: deleteRecords,
3146
+ count,
3147
+ list,
3148
+ table,
3149
+ many,
3150
+ single,
3151
+ pluck,
3152
+ execute,
3153
+ query,
3154
+ oo,
3155
+ oO,
3156
+ ox,
3157
+ xx,
3158
+ tableExists: tableExists2,
3159
+ syncSchema: syncSchema2,
3160
+ transaction
3161
+ };
3162
+ await schemaManager.initializeSchemas(currentDb, options2);
3163
+ };
3164
+ const getTableSchema = async (table2) => {
3165
+ validateTableName(table2);
3166
+ try {
3167
+ const exists = await tableExists2(table2);
3168
+ if (!exists) {
3169
+ return null;
3170
+ }
3171
+ const columnRows = await many`
3172
+ SELECT
3173
+ column_name,
3174
+ data_type,
3175
+ is_nullable,
3176
+ column_default
3177
+ FROM information_schema.columns
3178
+ WHERE table_name = ${table2}
3179
+ AND table_schema = 'public'
3180
+ ORDER BY ordinal_position
3181
+ `;
3182
+ const pkRows = await many`
3183
+ SELECT kcu.column_name
3184
+ FROM information_schema.table_constraints tc
3185
+ JOIN information_schema.key_column_usage kcu
3186
+ ON tc.constraint_name = kcu.constraint_name
3187
+ AND tc.table_schema = kcu.table_schema
3188
+ WHERE tc.table_name = ${table2}
3189
+ AND tc.table_schema = 'public'
3190
+ AND tc.constraint_type = 'PRIMARY KEY'
3191
+ `;
3192
+ const pkColumns = new Set(pkRows.map((row) => row.column_name));
3193
+ const columns = {};
3194
+ for (const row of columnRows) {
3195
+ const colName = row.column_name;
3196
+ columns[colName] = {
3197
+ type: row.data_type,
3198
+ primaryKey: pkColumns.has(colName),
3199
+ notNull: row.is_nullable === "NO",
3200
+ defaultValue: row.column_default
3201
+ };
3202
+ }
3203
+ const indexRows = await many`
3204
+ SELECT
3205
+ indexname,
3206
+ indexdef
3207
+ FROM pg_indexes
3208
+ WHERE tablename = ${table2}
3209
+ AND schemaname = 'public'
3210
+ AND indexname NOT LIKE '%_pkey'
3211
+ `;
3212
+ const indexes = [];
3213
+ for (const row of indexRows) {
3214
+ const indexName = row.indexname;
3215
+ const indexDef = row.indexdef;
3216
+ const colMatch = indexDef.match(/\(([^)]+)\)/);
3217
+ const indexColumns = colMatch ? colMatch[1].split(",").map((col) => col.trim()) : [];
3218
+ const isUnique = indexDef.toUpperCase().includes("UNIQUE");
3219
+ indexes.push({
3220
+ name: indexName,
3221
+ columns: indexColumns,
3222
+ unique: isUnique
3223
+ });
3224
+ }
3225
+ const fkRows = await many`
3226
+ SELECT
3227
+ kcu.column_name,
3228
+ ccu.table_name AS foreign_table_name,
3229
+ ccu.column_name AS foreign_column_name,
3230
+ rc.delete_rule,
3231
+ rc.update_rule
3232
+ FROM information_schema.table_constraints AS tc
3233
+ JOIN information_schema.key_column_usage AS kcu
3234
+ ON tc.constraint_name = kcu.constraint_name
3235
+ AND tc.table_schema = kcu.table_schema
3236
+ JOIN information_schema.constraint_column_usage AS ccu
3237
+ ON ccu.constraint_name = tc.constraint_name
3238
+ AND ccu.table_schema = tc.table_schema
3239
+ JOIN information_schema.referential_constraints AS rc
3240
+ ON rc.constraint_name = tc.constraint_name
3241
+ AND rc.constraint_schema = tc.table_schema
3242
+ WHERE tc.constraint_type = 'FOREIGN KEY'
3243
+ AND tc.table_name = ${table2}
3244
+ AND tc.table_schema = 'public'
3245
+ `;
3246
+ const foreignKeys = [];
3247
+ for (const fkRow of fkRows) {
3248
+ foreignKeys.push({
3249
+ column: fkRow.column_name,
3250
+ referencesTable: fkRow.foreign_table_name,
3251
+ referencesColumn: fkRow.foreign_column_name,
3252
+ onDelete: fkRow.delete_rule,
3253
+ onUpdate: fkRow.update_rule
3254
+ });
3255
+ }
3256
+ return {
3257
+ tableName: table2,
3258
+ columns,
3259
+ indexes,
3260
+ foreignKeys
3261
+ };
3262
+ } catch (e) {
3263
+ throw new DatabaseError("Failed to retrieve table schema", {
3264
+ table: table2,
3265
+ originalError: formatDbError(e)
3266
+ });
3267
+ }
3268
+ };
3269
+ const alterTable = {
3270
+ /**
3271
+ * Adds a new column to an existing table
3272
+ *
3273
+ * @param table - Table name
3274
+ * @param column - Column definition with name
3275
+ * @returns Promise that resolves when column is added
3276
+ * @throws Error if the alter operation fails
3277
+ */
3278
+ addColumn: async (table2, column) => {
3279
+ validateTableName(table2);
3280
+ validateColumnName$1(column.name);
3281
+ try {
3282
+ const sql = generateAddColumnStatement(table2, column, "postgres");
3283
+ await client.query(sql);
3284
+ } catch (e) {
3285
+ throw new DatabaseError("Failed to add column to table", {
3286
+ table: table2,
3287
+ column: column.name,
3288
+ originalError: formatDbError(e)
3289
+ });
3290
+ }
3291
+ },
3292
+ /**
3293
+ * Adds a new index to an existing table
3294
+ *
3295
+ * @param table - Table name
3296
+ * @param index - Index definition
3297
+ * @returns Promise that resolves when index is created
3298
+ * @throws Error if the create index operation fails
3299
+ */
3300
+ addIndex: async (table2, index2) => {
3301
+ validateTableName(table2);
3302
+ validateIndexName(index2.name);
3303
+ for (const col of index2.columns) {
3304
+ validateColumnName$1(col);
3305
+ }
3306
+ try {
3307
+ const sql = generateCreateIndexStatement(table2, index2);
3308
+ await client.query(sql);
3309
+ } catch (e) {
3310
+ throw new DatabaseError("Failed to create index on table", {
3311
+ table: table2,
3312
+ index: index2.name,
3313
+ originalError: formatDbError(e)
3314
+ });
3315
+ }
3316
+ }
3317
+ };
3318
+ const db = {
3319
+ url,
3320
+ client,
3321
+ insert,
3322
+ update,
3323
+ upsert,
3324
+ get,
3325
+ getOrInsert,
3326
+ delete: deleteRecords,
3327
+ count,
3328
+ list,
3329
+ table,
3330
+ many,
3331
+ single,
3332
+ pluck,
3333
+ execute,
3334
+ query,
3335
+ oo,
3336
+ oO,
3337
+ ox,
3338
+ xx,
3339
+ tableExists: tableExists2,
3340
+ syncSchema: syncSchema2,
3341
+ initializeSchemas,
3342
+ transaction,
3343
+ beginTransaction,
3344
+ acquireSession,
3345
+ getTableSchema,
3346
+ alterTable,
3347
+ vector: createVectorCapabilities(pool)
3348
+ };
3349
+ const finalCacheKey = cacheKey || url;
3350
+ connectionCache.set(finalCacheKey, db);
3351
+ return db;
3352
+ }
3353
+ const postgres = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
3354
+ __proto__: null,
3355
+ clearPostgresConnectionCache,
3356
+ getDatabase: getDatabase$1
3357
+ }, Symbol.toStringTag, { value: "Module" }));
3358
+ function isDatabaseInstance(value) {
3359
+ return value && typeof value === "object" && typeof value.client !== "undefined" && typeof value.insert === "function" && typeof value.get === "function" && typeof value.query === "function";
3360
+ }
3361
+ async function getDatabase(options = {}) {
3362
+ if (isDatabaseInstance(options)) {
3363
+ return options;
3364
+ }
3365
+ Object.assign(
3366
+ options,
3367
+ loadEnvConfig(options, {
3368
+ packageName: "sql",
3369
+ schema: {
3370
+ type: "string",
3371
+ url: "string",
3372
+ host: "string",
3373
+ port: "number",
3374
+ database: "string",
3375
+ user: "string",
3376
+ password: "string"
3377
+ }
3378
+ })
3379
+ );
3380
+ if (!options.type && (options.url?.startsWith("file:") || options.url === ":memory:")) {
3381
+ options.type = "sqlite";
3382
+ }
3383
+ if (options.type === "postgres") {
3384
+ const postgres$1 = await Promise.resolve().then(() => postgres);
3385
+ return postgres$1.getDatabase(options);
3386
+ }
3387
+ if (options.type === "sqlite") {
3388
+ const sqlite = await import("./sqlite-DkCGjXmu.js");
3389
+ return sqlite.getDatabase(options);
3390
+ }
3391
+ if (options.type === "duckdb") {
3392
+ const duckdb = await import("./duckdb-Djx8WKZE.js");
3393
+ return duckdb.getDatabase(options);
3394
+ }
3395
+ if (options.type === "json") {
3396
+ const json$1 = await Promise.resolve().then(() => json);
3397
+ return json$1.getDatabase(options);
3398
+ }
3399
+ throw new Error("Invalid database type");
3400
+ }
3401
+ async function syncSchema(options) {
3402
+ const { db, schema } = options;
3403
+ if (!db || !schema) {
3404
+ throw new Error("db and schema are required");
3405
+ }
3406
+ if (db.syncSchema) {
3407
+ await db.syncSchema(schema);
3408
+ } else {
3409
+ throw new Error("Database adapter does not support schema synchronization");
3410
+ }
3411
+ }
3412
+ async function tableExists(db, tableName) {
3413
+ return db.tableExists(tableName);
3414
+ }
3415
+ function escapeSqlValue(value) {
3416
+ if (value === null) {
3417
+ return "NULL";
3418
+ }
3419
+ if (value instanceof Date) {
3420
+ return `'${value.toISOString()}'`;
3421
+ }
3422
+ if (typeof value === "number") {
3423
+ return value.toString();
3424
+ }
3425
+ if (typeof value === "boolean") {
3426
+ return value ? "1" : "0";
3427
+ }
3428
+ return `'${String(value).replace(/'/g, "''")}'`;
3429
+ }
3430
+ function validateColumnName(column) {
3431
+ if (!/^[a-zA-Z0-9_.]+$/.test(column)) {
3432
+ throw new Error(`Invalid column name: ${column}`);
3433
+ }
3434
+ return column;
3435
+ }
3436
+ const index = {
3437
+ getDatabase,
3438
+ syncSchema,
3439
+ tableExists,
3440
+ buildWhere
3441
+ };
3442
+ const PACKAGE_VERSION_INITIALIZED = true;
3443
+ export {
3444
+ DatabaseSchemaManager as D,
3445
+ PACKAGE_VERSION_INITIALIZED as P,
3446
+ validateIndexName as a,
3447
+ validateColumnName$1 as b,
3448
+ generateAddColumnStatement as c,
3449
+ buildWhere as d,
3450
+ convertUniqueIndexesToInlineConstraints as e,
3451
+ formatDbError as f,
3452
+ generateCreateIndexStatement as g,
3453
+ checkExpectedTables as h,
3454
+ checkRelationship as i,
3455
+ checkUniqueColumn as j,
3456
+ clearConnectionCache as k,
3457
+ clearPostgresConnectionCache as l,
3458
+ index as m,
3459
+ escapeSqlValue as n,
3460
+ getDatabase as o,
3461
+ runDoctor as p,
3462
+ validateColumnName as q,
3463
+ resolveSchemas as r,
3464
+ syncSchema as s,
3465
+ tableExists as t,
3466
+ validateTableName as v
3467
+ };
3468
+ //# sourceMappingURL=index-DL-5nB7l.js.map