@atscript/db-sqlite 0.1.34 → 0.1.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseDbAdapter } from "@atscript/utils-db";
1
+ import { AtscriptDbView, BaseDbAdapter, DbSpace } from "@atscript/utils-db";
2
2
  import { walkFilter } from "@uniqu/core";
3
3
 
4
4
  //#region rolldown:runtime
@@ -7,6 +7,207 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function.");
8
8
  });
9
9
 
10
+ //#endregion
11
+ //#region packages/db-sqlite/src/better-sqlite3-driver.ts
12
+ function _define_property$1(obj, key, value) {
13
+ if (key in obj) Object.defineProperty(obj, key, {
14
+ value,
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true
18
+ });
19
+ else obj[key] = value;
20
+ return obj;
21
+ }
22
+ var BetterSqlite3Driver = class {
23
+ run(sql, params) {
24
+ const stmt = this.db.prepare(sql);
25
+ const result = params ? stmt.run(...params) : stmt.run();
26
+ return {
27
+ changes: result.changes,
28
+ lastInsertRowid: result.lastInsertRowid
29
+ };
30
+ }
31
+ all(sql, params) {
32
+ const stmt = this.db.prepare(sql);
33
+ return params ? stmt.all(...params) : stmt.all();
34
+ }
35
+ get(sql, params) {
36
+ const stmt = this.db.prepare(sql);
37
+ return (params ? stmt.get(...params) : stmt.get()) ?? null;
38
+ }
39
+ exec(sql) {
40
+ this.db.exec(sql);
41
+ }
42
+ close() {
43
+ this.db.close();
44
+ }
45
+ constructor(pathOrDb, options) {
46
+ _define_property$1(this, "db", void 0);
47
+ if (typeof pathOrDb === "string") {
48
+ const Database = __require("better-sqlite3");
49
+ this.db = new Database(pathOrDb, options);
50
+ } else this.db = pathOrDb;
51
+ }
52
+ };
53
+
54
+ //#endregion
55
+ //#region packages/db-sqlite/src/sql-builder.ts
56
+ function buildInsert(table, data) {
57
+ const keys = Object.keys(data);
58
+ const cols = keys.map((k) => `"${esc(k)}"`).join(", ");
59
+ const placeholders = keys.map(() => "?").join(", ");
60
+ return {
61
+ sql: `INSERT INTO "${esc(table)}" (${cols}) VALUES (${placeholders})`,
62
+ params: keys.map((k) => toSqliteValue(data[k]))
63
+ };
64
+ }
65
+ function buildSelect(table, where, controls) {
66
+ const cols = buildProjection(controls?.$select);
67
+ let sql = `SELECT ${cols} FROM "${esc(table)}" WHERE ${where.sql}`;
68
+ const params = [...where.params];
69
+ if (controls?.$sort) {
70
+ const orderParts = [];
71
+ for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(`"${esc(col)}" ${dir === -1 ? "DESC" : "ASC"}`);
72
+ if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
73
+ }
74
+ if (controls?.$limit !== undefined) {
75
+ sql += ` LIMIT ?`;
76
+ params.push(controls.$limit);
77
+ }
78
+ if (controls?.$skip !== undefined) {
79
+ if (controls.$limit === undefined) sql += ` LIMIT -1`;
80
+ sql += ` OFFSET ?`;
81
+ params.push(controls.$skip);
82
+ }
83
+ return {
84
+ sql,
85
+ params
86
+ };
87
+ }
88
+ function buildUpdate(table, data, where) {
89
+ const setClauses = [];
90
+ const params = [];
91
+ for (const [key, value] of Object.entries(data)) {
92
+ setClauses.push(`"${esc(key)}" = ?`);
93
+ params.push(toSqliteValue(value));
94
+ }
95
+ return {
96
+ sql: `UPDATE "${esc(table)}" SET ${setClauses.join(", ")} WHERE ${where.sql}`,
97
+ params: [...params, ...where.params]
98
+ };
99
+ }
100
+ function buildDelete(table, where) {
101
+ return {
102
+ sql: `DELETE FROM "${esc(table)}" WHERE ${where.sql}`,
103
+ params: [...where.params]
104
+ };
105
+ }
106
+ function buildCreateTable(table, fields, foreignKeys) {
107
+ const colDefs = [];
108
+ const primaryKeys = fields.filter((f) => f.isPrimaryKey);
109
+ for (const field of fields) {
110
+ if (field.ignored) continue;
111
+ const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
112
+ let def = `"${esc(field.physicalName)}" ${sqlType}`;
113
+ if (field.isPrimaryKey && primaryKeys.length === 1) def += " PRIMARY KEY";
114
+ if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
115
+ colDefs.push(def);
116
+ }
117
+ if (primaryKeys.length > 1) {
118
+ const pkCols = primaryKeys.map((pk) => `"${esc(pk.physicalName)}"`).join(", ");
119
+ colDefs.push(`PRIMARY KEY (${pkCols})`);
120
+ }
121
+ if (foreignKeys) for (const fk of foreignKeys.values()) {
122
+ const localCols = fk.fields.map((f) => `"${esc(f)}"`).join(", ");
123
+ const targetCols = fk.targetFields.map((f) => `"${esc(f)}"`).join(", ");
124
+ let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc(fk.targetTable)}" (${targetCols})`;
125
+ if (fk.onDelete) constraint += ` ON DELETE ${refActionToSql(fk.onDelete)}`;
126
+ if (fk.onUpdate) constraint += ` ON UPDATE ${refActionToSql(fk.onUpdate)}`;
127
+ colDefs.push(constraint);
128
+ }
129
+ return `CREATE TABLE IF NOT EXISTS "${esc(table)}" (${colDefs.join(", ")})`;
130
+ }
131
+ function refActionToSql(action) {
132
+ switch (action) {
133
+ case "cascade": return "CASCADE";
134
+ case "restrict": return "RESTRICT";
135
+ case "setNull": return "SET NULL";
136
+ case "setDefault": return "SET DEFAULT";
137
+ default: return "NO ACTION";
138
+ }
139
+ }
140
+ function sqliteTypeFromDesignType(designType) {
141
+ switch (designType) {
142
+ case "number":
143
+ case "integer": return "REAL";
144
+ case "boolean": return "INTEGER";
145
+ case "string": return "TEXT";
146
+ default: return "TEXT";
147
+ }
148
+ }
149
+ function buildProjection(select) {
150
+ const fields = select?.asArray;
151
+ if (!fields) return "*";
152
+ let sql = "";
153
+ for (let i = 0; i < fields.length; i++) {
154
+ if (i > 0) sql += ", ";
155
+ sql += `"${esc(fields[i])}"`;
156
+ }
157
+ return sql || "*";
158
+ }
159
+ function esc(name) {
160
+ return name.replace(/"/g, "\"\"");
161
+ }
162
+ function toSqliteValue(value) {
163
+ if (value === undefined) return null;
164
+ if (value === null) return null;
165
+ if (typeof value === "object") return JSON.stringify(value);
166
+ if (typeof value === "boolean") return value ? 1 : 0;
167
+ return value;
168
+ }
169
+ function buildCreateView(viewName, plan, columns, resolveFieldRef) {
170
+ const selectCols = columns.map((c) => `"${esc(c.sourceTable)}"."${esc(c.sourceColumn)}" AS "${esc(c.viewColumn)}"`).join(", ");
171
+ let sql = `CREATE VIEW IF NOT EXISTS "${esc(viewName)}" AS SELECT ${selectCols} FROM "${esc(plan.entryTable)}"`;
172
+ for (const join of plan.joins) {
173
+ const onClause = queryNodeToSql(join.condition, resolveFieldRef);
174
+ sql += ` JOIN "${esc(join.targetTable)}" ON ${onClause}`;
175
+ }
176
+ if (plan.filter) {
177
+ const whereClause = queryNodeToSql(plan.filter, resolveFieldRef);
178
+ sql += ` WHERE ${whereClause}`;
179
+ }
180
+ return sql;
181
+ }
182
+ const queryOpToSql = {
183
+ $eq: "=",
184
+ $ne: "!=",
185
+ $gt: ">",
186
+ $gte: ">=",
187
+ $lt: "<",
188
+ $lte: "<="
189
+ };
190
+ /**
191
+ * Renders an AtscriptQueryNode tree to raw SQL (no parameters — for DDL use only).
192
+ */ function queryNodeToSql(node, resolveFieldRef) {
193
+ if ("$and" in node) {
194
+ const children = node.$and;
195
+ return children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" AND ");
196
+ }
197
+ if ("$or" in node) {
198
+ const children = node.$or;
199
+ return `(${children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" OR ")})`;
200
+ }
201
+ if ("$not" in node) return `NOT (${queryNodeToSql(node.$not, resolveFieldRef)})`;
202
+ const comp = node;
203
+ const leftSql = resolveFieldRef(comp.left);
204
+ const sqlOp = queryOpToSql[comp.op] || "=";
205
+ if (comp.right && typeof comp.right === "object" && "field" in comp.right) return `${leftSql} ${sqlOp} ${resolveFieldRef(comp.right)}`;
206
+ if (comp.right === null || comp.right === undefined) return comp.op === "$ne" ? `${leftSql} IS NOT NULL` : `${leftSql} IS NULL`;
207
+ if (typeof comp.right === "string") return `${leftSql} ${sqlOp} '${comp.right.replace(/'/g, "''")}'`;
208
+ return `${leftSql} ${sqlOp} ${comp.right}`;
209
+ }
210
+
10
211
  //#endregion
