sqlite-zod-orm 3.24.0 → 3.25.0
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.js +120 -117
- package/package.json +1 -1
- package/src/context.ts +6 -0
- package/src/crud.ts +11 -9
- package/src/database.ts +104 -101
- package/src/query.ts +5 -4
package/dist/index.js
CHANGED
|
@@ -5220,12 +5220,12 @@ function executeProxyQuery(schemas, callback, executor) {
|
|
|
5220
5220
|
function createQueryBuilder(ctx, entityName, initialCols) {
|
|
5221
5221
|
const schema = ctx.schemas[entityName];
|
|
5222
5222
|
const executor = (sql, params, raw) => {
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
return rows;
|
|
5228
|
-
|
|
5223
|
+
return ctx._m(`SQL: ${sql.slice(0, 60)}`, () => {
|
|
5224
|
+
const rows = ctx.db.query(sql).all(...params);
|
|
5225
|
+
if (raw)
|
|
5226
|
+
return rows;
|
|
5227
|
+
return rows.map((row) => ctx.attachMethods(entityName, transformFromStorage(row, schema)));
|
|
5228
|
+
});
|
|
5229
5229
|
};
|
|
5230
5230
|
const singleExecutor = (sql, params, raw) => {
|
|
5231
5231
|
const results = executor(sql, params, raw);
|
|
@@ -5403,9 +5403,9 @@ function insert(ctx, entityName, data) {
|
|
|
5403
5403
|
let inputData = { ...data };
|
|
5404
5404
|
const hooks = ctx.hooks[entityName];
|
|
5405
5405
|
if (hooks?.beforeInsert) {
|
|
5406
|
-
const
|
|
5407
|
-
if (
|
|
5408
|
-
inputData =
|
|
5406
|
+
const result = hooks.beforeInsert(inputData);
|
|
5407
|
+
if (result)
|
|
5408
|
+
inputData = result;
|
|
5409
5409
|
}
|
|
5410
5410
|
const validatedData = asZodObject(schema).passthrough().parse(inputData);
|
|
5411
5411
|
const transformed = transformForStorage(validatedData);
|
|
@@ -5417,10 +5417,12 @@ function insert(ctx, entityName, data) {
|
|
|
5417
5417
|
const columns = Object.keys(transformed);
|
|
5418
5418
|
const quotedCols = columns.map((c) => `"${c}"`);
|
|
5419
5419
|
const sql = columns.length === 0 ? `INSERT INTO "${entityName}" DEFAULT VALUES` : `INSERT INTO "${entityName}" (${quotedCols.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`;
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5420
|
+
let lastId = 0;
|
|
5421
|
+
ctx._m(`SQL: ${sql.slice(0, 40)}`, () => {
|
|
5422
|
+
const result = ctx.db.query(sql).run(...Object.values(transformed));
|
|
5423
|
+
lastId = result.lastInsertRowid;
|
|
5424
|
+
});
|
|
5425
|
+
const newEntity = getById(ctx, entityName, lastId);
|
|
5424
5426
|
if (!newEntity)
|
|
5425
5427
|
throw new Error("Failed to retrieve entity after insertion");
|
|
5426
5428
|
if (hooks?.afterInsert)
|
|
@@ -5445,9 +5447,9 @@ function update(ctx, entityName, id, data) {
|
|
|
5445
5447
|
}
|
|
5446
5448
|
const setClause = Object.keys(transformed).map((key) => `"${key}" = ?`).join(", ");
|
|
5447
5449
|
const sql = `UPDATE "${entityName}" SET ${setClause} WHERE id = ?`;
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5450
|
+
ctx._m(`SQL: UPDATE ${entityName} SET ...`, () => {
|
|
5451
|
+
ctx.db.query(sql).run(...Object.values(transformed), id);
|
|
5452
|
+
});
|
|
5451
5453
|
const updated = getById(ctx, entityName, id);
|
|
5452
5454
|
if (hooks?.afterUpdate && updated)
|
|
5453
5455
|
hooks.afterUpdate(updated);
|
|
@@ -5516,15 +5518,11 @@ function deleteWhere(ctx, entityName, conditions) {
|
|
|
5516
5518
|
if (ctx.softDeletes) {
|
|
5517
5519
|
const now = new Date().toISOString();
|
|
5518
5520
|
const sql2 = `UPDATE "${entityName}" SET "deletedAt" = ? ${clause}`;
|
|
5519
|
-
|
|
5520
|
-
console.log("[satidb]", sql2, [now, ...values]);
|
|
5521
|
-
const result2 = ctx.db.query(sql2).run(now, ...values);
|
|
5521
|
+
const result2 = ctx._m(`SQL: ${sql2.slice(0, 50)}`, () => ctx.db.query(sql2).run(now, ...values));
|
|
5522
5522
|
return result2.changes ?? 0;
|
|
5523
5523
|
}
|
|
5524
5524
|
const sql = `DELETE FROM "${entityName}" ${clause}`;
|
|
5525
|
-
|
|
5526
|
-
console.log("[satidb]", sql, values);
|
|
5527
|
-
const result = ctx.db.query(sql).run(...values);
|
|
5525
|
+
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx.db.query(sql).run(...values));
|
|
5528
5526
|
return result.changes ?? 0;
|
|
5529
5527
|
}
|
|
5530
5528
|
function createDeleteBuilder(ctx, entityName) {
|
|
@@ -5679,7 +5677,8 @@ class _Database {
|
|
|
5679
5677
|
softDeletes: this._softDeletes,
|
|
5680
5678
|
hooks: options.hooks ?? {},
|
|
5681
5679
|
computed: options.computed ?? {},
|
|
5682
|
-
cascade: options.cascade ?? {}
|
|
5680
|
+
cascade: options.cascade ?? {},
|
|
5681
|
+
_m: (label, fn) => this._m(label, fn)
|
|
5683
5682
|
};
|
|
5684
5683
|
this._m("Init tables", () => this.initializeTables());
|
|
5685
5684
|
if (this._reactive)
|
|
@@ -5696,55 +5695,59 @@ class _Database {
|
|
|
5696
5695
|
insertMany: (rows) => this._m(`${entityName}.insertMany(${rows.length})`, () => insertMany(this._ctx, entityName, rows)),
|
|
5697
5696
|
update: (idOrData, data) => {
|
|
5698
5697
|
if (typeof idOrData === "number")
|
|
5699
|
-
return update(this._ctx, entityName, idOrData, data);
|
|
5698
|
+
return this._m(`${entityName}.update(${idOrData})`, () => update(this._ctx, entityName, idOrData, data));
|
|
5700
5699
|
return createUpdateBuilder(this._ctx, entityName, idOrData);
|
|
5701
5700
|
},
|
|
5702
|
-
upsert: (conditions, data) => upsert(this._ctx, entityName, data, conditions),
|
|
5703
|
-
upsertMany: (rows, conditions) => upsertMany(this._ctx, entityName, rows, conditions),
|
|
5704
|
-
findOrCreate: (conditions, defaults) => findOrCreate(this._ctx, entityName, conditions, defaults),
|
|
5701
|
+
upsert: (conditions, data) => this._m(`${entityName}.upsert`, () => upsert(this._ctx, entityName, data, conditions)),
|
|
5702
|
+
upsertMany: (rows, conditions) => this._m(`${entityName}.upsertMany(${rows.length})`, () => upsertMany(this._ctx, entityName, rows, conditions)),
|
|
5703
|
+
findOrCreate: (conditions, defaults) => this._m(`${entityName}.findOrCreate`, () => findOrCreate(this._ctx, entityName, conditions, defaults)),
|
|
5705
5704
|
delete: (id) => {
|
|
5706
5705
|
if (typeof id === "number") {
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
if (
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5706
|
+
return this._m(`${entityName}.delete(${id})`, () => {
|
|
5707
|
+
const hooks = this._ctx.hooks[entityName];
|
|
5708
|
+
if (hooks?.beforeDelete) {
|
|
5709
|
+
const result = hooks.beforeDelete(id);
|
|
5710
|
+
if (result === false)
|
|
5711
|
+
return;
|
|
5712
|
+
}
|
|
5713
|
+
const cascadeTargets = this._ctx.cascade[entityName];
|
|
5714
|
+
if (cascadeTargets) {
|
|
5715
|
+
for (const childTable of cascadeTargets) {
|
|
5716
|
+
const rel = this._ctx.relationships.find((r) => r.type === "belongs-to" && r.from === childTable && r.to === entityName);
|
|
5717
|
+
if (rel) {
|
|
5718
|
+
if (this._softDeletes) {
|
|
5719
|
+
const now = new Date().toISOString();
|
|
5720
|
+
this.db.query(`UPDATE "${childTable}" SET "deletedAt" = ? WHERE "${rel.foreignKey}" = ?`).run(now, id);
|
|
5721
|
+
} else {
|
|
5722
|
+
this.db.query(`DELETE FROM "${childTable}" WHERE "${rel.foreignKey}" = ?`).run(id);
|
|
5723
|
+
}
|
|
5723
5724
|
}
|
|
5724
5725
|
}
|
|
5725
5726
|
}
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5727
|
+
if (this._softDeletes) {
|
|
5728
|
+
const now = new Date().toISOString();
|
|
5729
|
+
this.db.query(`UPDATE "${entityName}" SET "deletedAt" = ? WHERE id = ?`).run(now, id);
|
|
5730
|
+
if (hooks?.afterDelete)
|
|
5731
|
+
hooks.afterDelete(id);
|
|
5732
|
+
return;
|
|
5733
|
+
}
|
|
5734
|
+
return deleteEntity(this._ctx, entityName, id);
|
|
5735
|
+
});
|
|
5735
5736
|
}
|
|
5736
5737
|
return createDeleteBuilder(this._ctx, entityName);
|
|
5737
5738
|
},
|
|
5738
5739
|
restore: (id) => {
|
|
5739
5740
|
if (!this._softDeletes)
|
|
5740
5741
|
throw new Error("restore() requires softDeletes: true");
|
|
5741
|
-
this.
|
|
5742
|
+
this._m(`${entityName}.restore(${id})`, () => {
|
|
5743
|
+
this.db.query(`UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`).run(id);
|
|
5744
|
+
});
|
|
5742
5745
|
},
|
|
5743
5746
|
select: (...cols) => createQueryBuilder(this._ctx, entityName, cols),
|
|
5744
|
-
count: () => {
|
|
5747
|
+
count: () => this._m(`${entityName}.count`, () => {
|
|
5745
5748
|
const row = this.db.query(`SELECT COUNT(*) as count FROM "${entityName}"${this._softDeletes ? ' WHERE "deletedAt" IS NULL' : ""}`).get();
|
|
5746
5749
|
return row?.count ?? 0;
|
|
5747
|
-
},
|
|
5750
|
+
}),
|
|
5748
5751
|
on: (event, callback) => {
|
|
5749
5752
|
return this._registerListener(entityName, event, callback);
|
|
5750
5753
|
},
|
|
@@ -5889,28 +5892,22 @@ class _Database {
|
|
|
5889
5892
|
this.db.query('DELETE FROM "_changes" WHERE id <= ?').run(this._changeWatermark);
|
|
5890
5893
|
}
|
|
5891
5894
|
transaction(callback) {
|
|
5892
|
-
return this.db.transaction(callback)();
|
|
5895
|
+
return this._m("transaction", () => this.db.transaction(callback)());
|
|
5893
5896
|
}
|
|
5894
5897
|
close() {
|
|
5895
5898
|
this._stopPolling();
|
|
5896
5899
|
this.db.close();
|
|
5897
5900
|
}
|
|
5898
5901
|
query(callback) {
|
|
5899
|
-
return executeProxyQuery(this.schemas, callback, (sql, params) => {
|
|
5900
|
-
if (this._debug)
|
|
5901
|
-
console.log("[satidb]", sql, params);
|
|
5902
|
+
return this._m("query(proxy)", () => executeProxyQuery(this.schemas, callback, (sql, params) => {
|
|
5902
5903
|
return this.db.query(sql).all(...params);
|
|
5903
|
-
});
|
|
5904
|
+
}));
|
|
5904
5905
|
}
|
|
5905
5906
|
raw(sql, ...params) {
|
|
5906
|
-
|
|
5907
|
-
console.log("[satidb]", sql, params);
|
|
5908
|
-
return this.db.query(sql).all(...params);
|
|
5907
|
+
return this._m(`raw: ${sql.slice(0, 60)}`, () => this.db.query(sql).all(...params));
|
|
5909
5908
|
}
|
|
5910
5909
|
exec(sql, ...params) {
|
|
5911
|
-
|
|
5912
|
-
console.log("[satidb]", sql, params);
|
|
5913
|
-
this.db.run(sql, ...params);
|
|
5910
|
+
this._m(`exec: ${sql.slice(0, 60)}`, () => this.db.run(sql, ...params));
|
|
5914
5911
|
}
|
|
5915
5912
|
tables() {
|
|
5916
5913
|
return Object.keys(this.schemas);
|
|
@@ -5919,70 +5916,76 @@ class _Database {
|
|
|
5919
5916
|
return this.db.query(`PRAGMA table_info("${tableName}")`).all();
|
|
5920
5917
|
}
|
|
5921
5918
|
dump() {
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5919
|
+
return this._m("dump", () => {
|
|
5920
|
+
const result = {};
|
|
5921
|
+
for (const tableName of Object.keys(this.schemas)) {
|
|
5922
|
+
result[tableName] = this.db.query(`SELECT * FROM "${tableName}"`).all();
|
|
5923
|
+
}
|
|
5924
|
+
return result;
|
|
5925
|
+
});
|
|
5927
5926
|
}
|
|
5928
5927
|
load(data, options) {
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
const
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
const
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5928
|
+
this._m(`load(${Object.keys(data).join(",")})`, () => {
|
|
5929
|
+
const txn = this.db.transaction(() => {
|
|
5930
|
+
for (const [tableName, rows] of Object.entries(data)) {
|
|
5931
|
+
if (!this.schemas[tableName])
|
|
5932
|
+
continue;
|
|
5933
|
+
if (!options?.append) {
|
|
5934
|
+
this.db.run(`DELETE FROM "${tableName}"`);
|
|
5935
|
+
}
|
|
5936
|
+
for (const row of rows) {
|
|
5937
|
+
const cols = Object.keys(row).filter((k) => k !== "id");
|
|
5938
|
+
const placeholders = cols.map(() => "?").join(", ");
|
|
5939
|
+
const values = cols.map((c) => {
|
|
5940
|
+
const v = row[c];
|
|
5941
|
+
if (v !== null && v !== undefined && typeof v === "object" && !(v instanceof Buffer)) {
|
|
5942
|
+
return JSON.stringify(v);
|
|
5943
|
+
}
|
|
5944
|
+
return v;
|
|
5945
|
+
});
|
|
5946
|
+
this.db.query(`INSERT INTO "${tableName}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`).run(...values);
|
|
5947
|
+
}
|
|
5947
5948
|
}
|
|
5948
|
-
}
|
|
5949
|
+
});
|
|
5950
|
+
txn();
|
|
5949
5951
|
});
|
|
5950
|
-
txn();
|
|
5951
5952
|
}
|
|
5952
5953
|
seed(fixtures) {
|
|
5953
5954
|
this.load(fixtures, { append: true });
|
|
5954
5955
|
}
|
|
5955
5956
|
diff() {
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
const
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5957
|
+
return this._m("diff", () => {
|
|
5958
|
+
const result = {};
|
|
5959
|
+
const systemCols = new Set(["id", "createdAt", "updatedAt", "deletedAt"]);
|
|
5960
|
+
for (const [tableName, schema] of Object.entries(this.schemas)) {
|
|
5961
|
+
const schemaFields = getStorableFields(schema);
|
|
5962
|
+
const schemaColMap = new Map(schemaFields.map((f) => [f.name, zodTypeToSqlType(f.type)]));
|
|
5963
|
+
const liveColumns = this.columns(tableName);
|
|
5964
|
+
const liveColMap = new Map(liveColumns.map((c) => [c.name, c.type]));
|
|
5965
|
+
const added = [];
|
|
5966
|
+
const removed = [];
|
|
5967
|
+
const typeChanged = [];
|
|
5968
|
+
for (const [col, expectedType] of schemaColMap) {
|
|
5969
|
+
if (!liveColMap.has(col)) {
|
|
5970
|
+
added.push(col);
|
|
5971
|
+
} else {
|
|
5972
|
+
const actualType = liveColMap.get(col);
|
|
5973
|
+
if (actualType !== expectedType) {
|
|
5974
|
+
typeChanged.push({ column: col, expected: expectedType, actual: actualType });
|
|
5975
|
+
}
|
|
5973
5976
|
}
|
|
5974
5977
|
}
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5978
|
+
for (const col of liveColMap.keys()) {
|
|
5979
|
+
if (!systemCols.has(col) && !schemaColMap.has(col)) {
|
|
5980
|
+
removed.push(col);
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
if (added.length > 0 || removed.length > 0 || typeChanged.length > 0) {
|
|
5984
|
+
result[tableName] = { added, removed, typeChanged };
|
|
5979
5985
|
}
|
|
5980
5986
|
}
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
}
|
|
5984
|
-
}
|
|
5985
|
-
return result;
|
|
5987
|
+
return result;
|
|
5988
|
+
});
|
|
5986
5989
|
}
|
|
5987
5990
|
}
|
|
5988
5991
|
var Database = _Database;
|
package/package.json
CHANGED
package/src/context.ts
CHANGED
|
@@ -40,4 +40,10 @@ export interface DatabaseContext {
|
|
|
40
40
|
|
|
41
41
|
/** Cascade delete config — parent table → list of child tables to auto-delete. */
|
|
42
42
|
cascade: Record<string, string[]>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Conditional measurement helper — wraps fn with measure-fn when debug is on.
|
|
46
|
+
* When debug is off, executes fn directly with zero overhead.
|
|
47
|
+
*/
|
|
48
|
+
_m<T>(label: string, fn: () => T): T;
|
|
43
49
|
}
|
package/src/crud.ts
CHANGED
|
@@ -66,9 +66,12 @@ export function insert<T extends Record<string, any>>(ctx: DatabaseContext, enti
|
|
|
66
66
|
? `INSERT INTO "${entityName}" DEFAULT VALUES`
|
|
67
67
|
: `INSERT INTO "${entityName}" (${quotedCols.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
let lastId = 0;
|
|
70
|
+
ctx._m(`SQL: ${sql.slice(0, 40)}`, () => {
|
|
71
|
+
const result = ctx.db.query(sql).run(...Object.values(transformed));
|
|
72
|
+
lastId = result.lastInsertRowid as number;
|
|
73
|
+
});
|
|
74
|
+
const newEntity = getById(ctx, entityName, lastId);
|
|
72
75
|
if (!newEntity) throw new Error('Failed to retrieve entity after insertion');
|
|
73
76
|
|
|
74
77
|
// afterInsert hook
|
|
@@ -99,8 +102,9 @@ export function update<T extends Record<string, any>>(ctx: DatabaseContext, enti
|
|
|
99
102
|
|
|
100
103
|
const setClause = Object.keys(transformed).map(key => `"${key}" = ?`).join(', ');
|
|
101
104
|
const sql = `UPDATE "${entityName}" SET ${setClause} WHERE id = ?`;
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
ctx._m(`SQL: UPDATE ${entityName} SET ...`, () => {
|
|
106
|
+
ctx.db.query(sql).run(...Object.values(transformed), id);
|
|
107
|
+
});
|
|
104
108
|
|
|
105
109
|
const updated = getById(ctx, entityName, id);
|
|
106
110
|
|
|
@@ -192,14 +196,12 @@ export function deleteWhere(ctx: DatabaseContext, entityName: string, conditions
|
|
|
192
196
|
// Soft delete: set deletedAt instead of removing rows
|
|
193
197
|
const now = new Date().toISOString();
|
|
194
198
|
const sql = `UPDATE "${entityName}" SET "deletedAt" = ? ${clause}`;
|
|
195
|
-
|
|
196
|
-
const result = ctx.db.query(sql).run(now, ...values);
|
|
199
|
+
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx.db.query(sql).run(now, ...values));
|
|
197
200
|
return (result as any).changes ?? 0;
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
const sql = `DELETE FROM "${entityName}" ${clause}`;
|
|
201
|
-
|
|
202
|
-
const result = ctx.db.query(sql).run(...values);
|
|
204
|
+
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx.db.query(sql).run(...values));
|
|
203
205
|
return (result as any).changes ?? 0;
|
|
204
206
|
}
|
|
205
207
|
|
package/src/database.ts
CHANGED
|
@@ -104,6 +104,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
104
104
|
hooks: options.hooks ?? {},
|
|
105
105
|
computed: options.computed ?? {},
|
|
106
106
|
cascade: options.cascade ?? {},
|
|
107
|
+
_m: <T>(label: string, fn: () => T): T => this._m(label, fn),
|
|
107
108
|
};
|
|
108
109
|
|
|
109
110
|
this._m('Init tables', () => this.initializeTables());
|
|
@@ -119,60 +120,62 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
119
120
|
insert: (data) => this._m(`${entityName}.insert`, () => insert(this._ctx, entityName, data)),
|
|
120
121
|
insertMany: (rows: any[]) => this._m(`${entityName}.insertMany(${rows.length})`, () => insertMany(this._ctx, entityName, rows)),
|
|
121
122
|
update: (idOrData: any, data?: any) => {
|
|
122
|
-
if (typeof idOrData === 'number') return update(this._ctx, entityName, idOrData, data);
|
|
123
|
+
if (typeof idOrData === 'number') return this._m(`${entityName}.update(${idOrData})`, () => update(this._ctx, entityName, idOrData, data));
|
|
123
124
|
return createUpdateBuilder(this._ctx, entityName, idOrData);
|
|
124
125
|
},
|
|
125
|
-
upsert: (conditions, data) => upsert(this._ctx, entityName, data, conditions),
|
|
126
|
-
upsertMany: (rows: any[], conditions?: any) => upsertMany(this._ctx, entityName, rows, conditions),
|
|
127
|
-
findOrCreate: (conditions: any, defaults?: any) => findOrCreate(this._ctx, entityName, conditions, defaults),
|
|
126
|
+
upsert: (conditions, data) => this._m(`${entityName}.upsert`, () => upsert(this._ctx, entityName, data, conditions)),
|
|
127
|
+
upsertMany: (rows: any[], conditions?: any) => this._m(`${entityName}.upsertMany(${rows.length})`, () => upsertMany(this._ctx, entityName, rows, conditions)),
|
|
128
|
+
findOrCreate: (conditions: any, defaults?: any) => this._m(`${entityName}.findOrCreate`, () => findOrCreate(this._ctx, entityName, conditions, defaults)),
|
|
128
129
|
delete: ((id?: any) => {
|
|
129
130
|
if (typeof id === 'number') {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
return this._m(`${entityName}.delete(${id})`, () => {
|
|
132
|
+
// beforeDelete hook — return false to cancel
|
|
133
|
+
const hooks = this._ctx.hooks[entityName];
|
|
134
|
+
if (hooks?.beforeDelete) {
|
|
135
|
+
const result = hooks.beforeDelete(id);
|
|
136
|
+
if (result === false) return;
|
|
137
|
+
}
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
139
|
+
// Cascade delete children first
|
|
140
|
+
const cascadeTargets = this._ctx.cascade[entityName];
|
|
141
|
+
if (cascadeTargets) {
|
|
142
|
+
for (const childTable of cascadeTargets) {
|
|
143
|
+
const rel = this._ctx.relationships.find(
|
|
144
|
+
r => r.type === 'belongs-to' && r.from === childTable && r.to === entityName
|
|
145
|
+
);
|
|
146
|
+
if (rel) {
|
|
147
|
+
if (this._softDeletes) {
|
|
148
|
+
const now = new Date().toISOString();
|
|
149
|
+
this.db.query(`UPDATE "${childTable}" SET "deletedAt" = ? WHERE "${rel.foreignKey}" = ?`).run(now, id);
|
|
150
|
+
} else {
|
|
151
|
+
this.db.query(`DELETE FROM "${childTable}" WHERE "${rel.foreignKey}" = ?`).run(id);
|
|
152
|
+
}
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
|
-
}
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
if (this._softDeletes) {
|
|
158
|
+
const now = new Date().toISOString();
|
|
159
|
+
this.db.query(`UPDATE "${entityName}" SET "deletedAt" = ? WHERE id = ?`).run(now, id);
|
|
160
|
+
if (hooks?.afterDelete) hooks.afterDelete(id);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
return deleteEntity(this._ctx, entityName, id);
|
|
164
|
+
});
|
|
163
165
|
}
|
|
164
166
|
return createDeleteBuilder(this._ctx, entityName);
|
|
165
167
|
}) as any,
|
|
166
168
|
restore: ((id: number) => {
|
|
167
169
|
if (!this._softDeletes) throw new Error('restore() requires softDeletes: true');
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
this._m(`${entityName}.restore(${id})`, () => {
|
|
171
|
+
this.db.query(`UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`).run(id);
|
|
172
|
+
});
|
|
170
173
|
}) as any,
|
|
171
174
|
select: (...cols: string[]) => createQueryBuilder(this._ctx, entityName, cols),
|
|
172
|
-
count: () => {
|
|
175
|
+
count: () => this._m(`${entityName}.count`, () => {
|
|
173
176
|
const row = this.db.query(`SELECT COUNT(*) as count FROM "${entityName}"${this._softDeletes ? ' WHERE "deletedAt" IS NULL' : ''}`).get() as any;
|
|
174
177
|
return row?.count ?? 0;
|
|
175
|
-
},
|
|
178
|
+
}),
|
|
176
179
|
on: (event: ChangeEvent, callback: (row: any) => void | Promise<void>) => {
|
|
177
180
|
return this._registerListener(entityName, event, callback);
|
|
178
181
|
},
|
|
@@ -379,7 +382,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
379
382
|
// =========================================================================
|
|
380
383
|
|
|
381
384
|
public transaction<T>(callback: () => T): T {
|
|
382
|
-
return this.db.transaction(callback)();
|
|
385
|
+
return this._m('transaction', () => this.db.transaction(callback)());
|
|
383
386
|
}
|
|
384
387
|
|
|
385
388
|
/** Close the database: stops polling and releases the SQLite handle. */
|
|
@@ -396,14 +399,13 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
396
399
|
public query<T extends Record<string, any> = Record<string, any>>(
|
|
397
400
|
callback: (ctx: { [K in keyof Schemas]: ProxyColumns<InferSchema<Schemas[K]>> }) => ProxyQueryResult
|
|
398
401
|
): T[] {
|
|
399
|
-
return executeProxyQuery(
|
|
402
|
+
return this._m('query(proxy)', () => executeProxyQuery(
|
|
400
403
|
this.schemas,
|
|
401
404
|
callback as any,
|
|
402
405
|
(sql: string, params: any[]) => {
|
|
403
|
-
if (this._debug) console.log('[satidb]', sql, params);
|
|
404
406
|
return this.db.query(sql).all(...params) as T[];
|
|
405
407
|
},
|
|
406
|
-
);
|
|
408
|
+
));
|
|
407
409
|
}
|
|
408
410
|
|
|
409
411
|
// =========================================================================
|
|
@@ -412,14 +414,12 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
412
414
|
|
|
413
415
|
/** Execute a raw SQL query and return results. */
|
|
414
416
|
public raw<T = any>(sql: string, ...params: any[]): T[] {
|
|
415
|
-
|
|
416
|
-
return this.db.query(sql).all(...params) as T[];
|
|
417
|
+
return this._m(`raw: ${sql.slice(0, 60)}`, () => this.db.query(sql).all(...params) as T[]);
|
|
417
418
|
}
|
|
418
419
|
|
|
419
420
|
/** Execute a raw SQL statement (INSERT/UPDATE/DELETE) without returning rows. */
|
|
420
421
|
public exec(sql: string, ...params: any[]): void {
|
|
421
|
-
|
|
422
|
-
this.db.run(sql, ...params);
|
|
422
|
+
this._m(`exec: ${sql.slice(0, 60)}`, () => this.db.run(sql, ...params));
|
|
423
423
|
}
|
|
424
424
|
|
|
425
425
|
// =========================================================================
|
|
@@ -445,11 +445,13 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
445
445
|
* Each key is a table name, value is an array of raw row objects.
|
|
446
446
|
*/
|
|
447
447
|
public dump(): Record<string, any[]> {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
448
|
+
return this._m('dump', () => {
|
|
449
|
+
const result: Record<string, any[]> = {};
|
|
450
|
+
for (const tableName of Object.keys(this.schemas)) {
|
|
451
|
+
result[tableName] = this.db.query(`SELECT * FROM "${tableName}"`).all();
|
|
452
|
+
}
|
|
453
|
+
return result;
|
|
454
|
+
});
|
|
453
455
|
}
|
|
454
456
|
|
|
455
457
|
/**
|
|
@@ -457,28 +459,29 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
457
459
|
* Use `{ append: true }` to insert without truncating.
|
|
458
460
|
*/
|
|
459
461
|
public load(data: Record<string, any[]>, options?: { append?: boolean }): void {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
462
|
+
this._m(`load(${Object.keys(data).join(',')})`, () => {
|
|
463
|
+
const txn = this.db.transaction(() => {
|
|
464
|
+
for (const [tableName, rows] of Object.entries(data)) {
|
|
465
|
+
if (!this.schemas[tableName]) continue;
|
|
466
|
+
if (!options?.append) {
|
|
467
|
+
this.db.run(`DELETE FROM "${tableName}"`);
|
|
468
|
+
}
|
|
469
|
+
for (const row of rows) {
|
|
470
|
+
const cols = Object.keys(row).filter(k => k !== 'id');
|
|
471
|
+
const placeholders = cols.map(() => '?').join(', ');
|
|
472
|
+
const values = cols.map(c => {
|
|
473
|
+
const v = row[c];
|
|
474
|
+
if (v !== null && v !== undefined && typeof v === 'object' && !(v instanceof Buffer)) {
|
|
475
|
+
return JSON.stringify(v);
|
|
476
|
+
}
|
|
477
|
+
return v;
|
|
478
|
+
});
|
|
479
|
+
this.db.query(`INSERT INTO "${tableName}" (${cols.map(c => `"${c}"`).join(', ')}) VALUES (${placeholders})`).run(...values);
|
|
480
|
+
}
|
|
478
481
|
}
|
|
479
|
-
}
|
|
482
|
+
});
|
|
483
|
+
txn();
|
|
480
484
|
});
|
|
481
|
-
txn();
|
|
482
485
|
}
|
|
483
486
|
|
|
484
487
|
/**
|
|
@@ -498,45 +501,45 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
498
501
|
* Returns a diff object per table: { added, removed, typeChanged }.
|
|
499
502
|
*/
|
|
500
503
|
public diff(): Record<string, { added: string[]; removed: string[]; typeChanged: { column: string; expected: string; actual: string }[] }> {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
504
|
+
return this._m('diff', () => {
|
|
505
|
+
const result: Record<string, { added: string[]; removed: string[]; typeChanged: { column: string; expected: string; actual: string }[] }> = {};
|
|
506
|
+
const systemCols = new Set(['id', 'createdAt', 'updatedAt', 'deletedAt']);
|
|
507
|
+
|
|
508
|
+
for (const [tableName, schema] of Object.entries(this.schemas)) {
|
|
509
|
+
const schemaFields = getStorableFields(schema);
|
|
510
|
+
const schemaColMap = new Map(schemaFields.map(f => [f.name, zodTypeToSqlType(f.type)]));
|
|
511
|
+
|
|
512
|
+
const liveColumns = this.columns(tableName);
|
|
513
|
+
const liveColMap = new Map(liveColumns.map(c => [c.name, c.type]));
|
|
514
|
+
|
|
515
|
+
const added: string[] = [];
|
|
516
|
+
const removed: string[] = [];
|
|
517
|
+
const typeChanged: { column: string; expected: string; actual: string }[] = [];
|
|
518
|
+
|
|
519
|
+
for (const [col, expectedType] of schemaColMap) {
|
|
520
|
+
if (!liveColMap.has(col)) {
|
|
521
|
+
added.push(col);
|
|
522
|
+
} else {
|
|
523
|
+
const actualType = liveColMap.get(col)!;
|
|
524
|
+
if (actualType !== expectedType) {
|
|
525
|
+
typeChanged.push({ column: col, expected: expectedType, actual: actualType });
|
|
526
|
+
}
|
|
523
527
|
}
|
|
524
528
|
}
|
|
525
|
-
}
|
|
526
529
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
530
|
+
for (const col of liveColMap.keys()) {
|
|
531
|
+
if (!systemCols.has(col) && !schemaColMap.has(col)) {
|
|
532
|
+
removed.push(col);
|
|
533
|
+
}
|
|
531
534
|
}
|
|
532
|
-
}
|
|
533
535
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
+
if (added.length > 0 || removed.length > 0 || typeChanged.length > 0) {
|
|
537
|
+
result[tableName] = { added, removed, typeChanged };
|
|
538
|
+
}
|
|
536
539
|
}
|
|
537
|
-
}
|
|
538
540
|
|
|
539
|
-
|
|
541
|
+
return result;
|
|
542
|
+
});
|
|
540
543
|
}
|
|
541
544
|
}
|
|
542
545
|
|
package/src/query.ts
CHANGED
|
@@ -39,10 +39,11 @@ export function createQueryBuilder(ctx: DatabaseContext, entityName: string, ini
|
|
|
39
39
|
const schema = ctx.schemas[entityName]!;
|
|
40
40
|
|
|
41
41
|
const executor = (sql: string, params: any[], raw: boolean): any[] => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
return ctx._m(`SQL: ${sql.slice(0, 60)}`, () => {
|
|
43
|
+
const rows = ctx.db.query(sql).all(...params);
|
|
44
|
+
if (raw) return rows;
|
|
45
|
+
return rows.map((row: any) => ctx.attachMethods(entityName, transformFromStorage(row, schema)));
|
|
46
|
+
});
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
const singleExecutor = (sql: string, params: any[], raw: boolean): any | null => {
|