@happyvertical/sql 0.80.0 → 0.80.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/backup/index.js +210 -188
  2. package/dist/backup/index.js.map +1 -1
  3. package/dist/chunks/alter-utils-K7tiuj5L.js +135 -0
  4. package/dist/chunks/alter-utils-K7tiuj5L.js.map +1 -0
  5. package/dist/chunks/duckdb-DykBBzd6.js +1056 -0
  6. package/dist/chunks/duckdb-DykBBzd6.js.map +1 -0
  7. package/dist/chunks/duckdb-schema-utils-B-XBP8QF.js +115 -0
  8. package/dist/chunks/duckdb-schema-utils-B-XBP8QF.js.map +1 -0
  9. package/dist/chunks/postgres-cli-DzZcXSIG.js +272 -0
  10. package/dist/chunks/postgres-cli-DzZcXSIG.js.map +1 -0
  11. package/dist/chunks/schema-manager-a8RlxIE2.js +450 -0
  12. package/dist/chunks/schema-manager-a8RlxIE2.js.map +1 -0
  13. package/dist/chunks/sqlite-YgU9LCVy.js +1045 -0
  14. package/dist/chunks/sqlite-YgU9LCVy.js.map +1 -0
  15. package/dist/chunks/sqlite-native-BGtpx7Ro.js +902 -0
  16. package/dist/chunks/sqlite-native-BGtpx7Ro.js.map +1 -0
  17. package/dist/cli/claude-context.js +17 -17
  18. package/dist/cli/claude-context.js.map +1 -1
  19. package/dist/index.js +3244 -35
  20. package/dist/index.js.map +1 -1
  21. package/package.json +5 -5
  22. package/dist/chunks/duckdb-B6m78QdL.js +0 -924
  23. package/dist/chunks/duckdb-B6m78QdL.js.map +0 -1
  24. package/dist/chunks/index-CzdYvRFp.js +0 -3692
  25. package/dist/chunks/index-CzdYvRFp.js.map +0 -1
  26. package/dist/chunks/postgres-cli-Cr6bBZ3q.js +0 -209
  27. package/dist/chunks/postgres-cli-Cr6bBZ3q.js.map +0 -1
  28. package/dist/chunks/sqlite-CPCNeJIH.js +0 -937
  29. package/dist/chunks/sqlite-CPCNeJIH.js.map +0 -1
  30. package/dist/chunks/sqlite-native-DqVPKNUu.js +0 -1129
  31. package/dist/chunks/sqlite-native-DqVPKNUu.js.map +0 -1
