@atscript/db-sqlite 0.1.33 → 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,111 +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) {
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
- return `CREATE TABLE IF NOT EXISTS "${esc$1(table)}" (${colDefs.join(", ")})`;
225
- }
226
- function sqliteTypeFromDesignType(designType) {
227
- switch (designType) {
228
- case "number":
229
- case "integer": return "REAL";
230
- case "boolean": return "INTEGER";
231
- case "string": return "TEXT";
232
- default: return "TEXT";
233
- }
234
- }
235
- function buildProjection(select) {
236
- const fields = select?.asArray;
237
- if (!fields) return "*";
238
- let sql = "";
239
- for (let i = 0; i < fields.length; i++) {
240
- if (i > 0) sql += ", ";
241
- sql += `"${esc$1(fields[i])}"`;
242
- }
243
- return sql || "*";
244
- }
245
- function esc$1(name) {
246
- return name.replace(/"/g, "\"\"");
247
- }
248
- /**
249
- * Converts a JS value to a SQLite-compatible value.
250
- * Objects and arrays are stored as JSON strings.
251
- */ function toSqliteValue$1(value) {
252
- if (value === undefined) return null;
253
- if (value === null) return null;
254
- if (typeof value === "object") return JSON.stringify(value);
255
- if (typeof value === "boolean") return value ? 1 : 0;
256
- return value;
257
- }
258
-
259
352
  //#endregion
260
353
  //#region packages/db-sqlite/src/sqlite-adapter.ts
