sqlite-zod-orm 3.25.0 → 3.26.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 +34 -23
- package/package.json +1 -1
- package/src/context.ts +6 -0
- package/src/crud.ts +10 -10
- package/src/database.ts +26 -11
- package/src/query.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -5221,7 +5221,7 @@ function createQueryBuilder(ctx, entityName, initialCols) {
|
|
|
5221
5221
|
const schema = ctx.schemas[entityName];
|
|
5222
5222
|
const executor = (sql, params, raw) => {
|
|
5223
5223
|
return ctx._m(`SQL: ${sql.slice(0, 60)}`, () => {
|
|
5224
|
-
const rows = ctx.
|
|
5224
|
+
const rows = ctx._stmt(sql).all(...params);
|
|
5225
5225
|
if (raw)
|
|
5226
5226
|
return rows;
|
|
5227
5227
|
return rows.map((row) => ctx.attachMethods(entityName, transformFromStorage(row, schema)));
|
|
@@ -5269,7 +5269,7 @@ function createQueryBuilder(ctx, entityName, initialCols) {
|
|
|
5269
5269
|
if (belongsTo2) {
|
|
5270
5270
|
const fk = belongsTo2.foreignKey;
|
|
5271
5271
|
const placeholders = parentIds.map(() => "?").join(", ");
|
|
5272
|
-
const childRows = ctx.
|
|
5272
|
+
const childRows = ctx._stmt(`SELECT * FROM ${hasMany.to} WHERE ${fk} IN (${placeholders})`).all(...parentIds);
|
|
5273
5273
|
const groups = new Map;
|
|
5274
5274
|
const childSchema = ctx.schemas[hasMany.to];
|
|
5275
5275
|
for (const rawRow of childRows) {
|
|
@@ -5381,21 +5381,21 @@ function buildWhereClause(conditions, tablePrefix) {
|
|
|
5381
5381
|
|
|
5382
5382
|
// src/crud.ts
|
|
5383
5383
|
function getById(ctx, entityName, id) {
|
|
5384
|
-
const row = ctx.
|
|
5384
|
+
const row = ctx._stmt(`SELECT * FROM "${entityName}" WHERE id = ?`).get(id);
|
|
5385
5385
|
if (!row)
|
|
5386
5386
|
return null;
|
|
5387
5387
|
return ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]));
|
|
5388
5388
|
}
|
|
5389
5389
|
function getOne(ctx, entityName, conditions) {
|
|
5390
5390
|
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
5391
|
-
const row = ctx.
|
|
5391
|
+
const row = ctx._stmt(`SELECT * FROM "${entityName}" ${clause} LIMIT 1`).get(...values);
|
|
5392
5392
|
if (!row)
|
|
5393
5393
|
return null;
|
|
5394
5394
|
return ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]));
|
|
5395
5395
|
}
|
|
5396
5396
|
function findMany(ctx, entityName, conditions = {}) {
|
|
5397
5397
|
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
5398
|
-
const rows = ctx.
|
|
5398
|
+
const rows = ctx._stmt(`SELECT * FROM "${entityName}" ${clause}`).all(...values);
|
|
5399
5399
|
return rows.map((row) => ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName])));
|
|
5400
5400
|
}
|
|
5401
5401
|
function insert(ctx, entityName, data) {
|
|
@@ -5419,7 +5419,7 @@ function insert(ctx, entityName, data) {
|
|
|
5419
5419
|
const sql = columns.length === 0 ? `INSERT INTO "${entityName}" DEFAULT VALUES` : `INSERT INTO "${entityName}" (${quotedCols.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`;
|
|
5420
5420
|
let lastId = 0;
|
|
5421
5421
|
ctx._m(`SQL: ${sql.slice(0, 40)}`, () => {
|
|
5422
|
-
const result = ctx.
|
|
5422
|
+
const result = ctx._stmt(sql).run(...Object.values(transformed));
|
|
5423
5423
|
lastId = result.lastInsertRowid;
|
|
5424
5424
|
});
|
|
5425
5425
|
const newEntity = getById(ctx, entityName, lastId);
|
|
@@ -5448,7 +5448,7 @@ function update(ctx, entityName, id, data) {
|
|
|
5448
5448
|
const setClause = Object.keys(transformed).map((key) => `"${key}" = ?`).join(", ");
|
|
5449
5449
|
const sql = `UPDATE "${entityName}" SET ${setClause} WHERE id = ?`;
|
|
5450
5450
|
ctx._m(`SQL: UPDATE ${entityName} SET ...`, () => {
|
|
5451
|
-
ctx.
|
|
5451
|
+
ctx._stmt(sql).run(...Object.values(transformed), id);
|
|
5452
5452
|
});
|
|
5453
5453
|
const updated = getById(ctx, entityName, id);
|
|
5454
5454
|
if (hooks?.afterUpdate && updated)
|
|
@@ -5466,7 +5466,7 @@ function updateWhere(ctx, entityName, data, conditions) {
|
|
|
5466
5466
|
throw new Error("update().where() requires at least one condition");
|
|
5467
5467
|
const setCols = Object.keys(transformed);
|
|
5468
5468
|
const setClause = setCols.map((key) => `"${key}" = ?`).join(", ");
|
|
5469
|
-
const result = ctx.
|
|
5469
|
+
const result = ctx._stmt(`UPDATE "${entityName}" SET ${setClause} ${clause}`).run(...setCols.map((key) => transformed[key]), ...whereValues);
|
|
5470
5470
|
return result.changes ?? 0;
|
|
5471
5471
|
}
|
|
5472
5472
|
function createUpdateBuilder(ctx, entityName, data) {
|
|
@@ -5507,7 +5507,7 @@ function deleteEntity(ctx, entityName, id) {
|
|
|
5507
5507
|
if (result === false)
|
|
5508
5508
|
return;
|
|
5509
5509
|
}
|
|
5510
|
-
ctx.
|
|
5510
|
+
ctx._stmt(`DELETE FROM "${entityName}" WHERE id = ?`).run(id);
|
|
5511
5511
|
if (hooks?.afterDelete)
|
|
5512
5512
|
hooks.afterDelete(id);
|
|
5513
5513
|
}
|
|
@@ -5518,11 +5518,11 @@ function deleteWhere(ctx, entityName, conditions) {
|
|
|
5518
5518
|
if (ctx.softDeletes) {
|
|
5519
5519
|
const now = new Date().toISOString();
|
|
5520
5520
|
const sql2 = `UPDATE "${entityName}" SET "deletedAt" = ? ${clause}`;
|
|
5521
|
-
const result2 = ctx._m(`SQL: ${sql2.slice(0, 50)}`, () => ctx.
|
|
5521
|
+
const result2 = ctx._m(`SQL: ${sql2.slice(0, 50)}`, () => ctx._stmt(sql2).run(now, ...values));
|
|
5522
5522
|
return result2.changes ?? 0;
|
|
5523
5523
|
}
|
|
5524
5524
|
const sql = `DELETE FROM "${entityName}" ${clause}`;
|
|
5525
|
-
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx.
|
|
5525
|
+
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx._stmt(sql).run(...values));
|
|
5526
5526
|
return result.changes ?? 0;
|
|
5527
5527
|
}
|
|
5528
5528
|
function createDeleteBuilder(ctx, entityName) {
|
|
@@ -5561,7 +5561,7 @@ function insertMany(ctx, entityName, rows) {
|
|
|
5561
5561
|
const columns = Object.keys(transformed);
|
|
5562
5562
|
const quotedCols = columns.map((c) => `"${c}"`);
|
|
5563
5563
|
const sql = columns.length === 0 ? `INSERT INTO "${entityName}" DEFAULT VALUES` : `INSERT INTO "${entityName}" (${quotedCols.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`;
|
|
5564
|
-
const result = ctx.
|
|
5564
|
+
const result = ctx._stmt(sql).run(...Object.values(transformed));
|
|
5565
5565
|
ids2.push(result.lastInsertRowid);
|
|
5566
5566
|
}
|
|
5567
5567
|
return ids2;
|
|
@@ -5647,6 +5647,15 @@ class _Database {
|
|
|
5647
5647
|
_pollTimer = null;
|
|
5648
5648
|
_pollInterval;
|
|
5649
5649
|
_measure;
|
|
5650
|
+
_stmtCache = new Map;
|
|
5651
|
+
_stmt(sql) {
|
|
5652
|
+
let stmt = this._stmtCache.get(sql);
|
|
5653
|
+
if (!stmt) {
|
|
5654
|
+
stmt = this.db.query(sql);
|
|
5655
|
+
this._stmtCache.set(sql, stmt);
|
|
5656
|
+
}
|
|
5657
|
+
return stmt;
|
|
5658
|
+
}
|
|
5650
5659
|
_m(label, fn) {
|
|
5651
5660
|
if (this._debug)
|
|
5652
5661
|
return this._measure.measureSync.assert(label, fn);
|
|
@@ -5678,7 +5687,8 @@ class _Database {
|
|
|
5678
5687
|
hooks: options.hooks ?? {},
|
|
5679
5688
|
computed: options.computed ?? {},
|
|
5680
5689
|
cascade: options.cascade ?? {},
|
|
5681
|
-
_m: (label, fn) => this._m(label, fn)
|
|
5690
|
+
_m: (label, fn) => this._m(label, fn),
|
|
5691
|
+
_stmt: (sql) => this._stmt(sql)
|
|
5682
5692
|
};
|
|
5683
5693
|
this._m("Init tables", () => this.initializeTables());
|
|
5684
5694
|
if (this._reactive)
|
|
@@ -5717,16 +5727,16 @@ class _Database {
|
|
|
5717
5727
|
if (rel) {
|
|
5718
5728
|
if (this._softDeletes) {
|
|
5719
5729
|
const now = new Date().toISOString();
|
|
5720
|
-
this.
|
|
5730
|
+
this._stmt(`UPDATE "${childTable}" SET "deletedAt" = ? WHERE "${rel.foreignKey}" = ?`).run(now, id);
|
|
5721
5731
|
} else {
|
|
5722
|
-
this.
|
|
5732
|
+
this._stmt(`DELETE FROM "${childTable}" WHERE "${rel.foreignKey}" = ?`).run(id);
|
|
5723
5733
|
}
|
|
5724
5734
|
}
|
|
5725
5735
|
}
|
|
5726
5736
|
}
|
|
5727
5737
|
if (this._softDeletes) {
|
|
5728
5738
|
const now = new Date().toISOString();
|
|
5729
|
-
this.
|
|
5739
|
+
this._stmt(`UPDATE "${entityName}" SET "deletedAt" = ? WHERE id = ?`).run(now, id);
|
|
5730
5740
|
if (hooks?.afterDelete)
|
|
5731
5741
|
hooks.afterDelete(id);
|
|
5732
5742
|
return;
|
|
@@ -5740,12 +5750,12 @@ class _Database {
|
|
|
5740
5750
|
if (!this._softDeletes)
|
|
5741
5751
|
throw new Error("restore() requires softDeletes: true");
|
|
5742
5752
|
this._m(`${entityName}.restore(${id})`, () => {
|
|
5743
|
-
this.
|
|
5753
|
+
this._stmt(`UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`).run(id);
|
|
5744
5754
|
});
|
|
5745
5755
|
},
|
|
5746
5756
|
select: (...cols) => createQueryBuilder(this._ctx, entityName, cols),
|
|
5747
5757
|
count: () => this._m(`${entityName}.count`, () => {
|
|
5748
|
-
const row = this.
|
|
5758
|
+
const row = this._stmt(`SELECT COUNT(*) as count FROM "${entityName}"${this._softDeletes ? ' WHERE "deletedAt" IS NULL' : ""}`).get();
|
|
5749
5759
|
return row?.count ?? 0;
|
|
5750
5760
|
}),
|
|
5751
5761
|
on: (event, callback) => {
|
|
@@ -5861,11 +5871,11 @@ class _Database {
|
|
|
5861
5871
|
}
|
|
5862
5872
|
}
|
|
5863
5873
|
_processChanges() {
|
|
5864
|
-
const head = this.
|
|
5874
|
+
const head = this._stmt('SELECT MAX(id) as m FROM "_changes"').get();
|
|
5865
5875
|
const maxId = head?.m ?? 0;
|
|
5866
5876
|
if (maxId <= this._changeWatermark)
|
|
5867
5877
|
return;
|
|
5868
|
-
const changes = this.
|
|
5878
|
+
const changes = this._stmt('SELECT id, tbl, op, row_id FROM "_changes" WHERE id > ? ORDER BY id').all(this._changeWatermark);
|
|
5869
5879
|
for (const change of changes) {
|
|
5870
5880
|
const listeners = this._listeners.filter((l) => l.table === change.tbl && l.event === change.op);
|
|
5871
5881
|
if (listeners.length > 0) {
|
|
@@ -5889,22 +5899,23 @@ class _Database {
|
|
|
5889
5899
|
}
|
|
5890
5900
|
this._changeWatermark = change.id;
|
|
5891
5901
|
}
|
|
5892
|
-
this.
|
|
5902
|
+
this._stmt('DELETE FROM "_changes" WHERE id <= ?').run(this._changeWatermark);
|
|
5893
5903
|
}
|
|
5894
5904
|
transaction(callback) {
|
|
5895
5905
|
return this._m("transaction", () => this.db.transaction(callback)());
|
|
5896
5906
|
}
|
|
5897
5907
|
close() {
|
|
5898
5908
|
this._stopPolling();
|
|
5909
|
+
this._stmtCache.clear();
|
|
5899
5910
|
this.db.close();
|
|
5900
5911
|
}
|
|
5901
5912
|
query(callback) {
|
|
5902
5913
|
return this._m("query(proxy)", () => executeProxyQuery(this.schemas, callback, (sql, params) => {
|
|
5903
|
-
return this.
|
|
5914
|
+
return this._stmt(sql).all(...params);
|
|
5904
5915
|
}));
|
|
5905
5916
|
}
|
|
5906
5917
|
raw(sql, ...params) {
|
|
5907
|
-
return this._m(`raw: ${sql.slice(0, 60)}`, () => this.
|
|
5918
|
+
return this._m(`raw: ${sql.slice(0, 60)}`, () => this._stmt(sql).all(...params));
|
|
5908
5919
|
}
|
|
5909
5920
|
exec(sql, ...params) {
|
|
5910
5921
|
this._m(`exec: ${sql.slice(0, 60)}`, () => this.db.run(sql, ...params));
|
package/package.json
CHANGED
package/src/context.ts
CHANGED
|
@@ -46,4 +46,10 @@ export interface DatabaseContext {
|
|
|
46
46
|
* When debug is off, executes fn directly with zero overhead.
|
|
47
47
|
*/
|
|
48
48
|
_m<T>(label: string, fn: () => T): T;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get a cached prepared statement. Compiles SQL once, reuses on subsequent calls.
|
|
52
|
+
* Falls back to `db.query(sql)` if the statement was finalized.
|
|
53
|
+
*/
|
|
54
|
+
_stmt(sql: string): ReturnType<SqliteDatabase['query']>;
|
|
49
55
|
}
|
package/src/crud.ts
CHANGED
|
@@ -14,21 +14,21 @@ import type { DatabaseContext } from './context';
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
|
|
16
16
|
export function getById(ctx: DatabaseContext, entityName: string, id: number): AugmentedEntity<any> | null {
|
|
17
|
-
const row = ctx.
|
|
17
|
+
const row = ctx._stmt(`SELECT * FROM "${entityName}" WHERE id = ?`).get(id) as any;
|
|
18
18
|
if (!row) return null;
|
|
19
19
|
return ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]!));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function getOne(ctx: DatabaseContext, entityName: string, conditions: Record<string, any>): AugmentedEntity<any> | null {
|
|
23
23
|
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
24
|
-
const row = ctx.
|
|
24
|
+
const row = ctx._stmt(`SELECT * FROM "${entityName}" ${clause} LIMIT 1`).get(...values) as any;
|
|
25
25
|
if (!row) return null;
|
|
26
26
|
return ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]!));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function findMany(ctx: DatabaseContext, entityName: string, conditions: Record<string, any> = {}): AugmentedEntity<any>[] {
|
|
30
30
|
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
31
|
-
const rows = ctx.
|
|
31
|
+
const rows = ctx._stmt(`SELECT * FROM "${entityName}" ${clause}`).all(...values);
|
|
32
32
|
return rows.map((row: any) =>
|
|
33
33
|
ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]!))
|
|
34
34
|
);
|
|
@@ -68,7 +68,7 @@ export function insert<T extends Record<string, any>>(ctx: DatabaseContext, enti
|
|
|
68
68
|
|
|
69
69
|
let lastId = 0;
|
|
70
70
|
ctx._m(`SQL: ${sql.slice(0, 40)}`, () => {
|
|
71
|
-
const result = ctx.
|
|
71
|
+
const result = ctx._stmt(sql).run(...Object.values(transformed));
|
|
72
72
|
lastId = result.lastInsertRowid as number;
|
|
73
73
|
});
|
|
74
74
|
const newEntity = getById(ctx, entityName, lastId);
|
|
@@ -103,7 +103,7 @@ export function update<T extends Record<string, any>>(ctx: DatabaseContext, enti
|
|
|
103
103
|
const setClause = Object.keys(transformed).map(key => `"${key}" = ?`).join(', ');
|
|
104
104
|
const sql = `UPDATE "${entityName}" SET ${setClause} WHERE id = ?`;
|
|
105
105
|
ctx._m(`SQL: UPDATE ${entityName} SET ...`, () => {
|
|
106
|
-
ctx.
|
|
106
|
+
ctx._stmt(sql).run(...Object.values(transformed), id);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
const updated = getById(ctx, entityName, id);
|
|
@@ -125,7 +125,7 @@ export function updateWhere(ctx: DatabaseContext, entityName: string, data: Reco
|
|
|
125
125
|
|
|
126
126
|
const setCols = Object.keys(transformed);
|
|
127
127
|
const setClause = setCols.map(key => `"${key}" = ?`).join(', ');
|
|
128
|
-
const result = ctx.
|
|
128
|
+
const result = ctx._stmt(`UPDATE "${entityName}" SET ${setClause} ${clause}`).run(
|
|
129
129
|
...setCols.map(key => transformed[key]),
|
|
130
130
|
...whereValues
|
|
131
131
|
);
|
|
@@ -181,7 +181,7 @@ export function deleteEntity(ctx: DatabaseContext, entityName: string, id: numbe
|
|
|
181
181
|
if (result === false) return;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
ctx.
|
|
184
|
+
ctx._stmt(`DELETE FROM "${entityName}" WHERE id = ?`).run(id);
|
|
185
185
|
|
|
186
186
|
// afterDelete hook
|
|
187
187
|
if (hooks?.afterDelete) hooks.afterDelete(id);
|
|
@@ -196,12 +196,12 @@ export function deleteWhere(ctx: DatabaseContext, entityName: string, conditions
|
|
|
196
196
|
// Soft delete: set deletedAt instead of removing rows
|
|
197
197
|
const now = new Date().toISOString();
|
|
198
198
|
const sql = `UPDATE "${entityName}" SET "deletedAt" = ? ${clause}`;
|
|
199
|
-
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx.
|
|
199
|
+
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx._stmt(sql).run(now, ...values));
|
|
200
200
|
return (result as any).changes ?? 0;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
const sql = `DELETE FROM "${entityName}" ${clause}`;
|
|
204
|
-
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx.
|
|
204
|
+
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx._stmt(sql).run(...values));
|
|
205
205
|
return (result as any).changes ?? 0;
|
|
206
206
|
}
|
|
207
207
|
|
|
@@ -247,7 +247,7 @@ export function insertMany<T extends Record<string, any>>(ctx: DatabaseContext,
|
|
|
247
247
|
const sql = columns.length === 0
|
|
248
248
|
? `INSERT INTO "${entityName}" DEFAULT VALUES`
|
|
249
249
|
: `INSERT INTO "${entityName}" (${quotedCols.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`;
|
|
250
|
-
const result = ctx.
|
|
250
|
+
const result = ctx._stmt(sql).run(...Object.values(transformed));
|
|
251
251
|
ids.push(result.lastInsertRowid as number);
|
|
252
252
|
}
|
|
253
253
|
return ids;
|
package/src/database.ts
CHANGED
|
@@ -67,6 +67,19 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
67
67
|
/** Scoped measure-fn instance for instrumentation. */
|
|
68
68
|
private _measure: ReturnType<typeof createMeasure>;
|
|
69
69
|
|
|
70
|
+
/** Prepared statement cache — avoids re-compiling identical SQL. */
|
|
71
|
+
private _stmtCache = new Map<string, ReturnType<SqliteDatabase['query']>>();
|
|
72
|
+
|
|
73
|
+
/** Get or create a cached prepared statement. */
|
|
74
|
+
private _stmt(sql: string): ReturnType<SqliteDatabase['query']> {
|
|
75
|
+
let stmt = this._stmtCache.get(sql);
|
|
76
|
+
if (!stmt) {
|
|
77
|
+
stmt = this.db.query(sql);
|
|
78
|
+
this._stmtCache.set(sql, stmt);
|
|
79
|
+
}
|
|
80
|
+
return stmt;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
/**
|
|
71
84
|
* Conditional measurement helper — wraps with measure-fn only when debug is on.
|
|
72
85
|
* When debug is off, executes fn directly with zero overhead.
|
|
@@ -105,6 +118,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
105
118
|
computed: options.computed ?? {},
|
|
106
119
|
cascade: options.cascade ?? {},
|
|
107
120
|
_m: <T>(label: string, fn: () => T): T => this._m(label, fn),
|
|
121
|
+
_stmt: (sql: string) => this._stmt(sql),
|
|
108
122
|
};
|
|
109
123
|
|
|
110
124
|
this._m('Init tables', () => this.initializeTables());
|
|
@@ -146,9 +160,9 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
146
160
|
if (rel) {
|
|
147
161
|
if (this._softDeletes) {
|
|
148
162
|
const now = new Date().toISOString();
|
|
149
|
-
this.
|
|
163
|
+
this._stmt(`UPDATE "${childTable}" SET "deletedAt" = ? WHERE "${rel.foreignKey}" = ?`).run(now, id);
|
|
150
164
|
} else {
|
|
151
|
-
this.
|
|
165
|
+
this._stmt(`DELETE FROM "${childTable}" WHERE "${rel.foreignKey}" = ?`).run(id);
|
|
152
166
|
}
|
|
153
167
|
}
|
|
154
168
|
}
|
|
@@ -156,7 +170,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
156
170
|
|
|
157
171
|
if (this._softDeletes) {
|
|
158
172
|
const now = new Date().toISOString();
|
|
159
|
-
this.
|
|
173
|
+
this._stmt(`UPDATE "${entityName}" SET "deletedAt" = ? WHERE id = ?`).run(now, id);
|
|
160
174
|
if (hooks?.afterDelete) hooks.afterDelete(id);
|
|
161
175
|
return;
|
|
162
176
|
}
|
|
@@ -168,12 +182,12 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
168
182
|
restore: ((id: number) => {
|
|
169
183
|
if (!this._softDeletes) throw new Error('restore() requires softDeletes: true');
|
|
170
184
|
this._m(`${entityName}.restore(${id})`, () => {
|
|
171
|
-
this.
|
|
185
|
+
this._stmt(`UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`).run(id);
|
|
172
186
|
});
|
|
173
187
|
}) as any,
|
|
174
188
|
select: (...cols: string[]) => createQueryBuilder(this._ctx, entityName, cols),
|
|
175
189
|
count: () => this._m(`${entityName}.count`, () => {
|
|
176
|
-
const row = this.
|
|
190
|
+
const row = this._stmt(`SELECT COUNT(*) as count FROM "${entityName}"${this._softDeletes ? ' WHERE "deletedAt" IS NULL' : ''}`).get() as any;
|
|
177
191
|
return row?.count ?? 0;
|
|
178
192
|
}),
|
|
179
193
|
on: (event: ChangeEvent, callback: (row: any) => void | Promise<void>) => {
|
|
@@ -339,11 +353,11 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
339
353
|
*/
|
|
340
354
|
private _processChanges(): void {
|
|
341
355
|
// Fast path: check if anything changed at all (single scalar, index-only)
|
|
342
|
-
const head = this.
|
|
356
|
+
const head = this._stmt('SELECT MAX(id) as m FROM "_changes"').get() as any;
|
|
343
357
|
const maxId: number = head?.m ?? 0;
|
|
344
358
|
if (maxId <= this._changeWatermark) return;
|
|
345
359
|
|
|
346
|
-
const changes = this.
|
|
360
|
+
const changes = this._stmt(
|
|
347
361
|
'SELECT id, tbl, op, row_id FROM "_changes" WHERE id > ? ORDER BY id'
|
|
348
362
|
).all(this._changeWatermark) as { id: number; tbl: string; op: string; row_id: number }[];
|
|
349
363
|
|
|
@@ -374,7 +388,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
374
388
|
}
|
|
375
389
|
|
|
376
390
|
// Clean up consumed changes
|
|
377
|
-
this.
|
|
391
|
+
this._stmt('DELETE FROM "_changes" WHERE id <= ?').run(this._changeWatermark);
|
|
378
392
|
}
|
|
379
393
|
|
|
380
394
|
// =========================================================================
|
|
@@ -385,9 +399,10 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
385
399
|
return this._m('transaction', () => this.db.transaction(callback)());
|
|
386
400
|
}
|
|
387
401
|
|
|
388
|
-
/** Close the database: stops polling and releases the SQLite handle. */
|
|
402
|
+
/** Close the database: stops polling, clears cache, and releases the SQLite handle. */
|
|
389
403
|
public close(): void {
|
|
390
404
|
this._stopPolling();
|
|
405
|
+
this._stmtCache.clear();
|
|
391
406
|
this.db.close();
|
|
392
407
|
}
|
|
393
408
|
|
|
@@ -403,7 +418,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
403
418
|
this.schemas,
|
|
404
419
|
callback as any,
|
|
405
420
|
(sql: string, params: any[]) => {
|
|
406
|
-
return this.
|
|
421
|
+
return this._stmt(sql).all(...params) as T[];
|
|
407
422
|
},
|
|
408
423
|
));
|
|
409
424
|
}
|
|
@@ -414,7 +429,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
414
429
|
|
|
415
430
|
/** Execute a raw SQL query and return results. */
|
|
416
431
|
public raw<T = any>(sql: string, ...params: any[]): T[] {
|
|
417
|
-
return this._m(`raw: ${sql.slice(0, 60)}`, () => this.
|
|
432
|
+
return this._m(`raw: ${sql.slice(0, 60)}`, () => this._stmt(sql).all(...params) as T[]);
|
|
418
433
|
}
|
|
419
434
|
|
|
420
435
|
/** Execute a raw SQL statement (INSERT/UPDATE/DELETE) without returning rows. */
|
package/src/query.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function createQueryBuilder(ctx: DatabaseContext, entityName: string, ini
|
|
|
40
40
|
|
|
41
41
|
const executor = (sql: string, params: any[], raw: boolean): any[] => {
|
|
42
42
|
return ctx._m(`SQL: ${sql.slice(0, 60)}`, () => {
|
|
43
|
-
const rows = ctx.
|
|
43
|
+
const rows = ctx._stmt(sql).all(...params);
|
|
44
44
|
if (raw) return rows;
|
|
45
45
|
return rows.map((row: any) => ctx.attachMethods(entityName, transformFromStorage(row, schema)));
|
|
46
46
|
});
|
|
@@ -103,7 +103,7 @@ export function createQueryBuilder(ctx: DatabaseContext, entityName: string, ini
|
|
|
103
103
|
if (belongsTo) {
|
|
104
104
|
const fk = belongsTo.foreignKey;
|
|
105
105
|
const placeholders = parentIds.map(() => '?').join(', ');
|
|
106
|
-
const childRows = ctx.
|
|
106
|
+
const childRows = ctx._stmt(
|
|
107
107
|
`SELECT * FROM ${hasMany.to} WHERE ${fk} IN (${placeholders})`
|
|
108
108
|
).all(...parentIds) as any[];
|
|
109
109
|
|