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