@@ -0,0 +1,902 @@
1
+ import { n as buildWhere, r as formatDbError, t as DatabaseSchemaManager } from "./schema-manager-a8RlxIE2.js";
2
+ import { a as validateTableName, i as validateIndexName, n as generateCreateIndexStatement, r as validateColumnName, t as generateAddColumnStatement } from "./alter-utils-K7tiuj5L.js";
3
+ import { resolveSchemas } from "../index.js";
4
+ import { DatabaseError } from "@happyvertical/utils";
5
+ import { resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { createRequire } from "node:module";
8
+ //#region src/sqlite-native.ts
9
+ var nativeMemoryConnectionCache = /* @__PURE__ */ new Map();
10
+ var nativePendingConnections = /* @__PURE__ */ new Map();
11
+ var nativeNullAwareUpsertLocks = /* @__PURE__ */ new Map();
12
+ var nodeSqliteModulePromise = null;
13
+ var requireOptionalPackage = createRequire(import.meta.url);
14
+ function getCapabilityOptions(value) {
15
+ return typeof value === "object" && value ? value : {};
16
+ }
17
+ function resolveNativeLocation(url = ":memory:") {
18
+ if (url.startsWith("libsql://") || url.startsWith("http://") || url.startsWith("https://")) throw new DatabaseError("SQLite native capabilities are only supported for local SQLite databases", {
19
+ url,
20
+ hint: "Use a local file path, 'file:...' URL, or ':memory:' when enabling SQLite capabilities."
21
+ });
22
+ if (url === ":memory:" || url.startsWith("file::memory:")) return {
23
+ location: url,
24
+ filePath: null,
25
+ isMemory: true
26
+ };
27
+ if (url.startsWith("file://")) {
28
+ const filePath = fileURLToPath(new URL(url));
29
+ return {
30
+ location: filePath,
31
+ filePath,
32
+ isMemory: false
33
+ };
34
+ }
35
+ if (url.startsWith("file:")) {
36
+ const path = url.slice(5);
37
+ if (!path || path.includes("?")) throw new DatabaseError("SQLite native capabilities do not support file: URLs with query parameters", { url });
38
+ const filePath = resolve(path);
39
+ return {
40
+ location: filePath,
41
+ filePath,
42
+ isMemory: false
43
+ };
44
+ }
45
+ const filePath = resolve(url);
46
+ return {
47
+ location: filePath,
48
+ filePath,
49
+ isMemory: false
50
+ };
51
+ }
52
+ async function loadNodeSqlite() {
53
+ nodeSqliteModulePromise ??= importNodeSqlite().catch((error) => {
54
+ nodeSqliteModulePromise = null;
55
+ throw error;
56
+ });
57
+ return nodeSqliteModulePromise;
58
+ }
59
+ async function importNodeSqlite() {
60
+ const originalEmitWarning = process.emitWarning;
61
+ try {
62
+ process.emitWarning = function suppressNodeSqliteExperimentalWarning(warning, ...args) {
63
+ if (args[0] === "ExperimentalWarning" && String(warning).includes("SQLite is an experimental feature")) return;
64
+ return originalEmitWarning.call(process, warning, ...args);
65
+ };
66
+ return await import(
67
+ /* @vite-ignore */
68
+ "node:sqlite"
69
+ );
70
+ } catch (error) {
71
+ throw new DatabaseError("Node native SQLite is not available", {
72
+ originalError: formatDbError(error),
73
+ hint: "SQLite native capabilities require a Node.js runtime with node:sqlite support."
74
+ });
75
+ } finally {
76
+ process.emitWarning = originalEmitWarning;
77
+ }
78
+ }
79
+ async function loadSqliteVectorExtension(database) {
80
+ try {
81
+ const module = requireOptionalPackage("@sqliteai/sqlite-vector");
82
+ const getExtensionPath = module.getExtensionPath ?? module.default?.getExtensionPath;
83
+ if (typeof getExtensionPath !== "function") throw new Error("getExtensionPath export was not found");
84
+ database.enableLoadExtension(true);
85
+ database.loadExtension(getExtensionPath());
86
+ } catch (error) {
87
+ throw new DatabaseError("Failed to load optional SQLite vector capability: install @sqliteai/sqlite-vector", {
88
+ packageName: "@sqliteai/sqlite-vector",
89
+ originalError: formatDbError(error),
90
+ hint: "Install @sqliteai/sqlite-vector to use SQLite vector capabilities."
91
+ });
92
+ } finally {
93
+ try {
94
+ database.enableLoadExtension(false);
95
+ } catch (_error) {}
96
+ }
97
+ }
98
+ async function openHonkerDatabase(filePath, options) {
99
+ try {
100
+ const module = requireOptionalPackage("@russellthehippo/honker-node");
101
+ const open = module.open ?? module.default?.open;
102
+ if (typeof open !== "function") throw new Error("open export was not found");
103
+ return open(filePath, options.maxReaders, options.watcherBackend);
104
+ } catch (error) {
105
+ throw new DatabaseError("Failed to load optional SQLite notification capability: install @russellthehippo/honker-node", {
106
+ packageName: "@russellthehippo/honker-node",
107
+ originalError: formatDbError(error),
108
+ hint: "Install @russellthehippo/honker-node to use SQLite notification capabilities."
109
+ });
110
+ }
111
+ }
112
+ function toPlainRow(row) {
113
+ return Object.fromEntries(Object.entries(row));
114
+ }
115
+ function getErrorMessage(error) {
116
+ return error instanceof Error ? error.message : void 0;
117
+ }
118
+ function firstSqlToken(sql) {
119
+ return sql.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
120
+ }
121
+ function normalizeNativeQuery(sql, values) {
122
+ const flatArgs = Array.isArray(values[0]) ? values[0] : values;
123
+ const parameterIndexes = Array.from(sql.matchAll(/\$(\d+)/g)).map((match) => Number(match[1]));
124
+ if (parameterIndexes.length === 0) return {
125
+ sql,
126
+ args: flatArgs
127
+ };
128
+ const args = parameterIndexes.map((index) => flatArgs[index - 1]);
129
+ return {
130
+ sql: sql.replace(/\$(\d+)/g, "?"),
131
+ args
132
+ };
133
+ }
134
+ function executeNativeQuery(database, sql, args = []) {
135
+ const statement = database.prepare(sql);
136
+ const columns = statement.columns();
137
+ if (columns.length > 0) {
138
+ const rows = statement.all(...args).map(toPlainRow);
139
+ return {
140
+ rows,
141
+ rowCount: rows.length,
142
+ command: firstSqlToken(sql),
143
+ fields: columns.map((column) => ({ name: column.name }))
144
+ };
145
+ }
146
+ const result = statement.run(...args);
147
+ return {
148
+ rows: [],
149
+ rowCount: Number(result?.changes ?? 0),
150
+ command: firstSqlToken(sql),
151
+ fields: []
152
+ };
153
+ }
154
+ function createNativeTablesFromSchemas(database, schemas) {
155
+ for (const [tableName, schema] of Object.entries(schemas)) try {
156
+ if (executeNativeQuery(database, "SELECT name FROM sqlite_master WHERE type='table' AND name=?", [tableName]).rows.length > 0) continue;
157
+ database.exec(schema.ddl);
158
+ for (const indexSql of schema.indexes ?? []) try {
159
+ database.exec(indexSql);
160
+ } catch (error) {
161
+ console.warn(`[sqlite] Failed to create index for ${tableName}: ${formatDbError(error)}`);
162
+ }
163
+ for (const triggerSql of schema.triggers ?? []) try {
164
+ database.exec(triggerSql);
165
+ } catch (error) {
166
+ console.warn(`[sqlite] Failed to create trigger for ${tableName}: ${formatDbError(error)}`);
167
+ }
168
+ } catch (error) {
169
+ throw new DatabaseError(`Failed to create table ${tableName} from schema`, {
170
+ tableName,
171
+ schema,
172
+ originalError: formatDbError(error)
173
+ });
174
+ }
175
+ }
176
+ function quoteIdentifier(identifier) {
177
+ return `"${identifier.replace(/"/g, "\"\"")}"`;
178
+ }
179
+ function buildQuestionWhere(where, startIndex = 1) {
180
+ const built = buildWhere(where, startIndex, "sqlite");
181
+ return {
182
+ sql: built.sql.replace(/\$(\d+)/g, "?"),
183
+ values: built.values
184
+ };
185
+ }
186
+ function serializeValue(value) {
187
+ if (value === null || value === void 0) return value;
188
+ if (value instanceof Date) return value.toISOString();
189
+ if (typeof value === "object") return JSON.stringify(value);
190
+ return value;
191
+ }
192
+ function serializeRecord(record) {
193
+ const serialized = {};
194
+ for (const [key, value] of Object.entries(record)) {
195
+ if (value === void 0) continue;
196
+ serialized[key] = serializeValue(value);
197
+ }
198
+ return serialized;
199
+ }
200
+ async function withNativeNullAwareUpsertLock(lockKey, callback) {
201
+ const previous = nativeNullAwareUpsertLocks.get(lockKey) ?? Promise.resolve();
202
+ let release = () => {};
203
+ const current = new Promise((resolve) => {
204
+ release = resolve;
205
+ });
206
+ const next = previous.catch(() => {}).then(() => current);
207
+ nativeNullAwareUpsertLocks.set(lockKey, next);
208
+ await previous.catch(() => {});
209
+ try {
210
+ return await callback();
211
+ } finally {
212
+ release();
213
+ if (nativeNullAwareUpsertLocks.get(lockKey) === next) nativeNullAwareUpsertLocks.delete(lockKey);
214
+ }
215
+ }
216
+ function sqliteVectorDistance(metric) {
217
+ switch (metric) {
218
+ case "l2": return "L2";
219
+ case "ip": return "DOT";
220
+ default: return "COSINE";
221
+ }
222
+ }
223
+ function sqliteVectorQuantization(options) {
224
+ const parts = [];
225
+ switch (options.quantization ?? "turbo4") {
226
+ case "turbo2":
227
+ parts.push("qtype=TURBO,qbits=2");
228
+ break;
229
+ case "turbo3":
230
+ parts.push("qtype=TURBO,qbits=3");
231
+ break;
232
+ case "turbo4":
233
+ parts.push("qtype=TURBO,qbits=4");
234
+ break;
235
+ case "uint8":
236
+ parts.push("qtype=UINT8");
237
+ break;
238
+ case "int8":
239
+ parts.push("qtype=INT8");
240
+ break;
241
+ case "1bit":
242
+ parts.push("qtype=1BIT");
243
+ break;
244
+ }
245
+ if (options.maxMemory) parts.push(`max_memory=${options.maxMemory}`);
246
+ return parts.join(",");
247
+ }
248
+ function createNotificationCapabilities(honkerDb, closeables) {
249
+ return {
250
+ async notify(channel, payload) {
251
+ return Number(honkerDb.notify(channel, payload));
252
+ },
253
+ listen(channel, options) {
254
+ const listener = honkerDb.listen(channel, { fallbackPollS: options?.fallbackPollMs === null ? null : options?.fallbackPollMs !== void 0 ? options.fallbackPollMs / 1e3 : void 0 });
255
+ closeables.add(listener);
256
+ return listener;
257
+ },
258
+ async waitForUpdate(options) {
259
+ const updateEvents = honkerDb.updateEvents();
260
+ closeables.add(updateEvents);
261
+ let timeout;
262
+ let removeAbortListener;
263
+ try {
264
+ if (options?.signal?.aborted) return false;
265
+ const races = [updateEvents.next().then(() => true, () => false)];
266
+ if (options?.timeoutMs !== void 0) {
267
+ const timeoutMs = options.timeoutMs;
268
+ races.push(new Promise((resolve) => {
269
+ timeout = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
270
+ }));
271
+ }
272
+ if (options?.signal) races.push(new Promise((resolve) => {
273
+ const onAbort = () => resolve(false);
274
+ options.signal?.addEventListener("abort", onAbort, { once: true });
275
+ removeAbortListener = () => options.signal?.removeEventListener("abort", onAbort);
276
+ }));
277
+ return await Promise.race(races);
278
+ } finally {
279
+ if (timeout) clearTimeout(timeout);
280
+ removeAbortListener?.();
281
+ closeables.delete(updateEvents);
282
+ updateEvents.close?.();
283
+ }
284
+ },
285
+ async prune(options) {
286
+ return Number(honkerDb.pruneNotifications(options?.olderThanS, options?.maxKeep));
287
+ }
288
+ };
289
+ }
290
+ function createVectorCapabilities(database, options) {
291
+ const initialized = /* @__PURE__ */ new Map();
292
+ const indexed = /* @__PURE__ */ new Map();
293
+ const keyFor = (table, column) => `${table}.${column}`;
294
+ const columnExists = (table, column) => {
295
+ return database.prepare(`PRAGMA table_info(${quoteIdentifier(table)})`).all().map(toPlainRow).some((row) => row.name === column);
296
+ };
297
+ const ensureInitialized = (table, column, dimensions, metric = "cosine") => {
298
+ if (!Number.isInteger(dimensions) || dimensions <= 0) throw new DatabaseError("Vector dimensions must be a positive integer", { dimensions });
299
+ validateTableName(table);
300
+ validateColumnName(column);
301
+ executeNativeQuery(database, "SELECT vector_init(?, ?, ?)", [
302
+ table,
303
+ column,
304
+ `dimension=${dimensions},type=FLOAT32,distance=${sqliteVectorDistance(metric)}`
305
+ ]);
306
+ initialized.set(keyFor(table, column), {
307
+ dimensions,
308
+ metric
309
+ });
310
+ };
311
+ const quantize = (table, column, vectorOptions) => {
312
+ const quantizationOptions = sqliteVectorQuantization(vectorOptions);
313
+ if (quantizationOptions) executeNativeQuery(database, "SELECT vector_quantize(?, ?, ?)", [
314
+ table,
315
+ column,
316
+ quantizationOptions
317
+ ]);
318
+ else executeNativeQuery(database, "SELECT vector_quantize(?, ?)", [table, column]);
319
+ if (vectorOptions.preload) executeNativeQuery(database, "SELECT vector_quantize_preload(?, ?)", [table, column]);
320
+ };
321
+ return {
322
+ async ensureColumn(table, column, dimensions) {
323
+ validateTableName(table);
324
+ validateColumnName(column);
325
+ try {
326
+ if (!columnExists(table, column)) executeNativeQuery(database, `ALTER TABLE ${quoteIdentifier(table)} ADD COLUMN ${quoteIdentifier(column)} BLOB`);
327
+ ensureInitialized(table, column, dimensions, "cosine");
328
+ } catch (error) {
329
+ throw new DatabaseError("Failed to ensure SQLite vector column", {
330
+ table,
331
+ column,
332
+ dimensions,
333
+ originalError: formatDbError(error)
334
+ });
335
+ }
336
+ },
337
+ async ensureIndex(table, column, indexOptions) {
338
+ validateTableName(table);
339
+ validateColumnName(column);
340
+ const key = keyFor(table, column);
341
+ const dimensions = indexOptions?.dimensions ?? initialized.get(key)?.dimensions;
342
+ if (!dimensions) throw new DatabaseError("SQLite vector index creation requires dimensions", {
343
+ table,
344
+ column,
345
+ hint: "Call ensureColumn(table, column, dimensions) first or pass dimensions to ensureIndex()."
346
+ });
347
+ const metric = indexOptions?.metric ?? "cosine";
348
+ try {
349
+ ensureInitialized(table, column, dimensions, metric);
350
+ const vectorOptions = { ...options };
351
+ quantize(table, column, vectorOptions);
352
+ indexed.set(key, {
353
+ metric,
354
+ options: vectorOptions
355
+ });
356
+ } catch (error) {
357
+ throw new DatabaseError("Failed to ensure SQLite vector index", {
358
+ table,
359
+ column,
360
+ originalError: formatDbError(error)
361
+ });
362
+ }
363
+ },
364
+ async upsertVector(table, where, column, embedding) {
365
+ validateTableName(table);
366
+ validateColumnName(column);
367
+ const key = keyFor(table, column);
368
+ const dimensions = initialized.get(key)?.dimensions ?? embedding.length;
369
+ const { sql: whereClause, values } = buildQuestionWhere(where);
370
+ if (!whereClause) throw new DatabaseError("SQLite vector update requires at least one WHERE condition", {
371
+ table,
372
+ column
373
+ });
374
+ try {
375
+ ensureInitialized(table, column, dimensions, initialized.get(key)?.metric ?? "cosine");
376
+ executeNativeQuery(database, `UPDATE ${quoteIdentifier(table)} SET ${quoteIdentifier(column)} = vector_as_f32(?) ${whereClause}`, [JSON.stringify(embedding), ...values]);
377
+ const indexState = indexed.get(key);
378
+ if (indexState) quantize(table, column, indexState.options);
379
+ } catch (error) {
380
+ throw new DatabaseError("Failed to upsert SQLite vector", {
381
+ table,
382
+ column,
383
+ where,
384
+ originalError: formatDbError(error)
385
+ });
386
+ }
387
+ },
388
+ async search(table, column, embedding, searchOptions) {
389
+ validateTableName(table);
390
+ validateColumnName(column);
391
+ const key = keyFor(table, column);
392
+ const metric = searchOptions?.metric ?? "cosine";
393
+ const limit = searchOptions?.limit ?? 10;
394
+ const limitArg = BigInt(limit);
395
+ const dimensions = initialized.get(key)?.dimensions ?? embedding.length;
396
+ const scanFunction = indexed.get(key)?.metric === metric ? "vector_quantize_scan" : "vector_full_scan";
397
+ const convertedWhere = searchOptions?.where ? searchOptions.where.replace(/\$(\d+)/g, "?") : "";
398
+ const whereParams = searchOptions?.params ?? [];
399
+ try {
400
+ ensureInitialized(table, column, dimensions, metric);
401
+ return (convertedWhere ? executeNativeQuery(database, `SELECT base.*, base.rowid AS __sqlite_rowid, vec.distance
402
+ FROM ${scanFunction}(?, ?, vector_as_f32(?)) AS vec
403
+ JOIN ${quoteIdentifier(table)} AS base ON base.rowid = vec.rowid
404
+ WHERE (${convertedWhere})
405
+ ORDER BY vec.distance
406
+ LIMIT ?`, [
407
+ table,
408
+ column,
409
+ JSON.stringify(embedding),
410
+ ...whereParams,
411
+ limitArg
412
+ ]).rows : executeNativeQuery(database, `SELECT base.*, base.rowid AS __sqlite_rowid, vec.distance
413
+ FROM ${scanFunction}(?, ?, vector_as_f32(?), ?) AS vec
414
+ JOIN ${quoteIdentifier(table)} AS base ON base.rowid = vec.rowid
415
+ ORDER BY vec.distance`, [
416
+ table,
417
+ column,
418
+ JSON.stringify(embedding),
419
+ limitArg
420
+ ]).rows).map((row) => {
421
+ const { __sqlite_rowid: rowid, ...result } = row;
422
+ return {
423
+ ...result,
424
+ id: String(result.id ?? rowid),
425
+ distance: Number(row.distance)
426
+ };
427
+ });
428
+ } catch (error) {
429
+ throw new DatabaseError("Failed to execute SQLite vector search", {
430
+ table,
431
+ column,
432
+ metric,
433
+ limit,
434
+ originalError: formatDbError(error)
435
+ });
436
+ }
437
+ }
438
+ };
439
+ }
440
+ async function getNativeSqliteDatabase(options = {}) {
441
+ const url = options.url || ":memory:";
442
+ if (options.dbid) {
443
+ const cached = nativeMemoryConnectionCache.get(options.dbid);
444
+ if (cached) return cached;
445
+ const pending = nativePendingConnections.get(options.dbid);
446
+ if (pending) return pending;
447
+ }
448
+ const connectionPromise = createNativeSqliteDatabase(options, url);
449
+ if (options.dbid) nativePendingConnections.set(options.dbid, connectionPromise);
450
+ try {
451
+ const db = await connectionPromise;
452
+ if (options.dbid) nativeMemoryConnectionCache.set(options.dbid, db);
453
+ return db;
454
+ } finally {
455
+ if (options.dbid) nativePendingConnections.delete(options.dbid);
456
+ }
457
+ }
458
+ async function createNativeSqliteDatabase(options, url) {
459
+ const location = resolveNativeLocation(url);
460
+ const notificationsOptions = getCapabilityOptions(options.capabilities?.notifications);
461
+ const vectorOptions = getCapabilityOptions(options.capabilities?.vector);
462
+ const wantsNotifications = Boolean(options.capabilities?.notifications);
463
+ const wantsVector = Boolean(options.capabilities?.vector);
464
+ if (wantsNotifications && location.isMemory) throw new DatabaseError("SQLite notification capabilities require a file-backed database", {
465
+ url,
466
+ hint: "Use a temporary file-backed .db for development and tests that need notifications."
467
+ });
468
+ const { DatabaseSync } = await loadNodeSqlite();
469
+ const database = new DatabaseSync(location.location, { allowExtension: wantsVector });
470
+ try {
471
+ if (wantsVector) await loadSqliteVectorExtension(database);
472
+ const honkerDb = wantsNotifications && location.filePath ? await openHonkerDatabase(location.filePath, notificationsOptions) : null;
473
+ const notificationCloseables = /* @__PURE__ */ new Set();
474
+ let closed = false;
475
+ const vectorCapabilities = wantsVector ? createVectorCapabilities(database, vectorOptions) : void 0;
476
+ const notificationCapabilities = honkerDb ? createNotificationCapabilities(honkerDb, notificationCloseables) : void 0;
477
+ const query = async (sql, ...values) => {
478
+ const normalized = normalizeNativeQuery(sql, values);
479
+ try {
480
+ const result = executeNativeQuery(database, normalized.sql, normalized.args);
481
+ return {
482
+ rows: result.rows,
483
+ rowCount: result.rowCount
484
+ };
485
+ } catch (error) {
486
+ throw new DatabaseError("Failed to execute raw query", {
487
+ sql: normalized.sql,
488
+ values: normalized.args,
489
+ originalError: formatDbError(error)
490
+ });
491
+ }
492
+ };
493
+ const parseTemplate = (strings, ...vars) => {
494
+ let sql = strings[0];
495
+ const values = [];
496
+ for (let i = 0; i < vars.length; i++) {
497
+ values.push(vars[i]);
498
+ sql += `?${strings[i + 1]}`;
499
+ }
500
+ return {
501
+ sql,
502
+ values
503
+ };
504
+ };
505
+ const insert = async (table, data) => {
506
+ const records = (Array.isArray(data) ? data : [data]).map(serializeRecord);
507
+ const keys = Object.keys(records[0]);
508
+ const placeholders = records.map(() => `(${keys.map(() => "?").join(", ")})`).join(", ");
509
+ const values = records.flatMap((record) => keys.map((key) => record[key]));
510
+ const sql = `INSERT INTO ${table} (${keys.join(", ")}) VALUES ${placeholders}`;
511
+ try {
512
+ return {
513
+ operation: "insert",
514
+ affected: executeNativeQuery(database, sql, values).rowCount
515
+ };
516
+ } catch (error) {
517
+ throw new DatabaseError("Failed to insert records into table", {
518
+ table,
519
+ sql,
520
+ values,
521
+ originalError: formatDbError(error)
522
+ });
523
+ }
524
+ };
525
+ const get = async (table, where) => {
526
+ const { sql: whereClause, values } = buildQuestionWhere(where);
527
+ if (!whereClause) throw new DatabaseError("GET requires at least one WHERE condition to prevent returning an arbitrary record", { table });
528
+ const sql = `SELECT * FROM ${table} ${whereClause}`;
529
+ try {
530
+ return executeNativeQuery(database, sql, values).rows[0] || null;
531
+ } catch (error) {
532
+ throw new DatabaseError("Failed to retrieve record from table", {
533
+ table,
534
+ sql,
535
+ values,
536
+ originalError: formatDbError(error)
537
+ });
538
+ }
539
+ };
540
+ const list = async (table, where) => {
541
+ const { sql: whereClause, values } = buildQuestionWhere(where);
542
+ const sql = `SELECT * FROM ${table} ${whereClause}`;
543
+ try {
544
+ return executeNativeQuery(database, sql, values).rows;
545
+ } catch (error) {
546
+ throw new DatabaseError("Failed to list records from table", {
547
+ table,
548
+ sql,
549
+ values,
550
+ originalError: formatDbError(error)
551
+ });
552
+ }
553
+ };
554
+ const update = async (table, where, data) => {
555
+ const serializedData = serializeRecord(data);
556
+ const keys = Object.keys(serializedData);
557
+ const values = Object.values(serializedData);
558
+ const setClause = keys.map((key) => `${key} = ?`).join(", ");
559
+ const { sql: whereClause, values: whereValues } = buildQuestionWhere(where);
560
+ if (!whereClause) throw new DatabaseError("UPDATE requires at least one WHERE condition to prevent accidental update of all records", { table });
561
+ const sql = `UPDATE ${table} SET ${setClause} ${whereClause}`;
562
+ try {
563
+ return {
564
+ operation: "update",
565
+ affected: executeNativeQuery(database, sql, [...values, ...whereValues]).rowCount
566
+ };
567
+ } catch (error) {
568
+ throw new DatabaseError("Failed to update records in table", {
569
+ table,
570
+ sql,
571
+ values: [...values, ...whereValues],
572
+ originalError: formatDbError(error)
573
+ });
574
+ }
575
+ };
576
+ const hasNullConflictValue = (conflictColumns, data) => conflictColumns.some((column) => data[column] === null);
577
+ const validateUpsertConflictColumns = (table, conflictColumns, serializedData) => {
578
+ const missingColumns = conflictColumns.filter((column) => !(column in serializedData));
579
+ if (missingColumns.length > 0) throw new DatabaseError("Conflict columns missing from data", {
580
+ table,
581
+ conflictColumns,
582
+ missingColumns,
583
+ availableColumns: Object.keys(serializedData),
584
+ hint: "All columns specified in ON CONFLICT must be present in the data being inserted. Undefined values are filtered out during serialization - consider using null or an empty string instead."
585
+ });
586
+ };
587
+ const executeStandardUpsert = (table, conflictColumns, serializedData) => {
588
+ const keys = Object.keys(serializedData);
589
+ const values = Object.values(serializedData);
590
+ const placeholders = keys.map(() => "?").join(", ");
591
+ const updateSet = keys.map((key) => `${key} = excluded.${key}`).join(", ");
592
+ const conflict = conflictColumns.join(", ");
593
+ const sql = `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${conflict}) DO UPDATE SET ${updateSet}`;
594
+ return {
595
+ operation: "upsert",
596
+ affected: executeNativeQuery(database, sql, values).rowCount
597
+ };
598
+ };
599
+ const executeNullAwareUpsertAttempt = (table, conflictColumns, serializedData) => {
600
+ const keys = Object.keys(serializedData);
601
+ const values = Object.values(serializedData);
602
+ const updateSet = keys.map((key) => `${key} = ?`).join(", ");
603
+ const whereClause = conflictColumns.map((column) => `${column} IS ?`).join(" AND ");
604
+ const whereValues = conflictColumns.map((column) => serializedData[column]);
605
+ const updateResult = executeNativeQuery(database, `UPDATE ${table} SET ${updateSet} WHERE ${whereClause}`, [...values, ...whereValues]);
606
+ if (updateResult.rowCount > 0) return {
607
+ operation: "upsert",
608
+ affected: updateResult.rowCount
609
+ };
610
+ const placeholders = keys.map(() => "?").join(", ");
611
+ return {
612
+ operation: "upsert",
613
+ affected: executeNativeQuery(database, `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders})`, values).rowCount
614
+ };
615
+ };
616
+ const executeUpsert = async (table, conflictColumns, data, upsertOptions, acquireTransaction) => {
617
+ const serializedData = serializeRecord(data);
618
+ validateUpsertConflictColumns(table, conflictColumns, serializedData);
619
+ if (upsertOptions?.nullsDistinct || !hasNullConflictValue(conflictColumns, serializedData)) return executeStandardUpsert(table, conflictColumns, serializedData);
620
+ if (!acquireTransaction) return executeNullAwareUpsertAttempt(table, conflictColumns, serializedData);
621
+ return withNativeNullAwareUpsertLock(`${location.location}:${table}:${conflictColumns.map((column) => `${column}:${JSON.stringify(serializedData[column])}`).join("|")}`, async () => {
622
+ try {
623
+ database.exec("BEGIN IMMEDIATE");
624
+ const result = executeNullAwareUpsertAttempt(table, conflictColumns, serializedData);
625
+ database.exec("COMMIT");
626
+ return result;
627
+ } catch (error) {
628
+ try {
629
+ database.exec("ROLLBACK");
630
+ } catch (_rollbackError) {}
631
+ throw error;
632
+ }
633
+ });
634
+ };
635
+ const upsert = async (table, conflictColumns, data, upsertOptions) => {
636
+ try {
637
+ return await executeUpsert(table, conflictColumns, data, upsertOptions, true);
638
+ } catch (error) {
639
+ if (getErrorMessage(error) === "Conflict columns missing from data") throw error;
640
+ throw new DatabaseError("Failed to upsert record into table", {
641
+ table,
642
+ values: data,
643
+ conflictColumns,
644
+ originalError: formatDbError(error)
645
+ });
646
+ }
647
+ };
648
+ const upsertInCurrentTransaction = (table, conflictColumns, data, upsertOptions) => executeUpsert(table, conflictColumns, data, upsertOptions, false);
649
+ const getOrInsert = async (table, where, data) => {
650
+ const existing = await get(table, where);
651
+ if (existing) return existing;
652
+ await insert(table, data);
653
+ const inserted = await get(table, where);
654
+ if (!inserted) throw new DatabaseError("Failed to insert and retrieve record", {
655
+ table,
656
+ where,
657
+ data
658
+ });
659
+ return inserted;
660
+ };
661
+ const deleteRecords = async (table, where) => {
662
+ validateTableName(table);
663
+ const { sql: whereClause, values } = buildQuestionWhere(where);
664
+ if (!whereClause) throw new DatabaseError("DELETE requires at least one WHERE condition to prevent accidental deletion of all records", { table });
665
+ try {
666
+ return {
667
+ operation: "delete",
668
+ affected: executeNativeQuery(database, `DELETE FROM ${table} ${whereClause}`, values).rowCount
669
+ };
670
+ } catch (error) {
671
+ throw new DatabaseError("Failed to delete records from table", {
672
+ table,
673
+ where,
674
+ originalError: formatDbError(error)
675
+ });
676
+ }
677
+ };
678
+ const count = async (table, where) => {
679
+ validateTableName(table);
680
+ if (!where || typeof where === "object" && Object.keys(where).length === 0) {
681
+ const result = executeNativeQuery(database, `SELECT COUNT(*) as count FROM ${table}`);
682
+ return Number(result.rows[0]?.count) || 0;
683
+ }
684
+ const { sql: whereClause, values } = buildQuestionWhere(where);
685
+ const result = executeNativeQuery(database, `SELECT COUNT(*) as count FROM ${table} ${whereClause}`, values);
686
+ return Number(result.rows[0]?.count) || 0;
687
+ };
688
+ const tableExists = async (tableName) => {
689
+ return executeNativeQuery(database, "SELECT name FROM sqlite_master WHERE type='table' AND name=?", [tableName]).rows.length > 0;
690
+ };
691
+ const syncSchema = async (schema) => {
692
+ const commands = schema.replace(/\s+/g, " ").replace(/DEFAULT CAST\(([^)]+)\s+AS\s+\w+\)/gi, "DEFAULT $1").trim().split(";").map((command) => command.trim()).filter((command) => command.length > 0);
693
+ for (const command of commands) database.exec(command);
694
+ };
695
+ const many = async (strings, ...vars) => {
696
+ const { sql, values } = parseTemplate(strings, ...vars);
697
+ try {
698
+ return executeNativeQuery(database, sql, values).rows;
699
+ } catch (error) {
700
+ throw new DatabaseError("Failed to execute many query", {
701
+ sql,
702
+ values,
703
+ originalError: formatDbError(error)
704
+ });
705
+ }
706
+ };
707
+ const single = async (strings, ...vars) => {
708
+ const { sql, values } = parseTemplate(strings, ...vars);
709
+ try {
710
+ return executeNativeQuery(database, sql, values).rows[0] || null;
711
+ } catch (error) {
712
+ throw new DatabaseError("Failed to execute single query", {
713
+ sql,
714
+ values,
715
+ originalError: formatDbError(error)
716
+ });
717
+ }
718
+ };
719
+ const pluck = async (strings, ...vars) => {
720
+ const { sql, values } = parseTemplate(strings, ...vars);
721
+ try {
722
+ const row = executeNativeQuery(database, sql, values).rows[0];
723
+ return row ? Object.values(row)[0] : null;
724
+ } catch (error) {
725
+ throw new DatabaseError("Failed to execute pluck query", {
726
+ sql,
727
+ values,
728
+ originalError: formatDbError(error)
729
+ });
730
+ }
731
+ };
732
+ const execute = async (strings, ...vars) => {
733
+ const { sql, values } = parseTemplate(strings, ...vars);
734
+ try {
735
+ if (values.length === 0) {
736
+ database.exec(sql);
737
+ return;
738
+ }
739
+ executeNativeQuery(database, sql, values);
740
+ } catch (error) {
741
+ throw new DatabaseError("Failed to execute query", {
742
+ sql,
743
+ values,
744
+ originalError: formatDbError(error)
745
+ });
746
+ }
747
+ };
748
+ const oo = many;
749
+ const oO = single;
750
+ const ox = pluck;
751
+ const xx = execute;
752
+ const table = (tableName) => ({
753
+ insert: (data) => insert(tableName, data),
754
+ get: (where) => get(tableName, where),
755
+ list: (where) => list(tableName, where)
756
+ });
757
+ const transaction = async (callback) => {
758
+ try {
759
+ database.exec("BEGIN TRANSACTION");
760
+ const result = await callback(buildDatabaseInterface(upsertInCurrentTransaction));
761
+ database.exec("COMMIT");
762
+ return result;
763
+ } catch (error) {
764
+ database.exec("ROLLBACK");
765
+ throw error;
766
+ }
767
+ };
768
+ const beginTransaction = async () => {
769
+ database.exec("BEGIN TRANSACTION");
770
+ let active = true;
771
+ const commit = async () => {
772
+ if (!active) throw new DatabaseError("Transaction already ended", {});
773
+ database.exec("COMMIT");
774
+ active = false;
775
+ };
776
+ const rollback = async () => {
777
+ if (!active) throw new DatabaseError("Transaction already ended", {});
778
+ database.exec("ROLLBACK");
779
+ active = false;
780
+ };
781
+ return {
782
+ ...buildDatabaseInterface(upsertInCurrentTransaction),
783
+ commit,
784
+ rollback,
785
+ isActive: () => active
786
+ };
787
+ };
788
+ const initializeSchemas = async (schemaOptions) => {
789
+ await new DatabaseSchemaManager().initializeSchemas(buildDatabaseInterface(upsert), schemaOptions);
790
+ };
791
+ const getTableSchema = async (tableName) => {
792
+ validateTableName(tableName);
793
+ if (!await tableExists(tableName)) return null;
794
+ const columnRows = database.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all().map(toPlainRow);
795
+ const columns = {};
796
+ for (const row of columnRows) columns[row.name] = {
797
+ type: row.type,
798
+ primaryKey: row.pk === 1,
799
+ notNull: row.notnull === 1,
800
+ defaultValue: row.dflt_value
801
+ };
802
+ const indexRows = executeNativeQuery(database, `SELECT name FROM sqlite_master
803
+ WHERE type = 'index'
804
+ AND tbl_name = ?
805
+ AND name NOT LIKE 'sqlite_%'`, [tableName]).rows;
806
+ const indexes = [];
807
+ for (const row of indexRows) {
808
+ const indexName = row.name;
809
+ const indexInfoRows = database.prepare(`PRAGMA index_info(${quoteIdentifier(indexName)})`).all().map(toPlainRow);
810
+ const indexListRows = database.prepare(`PRAGMA index_list(${quoteIdentifier(tableName)})`).all().map(toPlainRow);
811
+ indexes.push({
812
+ name: indexName,
813
+ columns: indexInfoRows.map((info) => info.name),
814
+ unique: indexListRows.some((info) => info.name === indexName && info.unique === 1)
815
+ });
816
+ }
817
+ return {
818
+ tableName,
819
+ columns,
820
+ indexes,
821
+ foreignKeys: database.prepare(`PRAGMA foreign_key_list(${quoteIdentifier(tableName)})`).all().map(toPlainRow).map((row) => ({
822
+ column: row.from,
823
+ referencesTable: row.table,
824
+ referencesColumn: row.to,
825
+ onDelete: row.on_delete,
826
+ onUpdate: row.on_update
827
+ }))
828
+ };
829
+ };
830
+ const alterTable = {
831
+ addColumn: async (tableName, column) => {
832
+ validateTableName(tableName);
833
+ validateColumnName(column.name);
834
+ const sql = generateAddColumnStatement(tableName, column, "sqlite");
835
+ executeNativeQuery(database, sql);
836
+ },
837
+ addIndex: async (tableName, index) => {
838
+ validateTableName(tableName);
839
+ validateIndexName(index.name);
840
+ for (const column of index.columns) validateColumnName(column);
841
+ const sql = generateCreateIndexStatement(tableName, index);
842
+ executeNativeQuery(database, sql);
843
+ }
844
+ };
845
+ const close = async () => {
846
+ if (closed) return;
847
+ closed = true;
848
+ for (const closeable of notificationCloseables) closeable.close?.();
849
+ notificationCloseables.clear();
850
+ honkerDb?.close?.();
851
+ database.close();
852
+ if (options.dbid) nativeMemoryConnectionCache.delete(options.dbid);
853
+ };
854
+ function buildDatabaseInterface(upsertImpl) {
855
+ return {
856
+ url,
857
+ client: database,
858
+ query,
859
+ insert,
860
+ update,
861
+ upsert: upsertImpl,
862
+ get,
863
+ list,
864
+ getOrInsert,
865
+ delete: deleteRecords,
866
+ count,
867
+ table,
868
+ tableExists,
869
+ many,
870
+ single,
871
+ pluck,
872
+ execute,
873
+ oo,
874
+ oO,
875
+ ox,
876
+ xx,
877
+ syncSchema,
878
+ initializeSchemas,
879
+ transaction,
880
+ beginTransaction,
881
+ getTableSchema,
882
+ alterTable,
883
+ close,
884
+ ...vectorCapabilities ? { vector: vectorCapabilities } : {},
885
+ ...notificationCapabilities ? { notifications: notificationCapabilities } : {}
886
+ };
887
+ }
888
+ const db = buildDatabaseInterface(upsert);
889
+ const resolvedSchemas = resolveSchemas(options.schemas);
890
+ if (resolvedSchemas && Object.keys(resolvedSchemas).length > 0) createNativeTablesFromSchemas(database, resolvedSchemas);
891
+ return db;
892
+ } catch (error) {
893
+ try {
894
+ database.close();
895
+ } catch (_closeError) {}
896
+ throw error;
897
+ }
898
+ }
899
+ //#endregion
900
+ export { getNativeSqliteDatabase };
901
+
902
+ //# sourceMappingURL=sqlite-native-BGtpx7Ro.js.map