261
- function _define_property$1(obj, key, value) {
354
+ function _define_property(obj, key, value) {
262
355
  if (key in obj) Object.defineProperty(obj, key, {
263
356
  value,
264
357
  enumerable: true,
@@ -269,6 +362,19 @@ else obj[key] = value;
269
362
  return obj;
270
363
  }
271
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
+ }
272
378
  /** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
273
379
  return super.resolveTableName(false);
274
380
  }
@@ -277,27 +383,24 @@ var SqliteAdapter = class extends BaseDbAdapter {
277
383
  }
278
384
  async insertOne(data) {
279
385
  const { sql, params } = buildInsert(this.resolveTableName(), data);
386
+ this._log(sql, params);
280
387
  const result = this.driver.run(sql, params);
281
388
  return { insertedId: result.lastInsertRowid };
282
389
  }
283
390
  async insertMany(data) {
284
- const ids = [];
285
- this.driver.exec("BEGIN");
286
- try {
391
+ return this.withTransaction(async () => {
392
+ const ids = [];
287
393
  for (const row of data) {
288
394
  const { sql, params } = buildInsert(this.resolveTableName(), row);
395
+ this._log(sql, params);
289
396
  const result = this.driver.run(sql, params);
290
397
  ids.push(result.lastInsertRowid);
291
398
  }
292
- this.driver.exec("COMMIT");
293
- } catch (error) {
294
- this.driver.exec("ROLLBACK");
295
- throw error;
296
- }
297
- return {
298
- insertedCount: ids.length,
299
- insertedIds: ids
300
- };
399
+ return {
400
+ insertedCount: ids.length,
401
+ insertedIds: ids
402
+ };
403
+ });
301
404
  }
302
405
  async findOne(query) {
303
406
  const where = buildWhere(query.filter);
@@ -306,17 +409,20 @@ var SqliteAdapter = class extends BaseDbAdapter {
306
409
  $limit: 1
307
410
  };
308
411
  const { sql, params } = buildSelect(this.resolveTableName(), where, controls);
412
+ this._log(sql, params);
309
413
  return this.driver.get(sql, params);
310
414
  }
311
415
  async findMany(query) {
312
416
  const where = buildWhere(query.filter);
313
417
  const { sql, params } = buildSelect(this.resolveTableName(), where, query.controls);
418
+ this._log(sql, params);
314
419
  return this.driver.all(sql, params);
315
420
  }
316
421
  async count(query) {
317
422
  const where = buildWhere(query.filter);
318
423
  const tableName = this.resolveTableName();
319
424
  const sql = `SELECT COUNT(*) as cnt FROM "${esc(tableName)}" WHERE ${where.sql}`;
425
+ this._log(sql, where.params);
320
426
  const row = this.driver.get(sql, where.params);
321
427
  return row?.cnt ?? 0;
322
428
  }
@@ -330,7 +436,9 @@ var SqliteAdapter = class extends BaseDbAdapter {
330
436
  setParams.push(toSqliteValue(value));
331
437
  }
332
438
  const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
333
- 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);
334
442
  return {
335
443
  matchedCount: result.changes,
336
444
  modifiedCount: result.changes
@@ -339,6 +447,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
339
447
  async updateMany(filter, data) {
340
448
  const where = buildWhere(filter);
341
449
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
450
+ this._log(sql, params);
342
451
  const result = this.driver.run(sql, params);
343
452
  return {
344
453
  matchedCount: result.changes,
@@ -348,27 +457,22 @@ var SqliteAdapter = class extends BaseDbAdapter {
348
457
  async replaceOne(filter, data) {
349
458
  const where = buildWhere(filter);
350
459
  const tableName = this.resolveTableName();
351
- this.driver.exec("BEGIN");
352
- try {
353
- const delSql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
354
- const delResult = this.driver.run(delSql, where.params);
355
- if (delResult.changes > 0) {
356
- const { sql, params } = buildInsert(tableName, data);
357
- this.driver.run(sql, params);
358
- }
359
- this.driver.exec("COMMIT");
360
- return {
361
- matchedCount: delResult.changes,
362
- modifiedCount: delResult.changes
363
- };
364
- } catch (error) {
365
- this.driver.exec("ROLLBACK");
366
- throw error;
367
- }
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
+ };
368
471
  }
369
472
  async replaceMany(filter, data) {
370
473
  const where = buildWhere(filter);
371
474
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
475
+ this._log(sql, params);
372
476
  const result = this.driver.run(sql, params);
373
477
  return {
374
478
  matchedCount: result.changes,
@@ -379,19 +483,115 @@ var SqliteAdapter = class extends BaseDbAdapter {
379
483
  const where = buildWhere(filter);
380
484
  const tableName = this.resolveTableName();
381
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);
382
487
  const result = this.driver.run(sql, where.params);
383
488
  return { deletedCount: result.changes };
384
489
  }
385
490
  async deleteMany(filter) {
386
491
  const where = buildWhere(filter);
387
492
  const { sql, params } = buildDelete(this.resolveTableName(), where);
493
+ this._log(sql, params);
388
494
  const result = this.driver.run(sql, params);
389
495
  return { deletedCount: result.changes };
390
496
  }
391
497
  async ensureTable() {
392
- const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors);
498
+ if (this._table instanceof AtscriptDbView) return this.ensureView();
499
+ const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys);
500
+ this._log(sql);
393
501
  this.driver.exec(sql);
394
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
+ }
395
595
  async syncIndexes() {
396
596
  const tableName = this.resolveTableName();
397
597
  await this.syncIndexesWithDiff({
@@ -399,72 +599,38 @@ var SqliteAdapter = class extends BaseDbAdapter {
399
599
  createIndex: async (index) => {
400
600
  const unique = index.type === "unique" ? "UNIQUE " : "";
401
601
  const cols = index.fields.map((f) => `"${esc(f.name)}" ${f.sort === "desc" ? "DESC" : "ASC"}`).join(", ");
402
- 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);
403
605
  },
404
606
  dropIndex: async (name) => {
405
- 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);
406
610
  },
407
611
  shouldSkipType: (type) => type === "fulltext"
408
612
  });
409
613
  }
410
614
  constructor(driver) {
411
- super(), _define_property$1(this, "driver", void 0), this.driver = driver;
615
+ super(), _define_property(this, "driver", void 0), this.driver = driver;
616
+ this.driver.exec("PRAGMA foreign_keys = ON");
412
617
  }
413
618
  };
414
- function esc(name) {
415
- return name.replace(/"/g, "\"\"");
416
- }
417
- function toSqliteValue(value) {
418
- if (value === undefined) return null;
419
- if (value === null) return null;
420
- if (typeof value === "object") return JSON.stringify(value);
421
- if (typeof value === "boolean") return value ? 1 : 0;
422
- 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
+ }
423
626
  }
424
627
 
425
628
  //#endregion
426
- //#region packages/db-sqlite/src/better-sqlite3-driver.ts
427
- function _define_property(obj, key, value) {
428
- if (key in obj) Object.defineProperty(obj, key, {
429
- value,
430
- enumerable: true,
431
- configurable: true,
432
- writable: true
433
- });
434
- else obj[key] = value;
435
- 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));
436
633
  }
437
- var BetterSqlite3Driver = class {
438
- run(sql, params) {
439
- const stmt = this.db.prepare(sql);
440
- const result = params ? stmt.run(...params) : stmt.run();
441
- return {
442
- changes: result.changes,
443
- lastInsertRowid: result.lastInsertRowid
444
- };
445
- }
446
- all(sql, params) {
447
- const stmt = this.db.prepare(sql);
448
- return params ? stmt.all(...params) : stmt.all();
449
- }
450
- get(sql, params) {
451
- const stmt = this.db.prepare(sql);
452
- return (params ? stmt.get(...params) : stmt.get()) ?? null;
453
- }
454
- exec(sql) {
455
- this.db.exec(sql);
456
- }
457
- close() {
458
- this.db.close();
459
- }
460
- constructor(pathOrDb, options) {
461
- _define_property(this, "db", void 0);
462
- if (typeof pathOrDb === "string") {
463
- const Database = __require("better-sqlite3");
464
- this.db = new Database(pathOrDb, options);
465
- } else this.db = pathOrDb;
466
- }
467
- };
468
634
 
469
635
  //#endregion
470
- export { BetterSqlite3Driver, SqliteAdapter, buildWhere };
636
+ export { BetterSqlite3Driver, SqliteAdapter, buildWhere, createAdapter };