11
212
  //#region packages/db-sqlite/src/filter-builder.ts
12
213
  const EMPTY_AND = {
@@ -22,7 +223,7 @@ const EMPTY_OR = {
22
223
  * into a parameterized SQL WHERE clause.
23
224
  */ const sqlVisitor = {
24
225
  comparison(field, op, value) {
25
- const col = `"${escapeIdent(field)}"`;
226
+ const col = `"${esc(field)}"`;
26
227
  const v = toSqliteParam(value);
27
228
  switch (op) {
28
229
  case "$eq": {
@@ -119,7 +320,7 @@ const EMPTY_OR = {
119
320
  };
120
321
  function buildWhere(filter) {
121
322
  if (!filter || Object.keys(filter).length === 0) return EMPTY_AND;
122
- return walkFilter(filter, sqlVisitor);
323
+ return walkFilter(filter, sqlVisitor) ?? EMPTY_AND;
123
324
  }
124
325
  /**
125
326
  * Basic regex-to-LIKE conversion.
@@ -141,12 +342,6 @@ function buildWhere(filter) {
141
342
  return `%${core}%`;
142
343
  }
143
344
  /**
144
- * Escapes a SQL identifier to prevent injection.
145
- * Doubles any embedded double-quotes.
146
- */ function escapeIdent(name) {
147
- return name.replace(/"/g, "\"\"");
148
- }
149
- /**
150
345
  * Converts a JS value to a SQLite-bindable parameter.
151
346
  * SQLite cannot bind booleans — they must be 0/1.
152
347
  */ function toSqliteParam(value) {
@@ -154,128 +349,9 @@ function buildWhere(filter) {
154
349
  return value;
155
350
  }
156
351
 
157
- //#endregion
158
- //#region packages/db-sqlite/src/sql-builder.ts
159
- function buildInsert(table, data) {
160
- const keys = Object.keys(data);
161
- const cols = keys.map((k) => `"${esc$1(k)}"`).join(", ");
162
- const placeholders = keys.map(() => "?").join(", ");
163
- return {
164
- sql: `INSERT INTO "${esc$1(table)}" (${cols}) VALUES (${placeholders})`,
165
- params: keys.map((k) => toSqliteValue$1(data[k]))
166
- };
167
- }
168
- function buildSelect(table, where, controls) {
169
- const cols = buildProjection(controls?.$select);
170
- let sql = `SELECT ${cols} FROM "${esc$1(table)}" WHERE ${where.sql}`;
171
- const params = [...where.params];
172
- if (controls?.$sort) {
173
- const orderParts = [];
174
- for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(`"${esc$1(col)}" ${dir === -1 ? "DESC" : "ASC"}`);
175
- if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
176
- }
177
- if (controls?.$limit !== undefined) {
178
- sql += ` LIMIT ?`;
179
- params.push(controls.$limit);
180
- }
181
- if (controls?.$skip !== undefined) {
182
- if (controls.$limit === undefined) sql += ` LIMIT -1`;
183
- sql += ` OFFSET ?`;
184
- params.push(controls.$skip);
185
- }
186
- return {
187
- sql,
188
- params
189
- };
190
- }
191
- function buildUpdate(table, data, where) {
192
- const setClauses = [];
193
- const params = [];
194
- for (const [key, value] of Object.entries(data)) {
195
- setClauses.push(`"${esc$1(key)}" = ?`);
196
- params.push(toSqliteValue$1(value));
197
- }
198
- return {
199
- sql: `UPDATE "${esc$1(table)}" SET ${setClauses.join(", ")} WHERE ${where.sql}`,
200
- params: [...params, ...where.params]
201
- };
202
- }
203
- function buildDelete(table, where) {
204
- return {
205
- sql: `DELETE FROM "${esc$1(table)}" WHERE ${where.sql}`,
206
- params: [...where.params]
207
- };
208
- }
209
- function buildCreateTable(table, fields, foreignKeys) {
210
- const colDefs = [];
211
- const primaryKeys = fields.filter((f) => f.isPrimaryKey);
212
- for (const field of fields) {
213
- if (field.ignored) continue;
214
- const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
215
- let def = `"${esc$1(field.physicalName)}" ${sqlType}`;
216
- if (field.isPrimaryKey && primaryKeys.length === 1) def += " PRIMARY KEY";
217
- if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
218
- colDefs.push(def);
219
- }
220
- if (primaryKeys.length > 1) {
221
- const pkCols = primaryKeys.map((pk) => `"${esc$1(pk.physicalName)}"`).join(", ");
222
- colDefs.push(`PRIMARY KEY (${pkCols})`);
223
- }
224
- if (foreignKeys) for (const fk of foreignKeys.values()) {
225
- const localCols = fk.fields.map((f) => `"${esc$1(f)}"`).join(", ");
226
- const targetCols = fk.targetFields.map((f) => `"${esc$1(f)}"`).join(", ");
227
- let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc$1(fk.targetTable)}" (${targetCols})`;
228
- if (fk.onDelete) constraint += ` ON DELETE ${refActionToSql(fk.onDelete)}`;
229
- if (fk.onUpdate) constraint += ` ON UPDATE ${refActionToSql(fk.onUpdate)}`;
230
- colDefs.push(constraint);
231
- }
232
- return `CREATE TABLE IF NOT EXISTS "${esc$1(table)}" (${colDefs.join(", ")})`;
233
- }
234
- function refActionToSql(action) {
235
- switch (action) {
236
- case "cascade": return "CASCADE";
237
- case "restrict": return "RESTRICT";
238
- case "setNull": return "SET NULL";
239
- case "setDefault": return "SET DEFAULT";
240
- default: return "NO ACTION";
241
- }
242
- }
243
- function sqliteTypeFromDesignType(designType) {
244
- switch (designType) {
245
- case "number":
246
- case "integer": return "REAL";
247
- case "boolean": return "INTEGER";
248
- case "string": return "TEXT";
249
- default: return "TEXT";
250
- }
251
- }
252
- function buildProjection(select) {
253
- const fields = select?.asArray;
254
- if (!fields) return "*";
255
- let sql = "";
256
- for (let i = 0; i < fields.length; i++) {
257
- if (i > 0) sql += ", ";
258
- sql += `"${esc$1(fields[i])}"`;
259
- }
260
- return sql || "*";
261
- }
262
- function esc$1(name) {
263
- return name.replace(/"/g, "\"\"");
264
- }
265
- /**
266
- * Converts a JS value to a SQLite-compatible value.
267
- * Objects and arrays are stored as JSON strings.
268
- */ function toSqliteValue$1(value) {
269
- if (value === undefined) return null;
270
- if (value === null) return null;
271
- if (typeof value === "object") return JSON.stringify(value);
272
- if (typeof value === "boolean") return value ? 1 : 0;
273
- return value;
274
- }
275
-
276
352
  //#endregion
277
353
  //#region packages/db-sqlite/src/sqlite-adapter.ts
278
- function _define_property$1(obj, key, value) {
354
+ function _define_property(obj, key, value) {
279
355
  if (key in obj) Object.defineProperty(obj, key, {
280
356
  value,
281
357
  enumerable: true,
@@ -286,6 +362,19 @@ else obj[key] = value;
286
362
  return obj;
287
363
  }
288
364
  var SqliteAdapter = class extends BaseDbAdapter {
365
+ async _beginTransaction() {
366
+ this._log("BEGIN");
367
+ this.driver.exec("BEGIN");
368
+ return undefined;
369
+ }
370
+ async _commitTransaction() {
371
+ this._log("COMMIT");
372
+ this.driver.exec("COMMIT");
373
+ }
374
+ async _rollbackTransaction() {
375
+ this._log("ROLLBACK");
376
+ this.driver.exec("ROLLBACK");
377
+ }
289
378
  /** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
290
379
  return super.resolveTableName(false);
291
380
  }
@@ -294,27 +383,24 @@ var SqliteAdapter = class extends BaseDbAdapter {
294
383
  }
295
384
  async insertOne(data) {
296
385
  const { sql, params } = buildInsert(this.resolveTableName(), data);
386
+ this._log(sql, params);
297
387
  const result = this.driver.run(sql, params);
298
388
  return { insertedId: result.lastInsertRowid };
299
389
  }
300
390
  async insertMany(data) {
301
- const ids = [];
302
- this.driver.exec("BEGIN");
303
- try {
391
+ return this.withTransaction(async () => {
392
+ const ids = [];
304
393
  for (const row of data) {
305
394
  const { sql, params } = buildInsert(this.resolveTableName(), row);
395
+ this._log(sql, params);
306
396
  const result = this.driver.run(sql, params);
307
397
  ids.push(result.lastInsertRowid);
308
398
  }
309
- this.driver.exec("COMMIT");
310
- } catch (error) {
311
- this.driver.exec("ROLLBACK");
312
- throw error;
313
- }
314
- return {
315
- insertedCount: ids.length,
316
- insertedIds: ids
317
- };
399
+ return {
400
+ insertedCount: ids.length,
401
+ insertedIds: ids
402
+ };
403
+ });
318
404
  }
319
405
  async findOne(query) {
320
406
  const where = buildWhere(query.filter);
@@ -323,17 +409,20 @@ var SqliteAdapter = class extends BaseDbAdapter {
323
409
  $limit: 1
324
410
  };
325
411
  const { sql, params } = buildSelect(this.resolveTableName(), where, controls);
412
+ this._log(sql, params);
326
413
  return this.driver.get(sql, params);
327
414
  }
328
415
  async findMany(query) {
329
416
  const where = buildWhere(query.filter);
330
417
  const { sql, params } = buildSelect(this.resolveTableName(), where, query.controls);
418
+ this._log(sql, params);
331
419
  return this.driver.all(sql, params);
332
420
  }
333
421
  async count(query) {
334
422
  const where = buildWhere(query.filter);
335
423
  const tableName = this.resolveTableName();
336
424
  const sql = `SELECT COUNT(*) as cnt FROM "${esc(tableName)}" WHERE ${where.sql}`;
425
+ this._log(sql, where.params);
337
426
  const row = this.driver.get(sql, where.params);
338
427
  return row?.cnt ?? 0;
339
428
  }
@@ -347,7 +436,9 @@ var SqliteAdapter = class extends BaseDbAdapter {
347
436
  setParams.push(toSqliteValue(value));
348
437
  }
349
438
  const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
350
- const result = this.driver.run(sql, [...setParams, ...where.params]);
439
+ const allParams = [...setParams, ...where.params];
440
+ this._log(sql, allParams);
441
+ const result = this.driver.run(sql, allParams);
351
442
  return {
352
443
  matchedCount: result.changes,
353
444
  modifiedCount: result.changes
@@ -356,6 +447,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
356
447
  async updateMany(filter, data) {
357
448
  const where = buildWhere(filter);
358
449
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
450
+ this._log(sql, params);
359
451
  const result = this.driver.run(sql, params);
360
452
  return {
361
453
  matchedCount: result.changes,
@@ -365,27 +457,22 @@ var SqliteAdapter = class extends BaseDbAdapter {
365
457
  async replaceOne(filter, data) {
366
458
  const where = buildWhere(filter);
367
459
  const tableName = this.resolveTableName();
368
- this.driver.exec("BEGIN");
369
- try {
370
- const delSql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
371
- const delResult = this.driver.run(delSql, where.params);
372
- if (delResult.changes > 0) {
373
- const { sql, params } = buildInsert(tableName, data);
374
- this.driver.run(sql, params);
375
- }
376
- this.driver.exec("COMMIT");
377
- return {
378
- matchedCount: delResult.changes,
379
- modifiedCount: delResult.changes
380
- };
381
- } catch (error) {
382
- this.driver.exec("ROLLBACK");
383
- throw error;
384
- }
460
+ const limitedWhere = {
461
+ sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
462
+ params: where.params
463
+ };
464
+ const { sql, params } = buildUpdate(tableName, data, limitedWhere);
465
+ this._log(sql, params);
466
+ const result = this.driver.run(sql, params);
467
+ return {
468
+ matchedCount: result.changes,
469
+ modifiedCount: result.changes
470
+ };
385
471
  }
386
472
  async replaceMany(filter, data) {
387
473
  const where = buildWhere(filter);
388
474
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
475
+ this._log(sql, params);
389
476
  const result = this.driver.run(sql, params);
390
477
  return {
391
478
  matchedCount: result.changes,
@@ -396,19 +483,115 @@ var SqliteAdapter = class extends BaseDbAdapter {
396
483
  const where = buildWhere(filter);
397
484
  const tableName = this.resolveTableName();
398
485
  const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
486
+ this._log(sql, where.params);
399
487
  const result = this.driver.run(sql, where.params);
400
488
  return { deletedCount: result.changes };
401
489
  }
402
490
  async deleteMany(filter) {
403
491
  const where = buildWhere(filter);
404
492
  const { sql, params } = buildDelete(this.resolveTableName(), where);
493
+ this._log(sql, params);
405
494
  const result = this.driver.run(sql, params);
406
495
  return { deletedCount: result.changes };
407
496
  }
408
497
  async ensureTable() {
498
+ if (this._table instanceof AtscriptDbView) return this.ensureView();
409
499
  const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys);
500
+ this._log(sql);
410
501
  this.driver.exec(sql);
411
502
  }
503
+ async ensureView() {
504
+ const view = this._table;
505
+ const sql = buildCreateView(this.resolveTableName(), view.viewPlan, view.getViewColumnMappings(), (ref) => view.resolveFieldRef(ref));
506
+ this._log(sql);
507
+ this.driver.exec(sql);
508
+ }
509
+ async getExistingColumns() {
510
+ return this.getExistingColumnsForTable(this.resolveTableName());
511
+ }
512
+ async syncColumns(diff) {
513
+ const tableName = this.resolveTableName();
514
+ const added = [];
515
+ const renamed = [];
516
+ for (const { field, oldName } of diff.renamed ?? []) {
517
+ const ddl = `ALTER TABLE "${esc(tableName)}" RENAME COLUMN "${esc(oldName)}" TO "${esc(field.physicalName)}"`;
518
+ this._log(ddl);
519
+ this.driver.exec(ddl);
520
+ renamed.push(field.physicalName);
521
+ }
522
+ for (const field of diff.added) {
523
+ const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
524
+ let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
525
+ if (!field.optional && !field.isPrimaryKey) ddl += ` NOT NULL DEFAULT ${defaultValueForType(field.designType)}`;
526
+ this._log(ddl);
527
+ this.driver.exec(ddl);
528
+ added.push(field.physicalName);
529
+ }
530
+ return {
531
+ added,
532
+ renamed
533
+ };
534
+ }
535
+ async recreateTable() {
536
+ const tableName = this.resolveTableName();
537
+ const tempName = `${tableName}__tmp_${Date.now()}`;
538
+ const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
539
+ this._log(createSql);
540
+ this.driver.exec(createSql);
541
+ const oldCols = (await this.getExistingColumns()).map((c) => c.name);
542
+ const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
543
+ const oldColSet = new Set(oldCols);
544
+ const commonCols = newCols.filter((c) => oldColSet.has(c));
545
+ if (commonCols.length > 0) {
546
+ const cols = commonCols.map((c) => `"${esc(c)}"`).join(", ");
547
+ const copySql = `INSERT INTO "${esc(tempName)}" (${cols}) SELECT ${cols} FROM "${esc(tableName)}"`;
548
+ this._log(copySql);
549
+ this.driver.exec(copySql);
550
+ }
551
+ this.driver.exec(`DROP TABLE "${esc(tableName)}"`);
552
+ this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
553
+ }
554
+ async dropTable() {
555
+ const tableName = this.resolveTableName();
556
+ const ddl = `DROP TABLE IF EXISTS "${esc(tableName)}"`;
557
+ this._log(ddl);
558
+ this.driver.exec(ddl);
559
+ }
560
+ async dropColumns(columns) {
561
+ await this.withTransaction(async () => {
562
+ const tableName = this.resolveTableName();
563
+ for (const col of columns) {
564
+ const ddl = `ALTER TABLE "${esc(tableName)}" DROP COLUMN "${esc(col)}"`;
565
+ this._log(ddl);
566
+ this.driver.exec(ddl);
567
+ }
568
+ });
569
+ }
570
+ async dropTableByName(tableName) {
571
+ const ddl = `DROP TABLE IF EXISTS "${esc(tableName)}"`;
572
+ this._log(ddl);
573
+ this.driver.exec(ddl);
574
+ }
575
+ async dropViewByName(viewName) {
576
+ const ddl = `DROP VIEW IF EXISTS "${esc(viewName)}"`;
577
+ this._log(ddl);
578
+ this.driver.exec(ddl);
579
+ }
580
+ async renameTable(oldName) {
581
+ const newName = this.resolveTableName();
582
+ const ddl = `ALTER TABLE "${esc(oldName)}" RENAME TO "${esc(newName)}"`;
583
+ this._log(ddl);
584
+ this.driver.exec(ddl);
585
+ }
586
+ async getExistingColumnsForTable(tableName) {
587
+ const rows = this.driver.all(`PRAGMA table_info("${esc(tableName)}")`);
588
+ return rows.map((r) => ({
589
+ name: r.name,
590
+ type: r.type,
591
+ notnull: r.notnull === 1,
592
+ pk: r.pk > 0
593
+ }));
594
+ }
412
595
  async syncIndexes() {
413
596
  const tableName = this.resolveTableName();
414
597
  await this.syncIndexesWithDiff({
@@ -416,73 +599,38 @@ var SqliteAdapter = class extends BaseDbAdapter {
416
599
  createIndex: async (index) => {
417
600
  const unique = index.type === "unique" ? "UNIQUE " : "";
418
601
  const cols = index.fields.map((f) => `"${esc(f.name)}" ${f.sort === "desc" ? "DESC" : "ASC"}`).join(", ");
419
- this.driver.exec(`CREATE ${unique}INDEX IF NOT EXISTS "${esc(index.key)}" ON "${esc(tableName)}" (${cols})`);
602
+ const sql = `CREATE ${unique}INDEX IF NOT EXISTS "${esc(index.key)}" ON "${esc(tableName)}" (${cols})`;
603
+ this._log(sql);
604
+ this.driver.exec(sql);
420
605
  },
421
606
  dropIndex: async (name) => {
422
- this.driver.exec(`DROP INDEX IF EXISTS "${esc(name)}"`);
607
+ const sql = `DROP INDEX IF EXISTS "${esc(name)}"`;
608
+ this._log(sql);
609
+ this.driver.exec(sql);
423
610
  },
424
611
  shouldSkipType: (type) => type === "fulltext"
425
612
  });
426
613
  }
427
614
  constructor(driver) {
428
- super(), _define_property$1(this, "driver", void 0), this.driver = driver;
429
- driver.exec("PRAGMA foreign_keys = ON");
615
+ super(), _define_property(this, "driver", void 0), this.driver = driver;
616
+ this.driver.exec("PRAGMA foreign_keys = ON");
430
617
  }
431
618
  };
432
- function esc(name) {
433
- return name.replace(/"/g, "\"\"");
434
- }
435
- function toSqliteValue(value) {
436
- if (value === undefined) return null;
437
- if (value === null) return null;
438
- if (typeof value === "object") return JSON.stringify(value);
439
- if (typeof value === "boolean") return value ? 1 : 0;
440
- return value;
619
+ /** Returns a safe SQLite DEFAULT literal for a given design type. */ function defaultValueForType(designType) {
620
+ switch (designType) {
621
+ case "number":
622
+ case "integer": return "0";
623
+ case "boolean": return "0";
624
+ default: return "''";
625
+ }
441
626
  }
442
627
 
443
628
  //#endregion
444
- //#region packages/db-sqlite/src/better-sqlite3-driver.ts
445
- function _define_property(obj, key, value) {
446
- if (key in obj) Object.defineProperty(obj, key, {
447
- value,
448
- enumerable: true,
449
- configurable: true,
450
- writable: true
451
- });
452
- else obj[key] = value;
453
- return obj;
629
+ //#region packages/db-sqlite/src/index.ts
630
+ function createAdapter(connection, options) {
631
+ const driver = new BetterSqlite3Driver(connection, options);
632
+ return new DbSpace(() => new SqliteAdapter(driver));
454
633
  }
455
- var BetterSqlite3Driver = class {
456
- run(sql, params) {
457
- const stmt = this.db.prepare(sql);
458
- const result = params ? stmt.run(...params) : stmt.run();
459
- return {
460
- changes: result.changes,
461
- lastInsertRowid: result.lastInsertRowid
462
- };
463
- }
464
- all(sql, params) {
465
- const stmt = this.db.prepare(sql);
466
- return params ? stmt.all(...params) : stmt.all();
467
- }
468
- get(sql, params) {
469
- const stmt = this.db.prepare(sql);
470
- return (params ? stmt.get(...params) : stmt.get()) ?? null;
471
- }
472
- exec(sql) {
473
- this.db.exec(sql);
474
- }
475
- close() {
476
- this.db.close();
477
- }
478
- constructor(pathOrDb, options) {
479
- _define_property(this, "db", void 0);
480
- if (typeof pathOrDb === "string") {
481
- const Database = __require("better-sqlite3");
482
- this.db = new Database(pathOrDb, options);
483
- } else this.db = pathOrDb;
484
- }
485
- };
486
634
 
487
635
  //#endregion
488
- export { BetterSqlite3Driver, SqliteAdapter, buildWhere };
636
+ export { BetterSqlite3Driver, SqliteAdapter, buildWhere, createAdapter };