@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.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,128 +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, foreignKeys) {
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
- if (foreignKeys) for (const fk of foreignKeys.values()) {
242
- const localCols = fk.fields.map((f) => `"${esc$1(f)}"`).join(", ");
243
- const targetCols = fk.targetFields.map((f) => `"${esc$1(f)}"`).join(", ");
244
- let constraint = `FOREIGN KEY (${localCols}) REFERENCES "${esc$1(fk.targetTable)}" (${targetCols})`;
245
- if (fk.onDelete) constraint += ` ON DELETE ${refActionToSql(fk.onDelete)}`;
246
- if (fk.onUpdate) constraint += ` ON UPDATE ${refActionToSql(fk.onUpdate)}`;
247
- colDefs.push(constraint);
248
- }
249
- return `CREATE TABLE IF NOT EXISTS "${esc$1(table)}" (${colDefs.join(", ")})`;
250
- }
251
- function refActionToSql(action) {
252
- switch (action) {
253
- case "cascade": return "CASCADE";
254
- case "restrict": return "RESTRICT";
255
- case "setNull": return "SET NULL";
256
- case "setDefault": return "SET DEFAULT";
257
- default: return "NO ACTION";
258
- }
259
- }
260
- function sqliteTypeFromDesignType(designType) {
261
- switch (designType) {
262
- case "number":
263
- case "integer": return "REAL";
264
- case "boolean": return "INTEGER";
265
- case "string": return "TEXT";
266
- default: return "TEXT";
267
- }
268
- }
269
- function buildProjection(select) {
270
- const fields = select?.asArray;
271
- if (!fields) return "*";
272
- let sql = "";
273
- for (let i = 0; i < fields.length; i++) {
274
- if (i > 0) sql += ", ";
275
- sql += `"${esc$1(fields[i])}"`;
276
- }
277
- return sql || "*";
278
- }
279
- function esc$1(name) {
280
- return name.replace(/"/g, "\"\"");
281
- }
282
- /**
283
- * Converts a JS value to a SQLite-compatible value.
284
- * Objects and arrays are stored as JSON strings.
285
- */ function toSqliteValue$1(value) {
286
- if (value === undefined) return null;
287
- if (value === null) return null;
288
- if (typeof value === "object") return JSON.stringify(value);
289
- if (typeof value === "boolean") return value ? 1 : 0;
290
- return value;
291
- }
292
-
293
369
  //#endregion
294
370
  //#region packages/db-sqlite/src/sqlite-adapter.ts
