sqlite-zod-orm 3.25.0 → 3.26.1
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/README.md +15 -0
- package/dist/index.js +113 -53
- package/package.json +3 -3
- 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/README.md
CHANGED
|
@@ -228,6 +228,21 @@ db.exec('UPDATE users SET score = 0 WHERE role = ?', 'guest');
|
|
|
228
228
|
- Debug mode (SQL logging)
|
|
229
229
|
- Raw SQL escape hatch
|
|
230
230
|
|
|
231
|
+
## Contributing
|
|
232
|
+
|
|
233
|
+
SatiDB is opinionated by design. Before proposing new features, understand what it intentionally does **not** do:
|
|
234
|
+
|
|
235
|
+
| ❌ Don't add | Why |
|
|
236
|
+
|---|---|
|
|
237
|
+
| Tagged SQL templates | The whole point is "zero SQL" — `db.raw()` is the escape hatch |
|
|
238
|
+
| FTS5 wrapper | Wrapping it poorly is worse than not wrapping it — use `db.raw()` |
|
|
239
|
+
| Query middleware | `measure-fn` handles observability, `hooks` handle lifecycle |
|
|
240
|
+
| Cursor pagination | Offset pagination covers SQLite's single-process use case |
|
|
241
|
+
| Schema introspection API | Zod schemas are compile-time known — runtime reflection invites dynamic queries |
|
|
242
|
+
| Migration CLI | Auto-migration handles additive changes; use `db.exec()` for destructive ones |
|
|
243
|
+
|
|
244
|
+
**The rule:** if the query builder can already do it, don't add a new API surface for it.
|
|
245
|
+
|
|
231
246
|
## Requirements
|
|
232
247
|
|
|
233
248
|
- **Bun** ≥ 1.0 (uses `bun:sqlite` native bindings)
|
package/dist/index.js
CHANGED
|
@@ -23,8 +23,9 @@ var toAlpha = (num) => {
|
|
|
23
23
|
} while (n >= 0);
|
|
24
24
|
return result;
|
|
25
25
|
};
|
|
26
|
-
var maxResultLen =
|
|
27
|
-
var safeStringify = (value) => {
|
|
26
|
+
var maxResultLen = 0;
|
|
27
|
+
var safeStringify = (value, limit) => {
|
|
28
|
+
const cap = limit ?? maxResultLen;
|
|
28
29
|
if (value === undefined)
|
|
29
30
|
return "";
|
|
30
31
|
if (value === null)
|
|
@@ -37,7 +38,9 @@ var safeStringify = (value) => {
|
|
|
37
38
|
return value.toString();
|
|
38
39
|
if (typeof value === "string") {
|
|
39
40
|
const q = JSON.stringify(value);
|
|
40
|
-
|
|
41
|
+
if (cap === 0)
|
|
42
|
+
return q;
|
|
43
|
+
return q.length > cap ? q.slice(0, cap - 1) + '\u2026"' : q;
|
|
41
44
|
}
|
|
42
45
|
try {
|
|
43
46
|
const seen = new WeakSet;
|
|
@@ -53,7 +56,9 @@ var safeStringify = (value) => {
|
|
|
53
56
|
return `${val}n`;
|
|
54
57
|
return val;
|
|
55
58
|
});
|
|
56
|
-
|
|
59
|
+
if (cap === 0)
|
|
60
|
+
return str;
|
|
61
|
+
return str.length > cap ? str.slice(0, cap) + "\u2026" : str;
|
|
57
62
|
} catch {
|
|
58
63
|
return String(value);
|
|
59
64
|
}
|
|
@@ -67,7 +72,7 @@ var formatDuration = (ms) => {
|
|
|
67
72
|
const secs = Math.round(ms % 60000 / 1000);
|
|
68
73
|
return `${mins}m ${secs}s`;
|
|
69
74
|
};
|
|
70
|
-
var timestamps = process.env.MEASURE_TIMESTAMPS === "1" || process.env.MEASURE_TIMESTAMPS === "true";
|
|
75
|
+
var timestamps = typeof process !== "undefined" && (process.env.MEASURE_TIMESTAMPS === "1" || process.env.MEASURE_TIMESTAMPS === "true");
|
|
71
76
|
var ts = () => {
|
|
72
77
|
if (!timestamps)
|
|
73
78
|
return "";
|
|
@@ -78,7 +83,9 @@ var ts = () => {
|
|
|
78
83
|
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
|
79
84
|
return `[${h}:${m}:${s}.${ms}] `;
|
|
80
85
|
};
|
|
81
|
-
var silent = process.env.MEASURE_SILENT === "1" || process.env.MEASURE_SILENT === "true";
|
|
86
|
+
var silent = typeof process !== "undefined" && (process.env.MEASURE_SILENT === "1" || process.env.MEASURE_SILENT === "true");
|
|
87
|
+
var dotEndLabel = true;
|
|
88
|
+
var dotChar = "\xB7";
|
|
82
89
|
var logger = null;
|
|
83
90
|
var buildActionLabel = (actionInternal) => {
|
|
84
91
|
return typeof actionInternal === "object" && actionInternal !== null && "label" in actionInternal ? String(actionInternal.label) : String(actionInternal);
|
|
@@ -90,6 +97,20 @@ var extractBudget = (actionInternal) => {
|
|
|
90
97
|
return Number(actionInternal.budget);
|
|
91
98
|
return;
|
|
92
99
|
};
|
|
100
|
+
var extractTimeout = (actionInternal) => {
|
|
101
|
+
if (typeof actionInternal !== "object" || actionInternal === null)
|
|
102
|
+
return;
|
|
103
|
+
if ("timeout" in actionInternal)
|
|
104
|
+
return Number(actionInternal.timeout);
|
|
105
|
+
return;
|
|
106
|
+
};
|
|
107
|
+
var extractMaxResultLength = (actionInternal) => {
|
|
108
|
+
if (typeof actionInternal !== "object" || actionInternal === null)
|
|
109
|
+
return;
|
|
110
|
+
if ("maxResultLength" in actionInternal)
|
|
111
|
+
return Number(actionInternal.maxResultLength);
|
|
112
|
+
return;
|
|
113
|
+
};
|
|
93
114
|
var extractMeta = (actionInternal) => {
|
|
94
115
|
if (typeof actionInternal !== "object" || actionInternal === null)
|
|
95
116
|
return;
|
|
@@ -98,6 +119,8 @@ var extractMeta = (actionInternal) => {
|
|
|
98
119
|
delete details.label;
|
|
99
120
|
if ("budget" in details)
|
|
100
121
|
delete details.budget;
|
|
122
|
+
if ("maxResultLength" in details)
|
|
123
|
+
delete details.maxResultLength;
|
|
101
124
|
if (Object.keys(details).length === 0)
|
|
102
125
|
return;
|
|
103
126
|
return details;
|
|
@@ -126,16 +149,18 @@ var defaultLogger = (event, prefix) => {
|
|
|
126
149
|
console.log(`${t}${id} ... ${event.label}${formatMeta(event.meta)}`);
|
|
127
150
|
break;
|
|
128
151
|
case "success": {
|
|
129
|
-
const
|
|
152
|
+
const endLabel = dotEndLabel ? dotChar.repeat(event.label.length) : event.label;
|
|
153
|
+
const resultStr = event.result !== undefined ? safeStringify(event.result, event.maxResultLength) : "";
|
|
130
154
|
const arrow = resultStr ? ` \u2192 ${resultStr}` : "";
|
|
131
155
|
const budgetWarn = event.budget && event.duration > event.budget ? ` \u26A0 OVER BUDGET (${formatDuration(event.budget)})` : "";
|
|
132
|
-
console.log(`${t}${id}
|
|
156
|
+
console.log(`${t}${id} ${endLabel} ${formatDuration(event.duration)}${arrow}${budgetWarn}`);
|
|
133
157
|
break;
|
|
134
158
|
}
|
|
135
159
|
case "error": {
|
|
160
|
+
const endLabel = dotEndLabel ? dotChar.repeat(event.label.length) : event.label;
|
|
136
161
|
const errorMsg = event.error instanceof Error ? event.error.message : String(event.error);
|
|
137
162
|
const budgetWarn = event.budget && event.duration > event.budget ? ` \u26A0 OVER BUDGET (${formatDuration(event.budget)})` : "";
|
|
138
|
-
console.log(`${t}${id} \u2717 ${
|
|
163
|
+
console.log(`${t}${id} \u2717 ${endLabel} ${formatDuration(event.duration)} (${errorMsg})${budgetWarn}`);
|
|
139
164
|
if (event.error instanceof Error) {
|
|
140
165
|
console.error(`${id}`, event.error.stack ?? event.error.message);
|
|
141
166
|
if (event.error.cause) {
|
|
@@ -151,13 +176,14 @@ var defaultLogger = (event, prefix) => {
|
|
|
151
176
|
break;
|
|
152
177
|
}
|
|
153
178
|
};
|
|
154
|
-
var createNestedResolver = (isAsync, fullIdChain, childCounterRef, depth, resolver, prefix) => {
|
|
179
|
+
var createNestedResolver = (isAsync, fullIdChain, childCounterRef, depth, resolver, prefix, inheritedMaxLen) => {
|
|
155
180
|
return (...args) => {
|
|
156
181
|
const label = args[0];
|
|
157
182
|
const fn = args[1];
|
|
183
|
+
const onError = args[2];
|
|
158
184
|
if (typeof fn === "function") {
|
|
159
185
|
const childParentChain = [...fullIdChain, childCounterRef.value++];
|
|
160
|
-
return resolver(fn, label, childParentChain, depth + 1);
|
|
186
|
+
return resolver(fn, label, childParentChain, depth + 1, typeof onError === "function" ? onError : undefined, inheritedMaxLen);
|
|
161
187
|
} else {
|
|
162
188
|
emit({
|
|
163
189
|
type: "annotation",
|
|
@@ -171,20 +197,24 @@ var createNestedResolver = (isAsync, fullIdChain, childCounterRef, depth, resolv
|
|
|
171
197
|
};
|
|
172
198
|
};
|
|
173
199
|
var globalRootCounter = 0;
|
|
174
|
-
var createMeasureImpl = (prefix, counterRef) => {
|
|
200
|
+
var createMeasureImpl = (prefix, counterRef, scopeOpts) => {
|
|
175
201
|
const counter = counterRef ?? { get value() {
|
|
176
202
|
return globalRootCounter;
|
|
177
203
|
}, set value(v) {
|
|
178
204
|
globalRootCounter = v;
|
|
179
205
|
} };
|
|
206
|
+
const scopeMaxLen = scopeOpts?.maxResultLength;
|
|
180
207
|
let _lastError = null;
|
|
181
|
-
const _measureInternal = async (fnInternal, actionInternal, parentIdChain, depth) => {
|
|
208
|
+
const _measureInternal = async (fnInternal, actionInternal, parentIdChain, depth, onError, inheritedMaxLen) => {
|
|
182
209
|
const start = performance.now();
|
|
183
210
|
const childCounterRef = { value: 0 };
|
|
184
211
|
const label = buildActionLabel(actionInternal);
|
|
185
212
|
const budget = extractBudget(actionInternal);
|
|
186
|
-
const
|
|
187
|
-
const
|
|
213
|
+
const timeout = extractTimeout(actionInternal);
|
|
214
|
+
const localMaxLen = extractMaxResultLength(actionInternal);
|
|
215
|
+
const effectiveMaxLen = localMaxLen ?? inheritedMaxLen;
|
|
216
|
+
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0));
|
|
217
|
+
const fullIdChain = [...parentIdChain.map(String), currentId];
|
|
188
218
|
const idStr = fullIdChain.join("-");
|
|
189
219
|
emit({
|
|
190
220
|
type: "start",
|
|
@@ -193,27 +223,46 @@ var createMeasureImpl = (prefix, counterRef) => {
|
|
|
193
223
|
depth,
|
|
194
224
|
meta: extractMeta(actionInternal)
|
|
195
225
|
}, prefix);
|
|
196
|
-
const measureForNextLevel = createNestedResolver(true, fullIdChain, childCounterRef, depth, _measureInternal, prefix);
|
|
226
|
+
const measureForNextLevel = createNestedResolver(true, fullIdChain, childCounterRef, depth, _measureInternal, prefix, effectiveMaxLen);
|
|
197
227
|
try {
|
|
198
|
-
|
|
228
|
+
let result;
|
|
229
|
+
if (timeout && timeout > 0) {
|
|
230
|
+
result = await Promise.race([
|
|
231
|
+
fnInternal(measureForNextLevel),
|
|
232
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout (${formatDuration(timeout)})`)), timeout))
|
|
233
|
+
]);
|
|
234
|
+
} else {
|
|
235
|
+
result = await fnInternal(measureForNextLevel);
|
|
236
|
+
}
|
|
199
237
|
const duration = performance.now() - start;
|
|
200
|
-
emit({ type: "success", id: idStr, label, depth, duration, result, budget }, prefix);
|
|
238
|
+
emit({ type: "success", id: idStr, label, depth, duration, result, budget, maxResultLength: effectiveMaxLen }, prefix);
|
|
201
239
|
return result;
|
|
202
240
|
} catch (error) {
|
|
203
241
|
const duration = performance.now() - start;
|
|
204
|
-
emit({ type: "error", id: idStr, label, depth, duration, error, budget }, prefix);
|
|
242
|
+
emit({ type: "error", id: idStr, label, depth, duration, error, budget, maxResultLength: effectiveMaxLen }, prefix);
|
|
205
243
|
_lastError = error;
|
|
244
|
+
if (onError) {
|
|
245
|
+
try {
|
|
246
|
+
return onError(error);
|
|
247
|
+
} catch (onErrorError) {
|
|
248
|
+
emit({ type: "error", id: idStr, label: `${label} (onError)`, depth, duration: performance.now() - start, error: onErrorError, budget, maxResultLength: effectiveMaxLen }, prefix);
|
|
249
|
+
_lastError = onErrorError;
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
206
253
|
return null;
|
|
207
254
|
}
|
|
208
255
|
};
|
|
209
|
-
const _measureInternalSync = (fnInternal, actionInternal, parentIdChain, depth) => {
|
|
256
|
+
const _measureInternalSync = (fnInternal, actionInternal, parentIdChain, depth, _onError, inheritedMaxLen) => {
|
|
210
257
|
const start = performance.now();
|
|
211
258
|
const childCounterRef = { value: 0 };
|
|
212
259
|
const label = buildActionLabel(actionInternal);
|
|
213
260
|
const hasNested = fnInternal.length > 0;
|
|
214
261
|
const budget = extractBudget(actionInternal);
|
|
215
|
-
const
|
|
216
|
-
const
|
|
262
|
+
const localMaxLen = extractMaxResultLength(actionInternal);
|
|
263
|
+
const effectiveMaxLen = localMaxLen ?? inheritedMaxLen;
|
|
264
|
+
const currentId = toAlpha(Number(parentIdChain.pop() ?? 0));
|
|
265
|
+
const fullIdChain = [...parentIdChain.map(String), currentId];
|
|
217
266
|
const idStr = fullIdChain.join("-");
|
|
218
267
|
if (hasNested) {
|
|
219
268
|
emit({
|
|
@@ -224,22 +273,22 @@ var createMeasureImpl = (prefix, counterRef) => {
|
|
|
224
273
|
meta: extractMeta(actionInternal)
|
|
225
274
|
}, prefix);
|
|
226
275
|
}
|
|
227
|
-
const measureForNextLevel = createNestedResolver(false, fullIdChain, childCounterRef, depth, _measureInternalSync, prefix);
|
|
276
|
+
const measureForNextLevel = createNestedResolver(false, fullIdChain, childCounterRef, depth, _measureInternalSync, prefix, effectiveMaxLen);
|
|
228
277
|
try {
|
|
229
278
|
const result = fnInternal(measureForNextLevel);
|
|
230
279
|
const duration = performance.now() - start;
|
|
231
|
-
emit({ type: "success", id: idStr, label, depth, duration, result, budget }, prefix);
|
|
280
|
+
emit({ type: "success", id: idStr, label, depth, duration, result, budget, maxResultLength: effectiveMaxLen }, prefix);
|
|
232
281
|
return result;
|
|
233
282
|
} catch (error) {
|
|
234
283
|
const duration = performance.now() - start;
|
|
235
|
-
emit({ type: "error", id: idStr, label, depth, duration, error, budget }, prefix);
|
|
284
|
+
emit({ type: "error", id: idStr, label, depth, duration, error, budget, maxResultLength: effectiveMaxLen }, prefix);
|
|
236
285
|
_lastError = error;
|
|
237
286
|
return null;
|
|
238
287
|
}
|
|
239
288
|
};
|
|
240
|
-
const measureFn = async (arg1, arg2) => {
|
|
289
|
+
const measureFn = async (arg1, arg2, arg3) => {
|
|
241
290
|
if (typeof arg2 === "function") {
|
|
242
|
-
return _measureInternal(arg2, arg1, [counter.value++], 0);
|
|
291
|
+
return _measureInternal(arg2, arg1, [counter.value++], 0, arg3, scopeMaxLen);
|
|
243
292
|
} else {
|
|
244
293
|
const currentId = toAlpha(counter.value++);
|
|
245
294
|
emit({
|
|
@@ -349,7 +398,7 @@ var createMeasureImpl = (prefix, counterRef) => {
|
|
|
349
398
|
};
|
|
350
399
|
const measureSyncFn = (arg1, arg2) => {
|
|
351
400
|
if (typeof arg2 === "function") {
|
|
352
|
-
return _measureInternalSync(arg2, arg1, [counter.value++], 0);
|
|
401
|
+
return _measureInternalSync(arg2, arg1, [counter.value++], 0, undefined, scopeMaxLen);
|
|
353
402
|
} else {
|
|
354
403
|
const currentId = toAlpha(counter.value++);
|
|
355
404
|
emit({
|
|
@@ -385,9 +434,9 @@ var createMeasureImpl = (prefix, counterRef) => {
|
|
|
385
434
|
var globalInstance = createMeasureImpl();
|
|
386
435
|
var measure = globalInstance.measure;
|
|
387
436
|
var measureSync = globalInstance.measureSync;
|
|
388
|
-
var createMeasure = (scopePrefix) => {
|
|
437
|
+
var createMeasure = (scopePrefix, opts) => {
|
|
389
438
|
const scopeCounter = { value: 0 };
|
|
390
|
-
const scoped = createMeasureImpl(scopePrefix, scopeCounter);
|
|
439
|
+
const scoped = createMeasureImpl(scopePrefix, scopeCounter, opts);
|
|
391
440
|
return {
|
|
392
441
|
...scoped,
|
|
393
442
|
resetCounter: () => {
|
|
@@ -5221,7 +5270,7 @@ function createQueryBuilder(ctx, entityName, initialCols) {
|
|
|
5221
5270
|
const schema = ctx.schemas[entityName];
|
|
5222
5271
|
const executor = (sql, params, raw) => {
|
|
5223
5272
|
return ctx._m(`SQL: ${sql.slice(0, 60)}`, () => {
|
|
5224
|
-
const rows = ctx.
|
|
5273
|
+
const rows = ctx._stmt(sql).all(...params);
|
|
5225
5274
|
if (raw)
|
|
5226
5275
|
return rows;
|
|
5227
5276
|
return rows.map((row) => ctx.attachMethods(entityName, transformFromStorage(row, schema)));
|
|
@@ -5269,7 +5318,7 @@ function createQueryBuilder(ctx, entityName, initialCols) {
|
|
|
5269
5318
|
if (belongsTo2) {
|
|
5270
5319
|
const fk = belongsTo2.foreignKey;
|
|
5271
5320
|
const placeholders = parentIds.map(() => "?").join(", ");
|
|
5272
|
-
const childRows = ctx.
|
|
5321
|
+
const childRows = ctx._stmt(`SELECT * FROM ${hasMany.to} WHERE ${fk} IN (${placeholders})`).all(...parentIds);
|
|
5273
5322
|
const groups = new Map;
|
|
5274
5323
|
const childSchema = ctx.schemas[hasMany.to];
|
|
5275
5324
|
for (const rawRow of childRows) {
|
|
@@ -5381,21 +5430,21 @@ function buildWhereClause(conditions, tablePrefix) {
|
|
|
5381
5430
|
|
|
5382
5431
|
// src/crud.ts
|
|
5383
5432
|
function getById(ctx, entityName, id) {
|
|
5384
|
-
const row = ctx.
|
|
5433
|
+
const row = ctx._stmt(`SELECT * FROM "${entityName}" WHERE id = ?`).get(id);
|
|
5385
5434
|
if (!row)
|
|
5386
5435
|
return null;
|
|
5387
5436
|
return ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]));
|
|
5388
5437
|
}
|
|
5389
5438
|
function getOne(ctx, entityName, conditions) {
|
|
5390
5439
|
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
5391
|
-
const row = ctx.
|
|
5440
|
+
const row = ctx._stmt(`SELECT * FROM "${entityName}" ${clause} LIMIT 1`).get(...values);
|
|
5392
5441
|
if (!row)
|
|
5393
5442
|
return null;
|
|
5394
5443
|
return ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName]));
|
|
5395
5444
|
}
|
|
5396
5445
|
function findMany(ctx, entityName, conditions = {}) {
|
|
5397
5446
|
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
5398
|
-
const rows = ctx.
|
|
5447
|
+
const rows = ctx._stmt(`SELECT * FROM "${entityName}" ${clause}`).all(...values);
|
|
5399
5448
|
return rows.map((row) => ctx.attachMethods(entityName, transformFromStorage(row, ctx.schemas[entityName])));
|
|
5400
5449
|
}
|
|
5401
5450
|
function insert(ctx, entityName, data) {
|
|
@@ -5419,7 +5468,7 @@ function insert(ctx, entityName, data) {
|
|
|
5419
5468
|
const sql = columns.length === 0 ? `INSERT INTO "${entityName}" DEFAULT VALUES` : `INSERT INTO "${entityName}" (${quotedCols.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`;
|
|
5420
5469
|
let lastId = 0;
|
|
5421
5470
|
ctx._m(`SQL: ${sql.slice(0, 40)}`, () => {
|
|
5422
|
-
const result = ctx.
|
|
5471
|
+
const result = ctx._stmt(sql).run(...Object.values(transformed));
|
|
5423
5472
|
lastId = result.lastInsertRowid;
|
|
5424
5473
|
});
|
|
5425
5474
|
const newEntity = getById(ctx, entityName, lastId);
|
|
@@ -5448,7 +5497,7 @@ function update(ctx, entityName, id, data) {
|
|
|
5448
5497
|
const setClause = Object.keys(transformed).map((key) => `"${key}" = ?`).join(", ");
|
|
5449
5498
|
const sql = `UPDATE "${entityName}" SET ${setClause} WHERE id = ?`;
|
|
5450
5499
|
ctx._m(`SQL: UPDATE ${entityName} SET ...`, () => {
|
|
5451
|
-
ctx.
|
|
5500
|
+
ctx._stmt(sql).run(...Object.values(transformed), id);
|
|
5452
5501
|
});
|
|
5453
5502
|
const updated = getById(ctx, entityName, id);
|
|
5454
5503
|
if (hooks?.afterUpdate && updated)
|
|
@@ -5466,7 +5515,7 @@ function updateWhere(ctx, entityName, data, conditions) {
|
|
|
5466
5515
|
throw new Error("update().where() requires at least one condition");
|
|
5467
5516
|
const setCols = Object.keys(transformed);
|
|
5468
5517
|
const setClause = setCols.map((key) => `"${key}" = ?`).join(", ");
|
|
5469
|
-
const result = ctx.
|
|
5518
|
+
const result = ctx._stmt(`UPDATE "${entityName}" SET ${setClause} ${clause}`).run(...setCols.map((key) => transformed[key]), ...whereValues);
|
|
5470
5519
|
return result.changes ?? 0;
|
|
5471
5520
|
}
|
|
5472
5521
|
function createUpdateBuilder(ctx, entityName, data) {
|
|
@@ -5507,7 +5556,7 @@ function deleteEntity(ctx, entityName, id) {
|
|
|
5507
5556
|
if (result === false)
|
|
5508
5557
|
return;
|
|
5509
5558
|
}
|
|
5510
|
-
ctx.
|
|
5559
|
+
ctx._stmt(`DELETE FROM "${entityName}" WHERE id = ?`).run(id);
|
|
5511
5560
|
if (hooks?.afterDelete)
|
|
5512
5561
|
hooks.afterDelete(id);
|
|
5513
5562
|
}
|
|
@@ -5518,11 +5567,11 @@ function deleteWhere(ctx, entityName, conditions) {
|
|
|
5518
5567
|
if (ctx.softDeletes) {
|
|
5519
5568
|
const now = new Date().toISOString();
|
|
5520
5569
|
const sql2 = `UPDATE "${entityName}" SET "deletedAt" = ? ${clause}`;
|
|
5521
|
-
const result2 = ctx._m(`SQL: ${sql2.slice(0, 50)}`, () => ctx.
|
|
5570
|
+
const result2 = ctx._m(`SQL: ${sql2.slice(0, 50)}`, () => ctx._stmt(sql2).run(now, ...values));
|
|
5522
5571
|
return result2.changes ?? 0;
|
|
5523
5572
|
}
|
|
5524
5573
|
const sql = `DELETE FROM "${entityName}" ${clause}`;
|
|
5525
|
-
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx.
|
|
5574
|
+
const result = ctx._m(`SQL: ${sql.slice(0, 50)}`, () => ctx._stmt(sql).run(...values));
|
|
5526
5575
|
return result.changes ?? 0;
|
|
5527
5576
|
}
|
|
5528
5577
|
function createDeleteBuilder(ctx, entityName) {
|
|
@@ -5561,7 +5610,7 @@ function insertMany(ctx, entityName, rows) {
|
|
|
5561
5610
|
const columns = Object.keys(transformed);
|
|
5562
5611
|
const quotedCols = columns.map((c) => `"${c}"`);
|
|
5563
5612
|
const sql = columns.length === 0 ? `INSERT INTO "${entityName}" DEFAULT VALUES` : `INSERT INTO "${entityName}" (${quotedCols.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`;
|
|
5564
|
-
const result = ctx.
|
|
5613
|
+
const result = ctx._stmt(sql).run(...Object.values(transformed));
|
|
5565
5614
|
ids2.push(result.lastInsertRowid);
|
|
5566
5615
|
}
|
|
5567
5616
|
return ids2;
|
|
@@ -5647,6 +5696,15 @@ class _Database {
|
|
|
5647
5696
|
_pollTimer = null;
|
|
5648
5697
|
_pollInterval;
|
|
5649
5698
|
_measure;
|
|
5699
|
+
_stmtCache = new Map;
|
|
5700
|
+
_stmt(sql) {
|
|
5701
|
+
let stmt = this._stmtCache.get(sql);
|
|
5702
|
+
if (!stmt) {
|
|
5703
|
+
stmt = this.db.query(sql);
|
|
5704
|
+
this._stmtCache.set(sql, stmt);
|
|
5705
|
+
}
|
|
5706
|
+
return stmt;
|
|
5707
|
+
}
|
|
5650
5708
|
_m(label, fn) {
|
|
5651
5709
|
if (this._debug)
|
|
5652
5710
|
return this._measure.measureSync.assert(label, fn);
|
|
@@ -5678,7 +5736,8 @@ class _Database {
|
|
|
5678
5736
|
hooks: options.hooks ?? {},
|
|
5679
5737
|
computed: options.computed ?? {},
|
|
5680
5738
|
cascade: options.cascade ?? {},
|
|
5681
|
-
_m: (label, fn) => this._m(label, fn)
|
|
5739
|
+
_m: (label, fn) => this._m(label, fn),
|
|
5740
|
+
_stmt: (sql) => this._stmt(sql)
|
|
5682
5741
|
};
|
|
5683
5742
|
this._m("Init tables", () => this.initializeTables());
|
|
5684
5743
|
if (this._reactive)
|
|
@@ -5717,16 +5776,16 @@ class _Database {
|
|
|
5717
5776
|
if (rel) {
|
|
5718
5777
|
if (this._softDeletes) {
|
|
5719
5778
|
const now = new Date().toISOString();
|
|
5720
|
-
this.
|
|
5779
|
+
this._stmt(`UPDATE "${childTable}" SET "deletedAt" = ? WHERE "${rel.foreignKey}" = ?`).run(now, id);
|
|
5721
5780
|
} else {
|
|
5722
|
-
this.
|
|
5781
|
+
this._stmt(`DELETE FROM "${childTable}" WHERE "${rel.foreignKey}" = ?`).run(id);
|
|
5723
5782
|
}
|
|
5724
5783
|
}
|
|
5725
5784
|
}
|
|
5726
5785
|
}
|
|
5727
5786
|
if (this._softDeletes) {
|
|
5728
5787
|
const now = new Date().toISOString();
|
|
5729
|
-
this.
|
|
5788
|
+
this._stmt(`UPDATE "${entityName}" SET "deletedAt" = ? WHERE id = ?`).run(now, id);
|
|
5730
5789
|
if (hooks?.afterDelete)
|
|
5731
5790
|
hooks.afterDelete(id);
|
|
5732
5791
|
return;
|
|
@@ -5740,12 +5799,12 @@ class _Database {
|
|
|
5740
5799
|
if (!this._softDeletes)
|
|
5741
5800
|
throw new Error("restore() requires softDeletes: true");
|
|
5742
5801
|
this._m(`${entityName}.restore(${id})`, () => {
|
|
5743
|
-
this.
|
|
5802
|
+
this._stmt(`UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`).run(id);
|
|
5744
5803
|
});
|
|
5745
5804
|
},
|
|
5746
5805
|
select: (...cols) => createQueryBuilder(this._ctx, entityName, cols),
|
|
5747
5806
|
count: () => this._m(`${entityName}.count`, () => {
|
|
5748
|
-
const row = this.
|
|
5807
|
+
const row = this._stmt(`SELECT COUNT(*) as count FROM "${entityName}"${this._softDeletes ? ' WHERE "deletedAt" IS NULL' : ""}`).get();
|
|
5749
5808
|
return row?.count ?? 0;
|
|
5750
5809
|
}),
|
|
5751
5810
|
on: (event, callback) => {
|
|
@@ -5861,11 +5920,11 @@ class _Database {
|
|
|
5861
5920
|
}
|
|
5862
5921
|
}
|
|
5863
5922
|
_processChanges() {
|
|
5864
|
-
const head = this.
|
|
5923
|
+
const head = this._stmt('SELECT MAX(id) as m FROM "_changes"').get();
|
|
5865
5924
|
const maxId = head?.m ?? 0;
|
|
5866
5925
|
if (maxId <= this._changeWatermark)
|
|
5867
5926
|
return;
|
|
5868
|
-
const changes = this.
|
|
5927
|
+
const changes = this._stmt('SELECT id, tbl, op, row_id FROM "_changes" WHERE id > ? ORDER BY id').all(this._changeWatermark);
|
|
5869
5928
|
for (const change of changes) {
|
|
5870
5929
|
const listeners = this._listeners.filter((l) => l.table === change.tbl && l.event === change.op);
|
|
5871
5930
|
if (listeners.length > 0) {
|
|
@@ -5889,22 +5948,23 @@ class _Database {
|
|
|
5889
5948
|
}
|
|
5890
5949
|
this._changeWatermark = change.id;
|
|
5891
5950
|
}
|
|
5892
|
-
this.
|
|
5951
|
+
this._stmt('DELETE FROM "_changes" WHERE id <= ?').run(this._changeWatermark);
|
|
5893
5952
|
}
|
|
5894
5953
|
transaction(callback) {
|
|
5895
5954
|
return this._m("transaction", () => this.db.transaction(callback)());
|
|
5896
5955
|
}
|
|
5897
5956
|
close() {
|
|
5898
5957
|
this._stopPolling();
|
|
5958
|
+
this._stmtCache.clear();
|
|
5899
5959
|
this.db.close();
|
|
5900
5960
|
}
|
|
5901
5961
|
query(callback) {
|
|
5902
5962
|
return this._m("query(proxy)", () => executeProxyQuery(this.schemas, callback, (sql, params) => {
|
|
5903
|
-
return this.
|
|
5963
|
+
return this._stmt(sql).all(...params);
|
|
5904
5964
|
}));
|
|
5905
5965
|
}
|
|
5906
5966
|
raw(sql, ...params) {
|
|
5907
|
-
return this._m(`raw: ${sql.slice(0, 60)}`, () => this.
|
|
5967
|
+
return this._m(`raw: ${sql.slice(0, 60)}`, () => this._stmt(sql).all(...params));
|
|
5908
5968
|
}
|
|
5909
5969
|
exec(sql, ...params) {
|
|
5910
5970
|
this._m(`exec: ${sql.slice(0, 60)}`, () => this.db.run(sql, ...params));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sqlite-zod-orm",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.1",
|
|
4
4
|
"description": "Type-safe SQLite ORM for Bun — Zod schemas, fluent queries, auto relationships, zero SQL",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"typescript": "^5.0.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"measure-fn": "^3.
|
|
52
|
+
"measure-fn": "^3.10.0",
|
|
53
53
|
"zod": "^3.25.67"
|
|
54
54
|
},
|
|
55
55
|
"engines": {
|
|
56
56
|
"bun": ">=1.0.0"
|
|
57
57
|
}
|
|
58
|
-
}
|
|
58
|
+
}
|
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
|
|