295
- function _define_property$1(obj, key, value) {
371
+ function _define_property(obj, key, value) {
296
372
  if (key in obj) Object.defineProperty(obj, key, {
297
373
  value,
298
374
  enumerable: true,
@@ -303,6 +379,19 @@ else obj[key] = value;
303
379
  return obj;
304
380
  }
305
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
+ }
306
395
  /** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
307
396
  return super.resolveTableName(false);
308
397
  }
@@ -311,27 +400,24 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
311
400
  }
312
401
  async insertOne(data) {
313
402
  const { sql, params } = buildInsert(this.resolveTableName(), data);
403
+ this._log(sql, params);
314
404
  const result = this.driver.run(sql, params);
315
405
  return { insertedId: result.lastInsertRowid };
316
406
  }
317
407
  async insertMany(data) {
318
- const ids = [];
319
- this.driver.exec("BEGIN");
320
- try {
408
+ return this.withTransaction(async () => {
409
+ const ids = [];
321
410
  for (const row of data) {
322
411
  const { sql, params } = buildInsert(this.resolveTableName(), row);
412
+ this._log(sql, params);
323
413
  const result = this.driver.run(sql, params);
324
414
  ids.push(result.lastInsertRowid);
325
415
  }
326
- this.driver.exec("COMMIT");
327
- } catch (error) {
328
- this.driver.exec("ROLLBACK");
329
- throw error;
330
- }
331
- return {
332
- insertedCount: ids.length,
333
- insertedIds: ids
334
- };
416
+ return {
417
+ insertedCount: ids.length,
418
+ insertedIds: ids
419
+ };
420
+ });
335
421
  }
336
422
  async findOne(query) {
337
423
  const where = buildWhere(query.filter);
@@ -340,17 +426,20 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
340
426
  $limit: 1
341
427
  };
342
428
  const { sql, params } = buildSelect(this.resolveTableName(), where, controls);
429
+ this._log(sql, params);
343
430
  return this.driver.get(sql, params);
344
431
  }
345
432
  async findMany(query) {
346
433
  const where = buildWhere(query.filter);
347
434
  const { sql, params } = buildSelect(this.resolveTableName(), where, query.controls);
435
+ this._log(sql, params);
348
436
  return this.driver.all(sql, params);
349
437
  }
350
438
  async count(query) {
351
439
  const where = buildWhere(query.filter);
352
440
  const tableName = this.resolveTableName();
353
441
  const sql = `SELECT COUNT(*) as cnt FROM "${esc(tableName)}" WHERE ${where.sql}`;
442
+ this._log(sql, where.params);
354
443
  const row = this.driver.get(sql, where.params);
355
444
  return row?.cnt ?? 0;
356
445
  }
@@ -364,7 +453,9 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
364
453
  setParams.push(toSqliteValue(value));
365
454
  }
366
455
  const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
367
- 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);
368
459
  return {
369
460
  matchedCount: result.changes,
370
461
  modifiedCount: result.changes
@@ -373,6 +464,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
373
464
  async updateMany(filter, data) {
374
465
  const where = buildWhere(filter);
375
466
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
467
+ this._log(sql, params);
376
468
  const result = this.driver.run(sql, params);
377
469
  return {
378
470
  matchedCount: result.changes,
@@ -382,27 +474,22 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
382
474
  async replaceOne(filter, data) {
383
475
  const where = buildWhere(filter);
384
476
  const tableName = this.resolveTableName();
385
- this.driver.exec("BEGIN");
386
- try {
387
- const delSql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
388
- const delResult = this.driver.run(delSql, where.params);
389
- if (delResult.changes > 0) {
390
- const { sql, params } = buildInsert(tableName, data);
391
- this.driver.run(sql, params);
392
- }
393
- this.driver.exec("COMMIT");
394
- return {
395
- matchedCount: delResult.changes,
396
- modifiedCount: delResult.changes
397
- };
398
- } catch (error) {
399
- this.driver.exec("ROLLBACK");
400
- throw error;
401
- }
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
+ };
402
488
  }
403
489
  async replaceMany(filter, data) {
404
490
  const where = buildWhere(filter);
405
491
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
492
+ this._log(sql, params);
406
493
  const result = this.driver.run(sql, params);
407
494
  return {
408
495
  matchedCount: result.changes,
@@ -413,19 +500,115 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
413
500
  const where = buildWhere(filter);
414
501
  const tableName = this.resolveTableName();
415
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);
416
504
  const result = this.driver.run(sql, where.params);
417
505
  return { deletedCount: result.changes };
418
506
  }
419
507
  async deleteMany(filter) {
420
508
  const where = buildWhere(filter);
421
509
  const { sql, params } = buildDelete(this.resolveTableName(), where);
510
+ this._log(sql, params);
422
511
  const result = this.driver.run(sql, params);
423
512
  return { deletedCount: result.changes };
424
513
  }
425
514
  async ensureTable() {
515
+ if (this._table instanceof __atscript_utils_db.AtscriptDbView) return this.ensureView();
426
516
  const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys);
517
+ this._log(sql);
427
518
  this.driver.exec(sql);
428
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);
524
+ this.driver.exec(sql);
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
+ }
429
612
  async syncIndexes() {
430
613
  const tableName = this.resolveTableName();
431
614
  await this.syncIndexesWithDiff({
@@ -433,75 +616,41 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
433
616
  createIndex: async (index) => {
434
617
  const unique = index.type === "unique" ? "UNIQUE " : "";
435
618
  const cols = index.fields.map((f) => `"${esc(f.name)}" ${f.sort === "desc" ? "DESC" : "ASC"}`).join(", ");
436
- 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);
437
622
  },
438
623
  dropIndex: async (name) => {
439
- 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);
440
627
  },
441
628
  shouldSkipType: (type) => type === "fulltext"
442
629
  });
443
630
  }
444
631
  constructor(driver) {
445
- super(), _define_property$1(this, "driver", void 0), this.driver = driver;
446
- driver.exec("PRAGMA foreign_keys = ON");
632
+ super(), _define_property(this, "driver", void 0), this.driver = driver;
633
+ this.driver.exec("PRAGMA foreign_keys = ON");
447
634
  }
448
635
  };
449
- function esc(name) {
450
- return name.replace(/"/g, "\"\"");
451
- }
452
- function toSqliteValue(value) {
453
- if (value === undefined) return null;
454
- if (value === null) return null;
455
- if (typeof value === "object") return JSON.stringify(value);
456
- if (typeof value === "boolean") return value ? 1 : 0;
457
- 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
+ }
458
643
  }
459
644
 
460
645
  //#endregion
461
- //#region packages/db-sqlite/src/better-sqlite3-driver.ts
462
- function _define_property(obj, key, value) {
463
- if (key in obj) Object.defineProperty(obj, key, {
464
- value,
465
- enumerable: true,
466
- configurable: true,
467
- writable: true
468
- });
469
- else obj[key] = value;
470
- 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));
471
650
  }
472
- var BetterSqlite3Driver = class {
473
- run(sql, params) {
474
- const stmt = this.db.prepare(sql);
475
- const result = params ? stmt.run(...params) : stmt.run();
476
- return {
477
- changes: result.changes,
478
- lastInsertRowid: result.lastInsertRowid
479
- };
480
- }
481
- all(sql, params) {
482
- const stmt = this.db.prepare(sql);
483
- return params ? stmt.all(...params) : stmt.all();
484
- }
485
- get(sql, params) {
486
- const stmt = this.db.prepare(sql);
487
- return (params ? stmt.get(...params) : stmt.get()) ?? null;
488
- }
489
- exec(sql) {
490
- this.db.exec(sql);
491
- }
492
- close() {
493
- this.db.close();
494
- }
495
- constructor(pathOrDb, options) {
496
- _define_property(this, "db", void 0);
497
- if (typeof pathOrDb === "string") {
498
- const Database = require("better-sqlite3");
499
- this.db = new Database(pathOrDb, options);
500
- } else this.db = pathOrDb;
501
- }
502
- };
503
651
 
504
652
  //#endregion
505
653
  exports.BetterSqlite3Driver = BetterSqlite3Driver
506
654
  exports.SqliteAdapter = SqliteAdapter
507
- exports.buildWhere = buildWhere
655
+ exports.buildWhere = buildWhere
656
+ exports.createAdapter = createAdapter