sqlite-zod-orm 3.22.0 → 3.24.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 +410 -10
- package/package.json +57 -56
- package/src/builder.ts +28 -0
- package/src/database.ts +24 -9
package/dist/index.js
CHANGED
|
@@ -13,6 +13,389 @@ var __export = (target, all) => {
|
|
|
13
13
|
// src/database.ts
|
|
14
14
|
import { Database as SqliteDatabase } from "bun:sqlite";
|
|
15
15
|
|
|
16
|
+
// node_modules/measure-fn/index.ts
|
|
17
|
+
var toAlpha = (num) => {
|
|
18
|
+
let result = "";
|
|
19
|
+
let n = num;
|
|
20
|
+
do {
|
|
21
|
+
result = String.fromCharCode(97 + n % 26) + result;
|
|
22
|
+
n = Math.floor(n / 26) - 1;
|
|
23
|
+
} while (n >= 0);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
var maxResultLen = 80;
|
|
27
|
+
var safeStringify = (value) => {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return "";
|
|
30
|
+
if (value === null)
|
|
31
|
+
return "null";
|
|
32
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
33
|
+
return String(value);
|
|
34
|
+
if (typeof value === "function")
|
|
35
|
+
return `[Function: ${value.name || "anonymous"}]`;
|
|
36
|
+
if (typeof value === "symbol")
|
|
37
|
+
return value.toString();
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
const q = JSON.stringify(value);
|
|
40
|
+
return q.length > maxResultLen ? q.slice(0, maxResultLen - 1) + '\u2026"' : q;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const seen = new WeakSet;
|
|
44
|
+
const str = JSON.stringify(value, (_key, val) => {
|
|
45
|
+
if (typeof val === "object" && val !== null) {
|
|
46
|
+
if (seen.has(val))
|
|
47
|
+
return "[Circular]";
|
|
48
|
+
seen.add(val);
|
|
49
|
+
}
|
|
50
|
+
if (typeof val === "function")
|
|
51
|
+
return `[Function: ${val.name || "anonymous"}]`;
|
|
52
|
+
if (typeof val === "bigint")
|
|
53
|
+
return `${val}n`;
|
|
54
|
+
return val;
|
|
55
|
+
});
|
|
56
|
+
return str.length > maxResultLen ? str.slice(0, maxResultLen) + "\u2026" : str;
|
|
57
|
+
} catch {
|
|
58
|
+
return String(value);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var formatDuration = (ms) => {
|
|
62
|
+
if (ms < 1000)
|
|
63
|
+
return `${ms.toFixed(2)}ms`;
|
|
64
|
+
if (ms < 60000)
|
|
65
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
66
|
+
const mins = Math.floor(ms / 60000);
|
|
67
|
+
const secs = Math.round(ms % 60000 / 1000);
|
|
68
|
+
return `${mins}m ${secs}s`;
|
|
69
|
+
};
|
|
70
|
+
var timestamps = process.env.MEASURE_TIMESTAMPS === "1" || process.env.MEASURE_TIMESTAMPS === "true";
|
|
71
|
+
var ts = () => {
|
|
72
|
+
if (!timestamps)
|
|
73
|
+
return "";
|
|
74
|
+
const now = new Date;
|
|
75
|
+
const h = String(now.getHours()).padStart(2, "0");
|
|
76
|
+
const m = String(now.getMinutes()).padStart(2, "0");
|
|
77
|
+
const s = String(now.getSeconds()).padStart(2, "0");
|
|
78
|
+
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
|
79
|
+
return `[${h}:${m}:${s}.${ms}] `;
|
|
80
|
+
};
|
|
81
|
+
var silent = process.env.MEASURE_SILENT === "1" || process.env.MEASURE_SILENT === "true";
|
|
82
|
+
var logger = null;
|
|
83
|
+
var buildActionLabel = (actionInternal) => {
|
|
84
|
+
return typeof actionInternal === "object" && actionInternal !== null && "label" in actionInternal ? String(actionInternal.label) : String(actionInternal);
|
|
85
|
+
};
|
|
86
|
+
var extractBudget = (actionInternal) => {
|
|
87
|
+
if (typeof actionInternal !== "object" || actionInternal === null)
|
|
88
|
+
return;
|
|
89
|
+
if ("budget" in actionInternal)
|
|
90
|
+
return Number(actionInternal.budget);
|
|
91
|
+
return;
|
|
92
|
+
};
|
|
93
|
+
var extractMeta = (actionInternal) => {
|
|
94
|
+
if (typeof actionInternal !== "object" || actionInternal === null)
|
|
95
|
+
return;
|
|
96
|
+
const details = { ...actionInternal };
|
|
97
|
+
if ("label" in details)
|
|
98
|
+
delete details.label;
|
|
99
|
+
if ("budget" in details)
|
|
100
|
+
delete details.budget;
|
|
101
|
+
if (Object.keys(details).length === 0)
|
|
102
|
+
return;
|
|
103
|
+
return details;
|
|
104
|
+
};
|
|
105
|
+
var formatMeta = (meta) => {
|
|
106
|
+
if (!meta)
|
|
107
|
+
return "";
|
|
108
|
+
const params = Object.entries(meta).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(" ");
|
|
109
|
+
return ` (${params})`;
|
|
110
|
+
};
|
|
111
|
+
var emit = (event, prefix) => {
|
|
112
|
+
if (silent)
|
|
113
|
+
return;
|
|
114
|
+
if (logger) {
|
|
115
|
+
logger(event);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
defaultLogger(event, prefix);
|
|
119
|
+
};
|
|
120
|
+
var defaultLogger = (event, prefix) => {
|
|
121
|
+
const pfx = prefix ? `${prefix}:` : "";
|
|
122
|
+
const id = `[${pfx}${event.id}]`;
|
|
123
|
+
const t = ts();
|
|
124
|
+
switch (event.type) {
|
|
125
|
+
case "start":
|
|
126
|
+
console.log(`${t}${id} ... ${event.label}${formatMeta(event.meta)}`);
|
|
127
|
+
break;
|
|
128
|
+
case "success": {
|
|
129
|
+
const resultStr = event.result !== undefined ? safeStringify(event.result) : "";
|
|
130
|
+
const arrow = resultStr ? ` \u2192 ${resultStr}` : "";
|
|
131
|
+
const budgetWarn = event.budget && event.duration > event.budget ? ` \u26A0 OVER BUDGET (${formatDuration(event.budget)})` : "";
|
|
132
|
+
console.log(`${t}${id} \u2713 ${event.label} ${formatDuration(event.duration)}${arrow}${budgetWarn}`);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case "error": {
|
|
136
|
+
const errorMsg = event.error instanceof Error ? event.error.message : String(event.error);
|
|
137
|
+
const budgetWarn = event.budget && event.duration > event.budget ? ` \u26A0 OVER BUDGET (${formatDuration(event.budget)})` : "";
|
|
138
|
+
console.log(`${t}${id} \u2717 ${event.label} ${formatDuration(event.duration)} (${errorMsg})${budgetWarn}`);
|
|
139
|
+
if (event.error instanceof Error) {
|
|
140
|
+
console.error(`${id}`, event.error.stack ?? event.error.message);
|
|
141
|
+
if (event.error.cause) {
|
|
142
|
+
console.error(`${id} Cause:`, event.error.cause);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
console.error(`${id}`, event.error);
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "annotation":
|
|
150
|
+
console.log(`${t}${id} = ${event.label}${formatMeta(event.meta)}`);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var createNestedResolver = (isAsync, fullIdChain, childCounterRef, depth, resolver, prefix) => {
|
|
155
|
+
return (...args) => {
|
|
156
|
+
const label = args[0];
|
|
157
|
+
const fn = args[1];
|
|
158
|
+
if (typeof fn === "function") {
|
|
159
|
+
const childParentChain = [...fullIdChain, childCounterRef.value++];
|
|
160
|
+
return resolver(fn, label, childParentChain, depth + 1);
|
|
161
|
+
} else {
|
|
162
|
+
emit({
|
|
163
|
+
type: "annotation",
|
|
164
|
+
id: fullIdChain.join("-"),
|
|
165
|
+
label: buildActionLabel(label),
|
|
166
|
+
depth: depth + 1,
|
|
167
|
+
meta: extractMeta(label)
|
|
168
|
+
}, prefix);
|
|
169
|
+
return isAsync ? Promise.resolve(null) : null;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
var globalRootCounter = 0;
|
|
174
|
+
var createMeasureImpl = (prefix, counterRef) => {
|
|
175
|
+
const counter = counterRef ?? { get value() {
|
|
176
|
+
return globalRootCounter;
|
|
177
|
+
}, set value(v) {
|
|
178
|
+
globalRootCounter = v;
|
|
179
|
+
} };
|
|
180
|
+
let _lastError = null;
|
|
181
|
+
const _measureInternal = async (fnInternal, actionInternal, parentIdChain, depth) => {
|
|
182
|
+
const start = performance.now();
|
|
183
|
+
const childCounterRef = { value: 0 };
|
|
184
|
+
const label = buildActionLabel(actionInternal);
|
|
185
|
+
const budget = extractBudget(actionInternal);
|
|
186
|
+
const currentId = toAlpha(parentIdChain.pop() ?? 0);
|
|
187
|
+
const fullIdChain = [...parentIdChain, currentId];
|
|
188
|
+
const idStr = fullIdChain.join("-");
|
|
189
|
+
emit({
|
|
190
|
+
type: "start",
|
|
191
|
+
id: idStr,
|
|
192
|
+
label,
|
|
193
|
+
depth,
|
|
194
|
+
meta: extractMeta(actionInternal)
|
|
195
|
+
}, prefix);
|
|
196
|
+
const measureForNextLevel = createNestedResolver(true, fullIdChain, childCounterRef, depth, _measureInternal, prefix);
|
|
197
|
+
try {
|
|
198
|
+
const result = await fnInternal(measureForNextLevel);
|
|
199
|
+
const duration = performance.now() - start;
|
|
200
|
+
emit({ type: "success", id: idStr, label, depth, duration, result, budget }, prefix);
|
|
201
|
+
return result;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
const duration = performance.now() - start;
|
|
204
|
+
emit({ type: "error", id: idStr, label, depth, duration, error, budget }, prefix);
|
|
205
|
+
_lastError = error;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
const _measureInternalSync = (fnInternal, actionInternal, parentIdChain, depth) => {
|
|
210
|
+
const start = performance.now();
|
|
211
|
+
const childCounterRef = { value: 0 };
|
|
212
|
+
const label = buildActionLabel(actionInternal);
|
|
213
|
+
const hasNested = fnInternal.length > 0;
|
|
214
|
+
const budget = extractBudget(actionInternal);
|
|
215
|
+
const currentId = toAlpha(parentIdChain.pop() ?? 0);
|
|
216
|
+
const fullIdChain = [...parentIdChain, currentId];
|
|
217
|
+
const idStr = fullIdChain.join("-");
|
|
218
|
+
if (hasNested) {
|
|
219
|
+
emit({
|
|
220
|
+
type: "start",
|
|
221
|
+
id: idStr,
|
|
222
|
+
label,
|
|
223
|
+
depth,
|
|
224
|
+
meta: extractMeta(actionInternal)
|
|
225
|
+
}, prefix);
|
|
226
|
+
}
|
|
227
|
+
const measureForNextLevel = createNestedResolver(false, fullIdChain, childCounterRef, depth, _measureInternalSync, prefix);
|
|
228
|
+
try {
|
|
229
|
+
const result = fnInternal(measureForNextLevel);
|
|
230
|
+
const duration = performance.now() - start;
|
|
231
|
+
emit({ type: "success", id: idStr, label, depth, duration, result, budget }, prefix);
|
|
232
|
+
return result;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const duration = performance.now() - start;
|
|
235
|
+
emit({ type: "error", id: idStr, label, depth, duration, error, budget }, prefix);
|
|
236
|
+
_lastError = error;
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
const measureFn = async (arg1, arg2) => {
|
|
241
|
+
if (typeof arg2 === "function") {
|
|
242
|
+
return _measureInternal(arg2, arg1, [counter.value++], 0);
|
|
243
|
+
} else {
|
|
244
|
+
const currentId = toAlpha(counter.value++);
|
|
245
|
+
emit({
|
|
246
|
+
type: "annotation",
|
|
247
|
+
id: currentId,
|
|
248
|
+
label: buildActionLabel(arg1),
|
|
249
|
+
depth: 0,
|
|
250
|
+
meta: extractMeta(arg1)
|
|
251
|
+
}, prefix);
|
|
252
|
+
return Promise.resolve(null);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
measureFn.timed = async (arg1, arg2) => {
|
|
256
|
+
const start = performance.now();
|
|
257
|
+
const result = await measureFn(arg1, arg2);
|
|
258
|
+
const duration = performance.now() - start;
|
|
259
|
+
return { result, duration };
|
|
260
|
+
};
|
|
261
|
+
measureFn.retry = async (label, opts, fn) => {
|
|
262
|
+
const attempts = opts.attempts ?? 3;
|
|
263
|
+
const delay = opts.delay ?? 1000;
|
|
264
|
+
const backoff = opts.backoff ?? 1;
|
|
265
|
+
const lbl = buildActionLabel(label);
|
|
266
|
+
const budget = extractBudget(label);
|
|
267
|
+
for (let i = 0;i < attempts; i++) {
|
|
268
|
+
const attempt = i + 1;
|
|
269
|
+
const attemptLabel = `${lbl} [${attempt}/${attempts}]`;
|
|
270
|
+
const start = performance.now();
|
|
271
|
+
const currentId = toAlpha(counter.value++);
|
|
272
|
+
emit({
|
|
273
|
+
type: "start",
|
|
274
|
+
id: currentId,
|
|
275
|
+
label: attemptLabel,
|
|
276
|
+
depth: 0,
|
|
277
|
+
meta: extractMeta(label)
|
|
278
|
+
}, prefix);
|
|
279
|
+
try {
|
|
280
|
+
const result = await fn();
|
|
281
|
+
const duration = performance.now() - start;
|
|
282
|
+
emit({ type: "success", id: currentId, label: attemptLabel, depth: 0, duration, result, budget }, prefix);
|
|
283
|
+
return result;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
const duration = performance.now() - start;
|
|
286
|
+
emit({ type: "error", id: currentId, label: attemptLabel, depth: 0, duration, error, budget }, prefix);
|
|
287
|
+
if (attempt < attempts) {
|
|
288
|
+
await new Promise((r) => setTimeout(r, delay * Math.pow(backoff, i)));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
};
|
|
294
|
+
measureFn.assert = async (arg1, arg2) => {
|
|
295
|
+
const result = await measureFn(arg1, arg2);
|
|
296
|
+
if (result === null) {
|
|
297
|
+
const cause = _lastError;
|
|
298
|
+
_lastError = null;
|
|
299
|
+
throw new Error(`measure.assert: "${buildActionLabel(arg1)}" failed`, { cause });
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
};
|
|
303
|
+
measureFn.wrap = (label, fn) => {
|
|
304
|
+
return (...args) => measureFn(label, () => fn(...args));
|
|
305
|
+
};
|
|
306
|
+
measureFn.batch = async (label, items, fn, opts) => {
|
|
307
|
+
const lbl = buildActionLabel(label);
|
|
308
|
+
const total = items.length;
|
|
309
|
+
const every = opts?.every ?? Math.max(1, Math.ceil(total / 5));
|
|
310
|
+
const currentId = toAlpha(counter.value++);
|
|
311
|
+
const startTime = performance.now();
|
|
312
|
+
emit({
|
|
313
|
+
type: "start",
|
|
314
|
+
id: currentId,
|
|
315
|
+
label: `${lbl} (${total} items)`,
|
|
316
|
+
depth: 0,
|
|
317
|
+
meta: extractMeta(label)
|
|
318
|
+
}, prefix);
|
|
319
|
+
const results = [];
|
|
320
|
+
for (let i = 0;i < items.length; i++) {
|
|
321
|
+
try {
|
|
322
|
+
results.push(await fn(items[i], i));
|
|
323
|
+
} catch {
|
|
324
|
+
results.push(null);
|
|
325
|
+
}
|
|
326
|
+
if ((i + 1) % every === 0 && i + 1 < total) {
|
|
327
|
+
const elapsed = (performance.now() - startTime) / 1000;
|
|
328
|
+
const rate = ((i + 1) / elapsed).toFixed(0);
|
|
329
|
+
emit({
|
|
330
|
+
type: "annotation",
|
|
331
|
+
id: currentId,
|
|
332
|
+
label: `${i + 1}/${total} (${elapsed.toFixed(1)}s, ${rate}/s)`,
|
|
333
|
+
depth: 0
|
|
334
|
+
}, prefix);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const duration = performance.now() - startTime;
|
|
338
|
+
const budget = extractBudget(label);
|
|
339
|
+
emit({
|
|
340
|
+
type: "success",
|
|
341
|
+
id: currentId,
|
|
342
|
+
label: `${lbl} (${total} items)`,
|
|
343
|
+
depth: 0,
|
|
344
|
+
duration,
|
|
345
|
+
result: `${results.filter((r) => r !== null).length}/${total} ok`,
|
|
346
|
+
budget
|
|
347
|
+
}, prefix);
|
|
348
|
+
return results;
|
|
349
|
+
};
|
|
350
|
+
const measureSyncFn = (arg1, arg2) => {
|
|
351
|
+
if (typeof arg2 === "function") {
|
|
352
|
+
return _measureInternalSync(arg2, arg1, [counter.value++], 0);
|
|
353
|
+
} else {
|
|
354
|
+
const currentId = toAlpha(counter.value++);
|
|
355
|
+
emit({
|
|
356
|
+
type: "annotation",
|
|
357
|
+
id: currentId,
|
|
358
|
+
label: buildActionLabel(arg1),
|
|
359
|
+
depth: 0,
|
|
360
|
+
meta: extractMeta(arg1)
|
|
361
|
+
}, prefix);
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
measureSyncFn.timed = (arg1, arg2) => {
|
|
366
|
+
const start = performance.now();
|
|
367
|
+
const result = measureSyncFn(arg1, arg2);
|
|
368
|
+
const duration = performance.now() - start;
|
|
369
|
+
return { result, duration };
|
|
370
|
+
};
|
|
371
|
+
measureSyncFn.assert = (arg1, arg2) => {
|
|
372
|
+
const result = measureSyncFn(arg1, arg2);
|
|
373
|
+
if (result === null) {
|
|
374
|
+
const cause = _lastError;
|
|
375
|
+
_lastError = null;
|
|
376
|
+
throw new Error(`measureSync.assert: "${buildActionLabel(arg1)}" failed`, { cause });
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
};
|
|
380
|
+
measureSyncFn.wrap = (label, fn) => {
|
|
381
|
+
return (...args) => measureSyncFn(label, () => fn(...args));
|
|
382
|
+
};
|
|
383
|
+
return { measure: measureFn, measureSync: measureSyncFn };
|
|
384
|
+
};
|
|
385
|
+
var globalInstance = createMeasureImpl();
|
|
386
|
+
var measure = globalInstance.measure;
|
|
387
|
+
var measureSync = globalInstance.measureSync;
|
|
388
|
+
var createMeasure = (scopePrefix) => {
|
|
389
|
+
const scopeCounter = { value: 0 };
|
|
390
|
+
const scoped = createMeasureImpl(scopePrefix, scopeCounter);
|
|
391
|
+
return {
|
|
392
|
+
...scoped,
|
|
393
|
+
resetCounter: () => {
|
|
394
|
+
scopeCounter.value = 0;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
|
|
16
399
|
// node_modules/zod/v3/external.js
|
|
17
400
|
var exports_external = {};
|
|
18
401
|
__export(exports_external, {
|
|
@@ -4620,6 +5003,18 @@ class QueryBuilder {
|
|
|
4620
5003
|
const result = this.executor(`SELECT changes() as c`, [], true);
|
|
4621
5004
|
return result[0]?.c ?? 0;
|
|
4622
5005
|
}
|
|
5006
|
+
increment(column, amount = 1) {
|
|
5007
|
+
const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
|
|
5008
|
+
const whereMatch = selectSql.match(/WHERE (.+?)(?:\s+ORDER|\s+LIMIT|\s+GROUP|\s+HAVING|$)/s);
|
|
5009
|
+
const wherePart = whereMatch ? whereMatch[1] : "1=1";
|
|
5010
|
+
const sql = `UPDATE "${this.tableName}" SET "${column}" = "${column}" + ? WHERE ${wherePart}`;
|
|
5011
|
+
this.executor(sql, [amount, ...params], true);
|
|
5012
|
+
const result = this.executor(`SELECT changes() as c`, [], true);
|
|
5013
|
+
return result[0]?.c ?? 0;
|
|
5014
|
+
}
|
|
5015
|
+
decrement(column, amount = 1) {
|
|
5016
|
+
return this.increment(column, -amount);
|
|
5017
|
+
}
|
|
4623
5018
|
then(onfulfilled, onrejected) {
|
|
4624
5019
|
try {
|
|
4625
5020
|
const result = this.all();
|
|
@@ -5253,7 +5648,15 @@ class _Database {
|
|
|
5253
5648
|
_changeWatermark = 0;
|
|
5254
5649
|
_pollTimer = null;
|
|
5255
5650
|
_pollInterval;
|
|
5651
|
+
_measure;
|
|
5652
|
+
_m(label, fn) {
|
|
5653
|
+
if (this._debug)
|
|
5654
|
+
return this._measure.measureSync.assert(label, fn);
|
|
5655
|
+
return fn();
|
|
5656
|
+
}
|
|
5256
5657
|
constructor(dbFile, schemas, options = {}) {
|
|
5658
|
+
this._debug = options.debug === true;
|
|
5659
|
+
this._measure = createMeasure("satidb");
|
|
5257
5660
|
this.db = new SqliteDatabase(dbFile);
|
|
5258
5661
|
if (options.wal !== false)
|
|
5259
5662
|
this.db.run("PRAGMA journal_mode = WAL");
|
|
@@ -5263,7 +5666,6 @@ class _Database {
|
|
|
5263
5666
|
this._reactive = options.reactive !== false;
|
|
5264
5667
|
this._timestamps = options.timestamps === true;
|
|
5265
5668
|
this._softDeletes = options.softDeletes === true;
|
|
5266
|
-
this._debug = options.debug === true;
|
|
5267
5669
|
this._pollInterval = options.pollInterval ?? 100;
|
|
5268
5670
|
this.relationships = options.relations ? parseRelationsConfig(options.relations, schemas) : [];
|
|
5269
5671
|
this._ctx = {
|
|
@@ -5279,19 +5681,19 @@ class _Database {
|
|
|
5279
5681
|
computed: options.computed ?? {},
|
|
5280
5682
|
cascade: options.cascade ?? {}
|
|
5281
5683
|
};
|
|
5282
|
-
this.initializeTables();
|
|
5684
|
+
this._m("Init tables", () => this.initializeTables());
|
|
5283
5685
|
if (this._reactive)
|
|
5284
|
-
this.initializeChangeTracking();
|
|
5285
|
-
this.runMigrations();
|
|
5686
|
+
this._m("Change tracking", () => this.initializeChangeTracking());
|
|
5687
|
+
this._m("Run migrations", () => this.runMigrations());
|
|
5286
5688
|
if (options.indexes)
|
|
5287
|
-
this.createIndexes(options.indexes);
|
|
5689
|
+
this._m("Create indexes", () => this.createIndexes(options.indexes));
|
|
5288
5690
|
if (options.unique)
|
|
5289
|
-
this.createUniqueConstraints(options.unique);
|
|
5691
|
+
this._m("Unique constraints", () => this.createUniqueConstraints(options.unique));
|
|
5290
5692
|
for (const entityName of Object.keys(schemas)) {
|
|
5291
5693
|
const key = entityName;
|
|
5292
5694
|
const accessor = {
|
|
5293
|
-
insert: (data) => insert(this._ctx, entityName, data),
|
|
5294
|
-
insertMany: (rows) => insertMany(this._ctx, entityName, rows),
|
|
5695
|
+
insert: (data) => this._m(`${entityName}.insert`, () => insert(this._ctx, entityName, data)),
|
|
5696
|
+
insertMany: (rows) => this._m(`${entityName}.insertMany(${rows.length})`, () => insertMany(this._ctx, entityName, rows)),
|
|
5295
5697
|
update: (idOrData, data) => {
|
|
5296
5698
|
if (typeof idOrData === "number")
|
|
5297
5699
|
return update(this._ctx, entityName, idOrData, data);
|
|
@@ -5336,8 +5738,6 @@ class _Database {
|
|
|
5336
5738
|
restore: (id) => {
|
|
5337
5739
|
if (!this._softDeletes)
|
|
5338
5740
|
throw new Error("restore() requires softDeletes: true");
|
|
5339
|
-
if (this._debug)
|
|
5340
|
-
console.log("[satidb]", `UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`, [id]);
|
|
5341
5741
|
this.db.query(`UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`).run(id);
|
|
5342
5742
|
},
|
|
5343
5743
|
select: (...cols) => createQueryBuilder(this._ctx, entityName, cols),
|
package/package.json
CHANGED
|
@@ -1,57 +1,58 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "sqlite-zod-orm",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Type-safe SQLite ORM for Bun — Zod schemas, fluent queries, auto relationships, zero SQL",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./src/index.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"import": "./dist/index.js",
|
|
12
|
-
"types": "./src/index.ts"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm",
|
|
17
|
-
"clean": "rm -rf dist",
|
|
18
|
-
"test": "bun test",
|
|
19
|
-
"bench": "bun bench/triggers-vs-naive.ts && bun bench/poll-strategy.ts && bun bench/indexes.ts",
|
|
20
|
-
"prepublishOnly": "bun run build"
|
|
21
|
-
},
|
|
22
|
-
"files": [
|
|
23
|
-
"src",
|
|
24
|
-
"dist",
|
|
25
|
-
"README.md"
|
|
26
|
-
],
|
|
27
|
-
"keywords": [
|
|
28
|
-
"sqlite",
|
|
29
|
-
"database",
|
|
30
|
-
"bun",
|
|
31
|
-
"typescript",
|
|
32
|
-
"type-safe",
|
|
33
|
-
"orm",
|
|
34
|
-
"zod",
|
|
35
|
-
"sql",
|
|
36
|
-
"query-builder",
|
|
37
|
-
"relationships"
|
|
38
|
-
],
|
|
39
|
-
"author": "7flash",
|
|
40
|
-
"license": "MIT",
|
|
41
|
-
"repository": {
|
|
42
|
-
"type": "git",
|
|
43
|
-
"url": "git@github.com:7flash/sqlite-zod-orm.git"
|
|
44
|
-
},
|
|
45
|
-
"devDependencies": {
|
|
46
|
-
"bun-types": "latest"
|
|
47
|
-
},
|
|
48
|
-
"peerDependencies": {
|
|
49
|
-
"typescript": "^5.0.0"
|
|
50
|
-
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "sqlite-zod-orm",
|
|
3
|
+
"version": "3.24.0",
|
|
4
|
+
"description": "Type-safe SQLite ORM for Bun — Zod schemas, fluent queries, auto relationships, zero SQL",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./src/index.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"test": "bun test",
|
|
19
|
+
"bench": "bun bench/triggers-vs-naive.ts && bun bench/poll-strategy.ts && bun bench/indexes.ts",
|
|
20
|
+
"prepublishOnly": "bun run build"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"src",
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"keywords": [
|
|
28
|
+
"sqlite",
|
|
29
|
+
"database",
|
|
30
|
+
"bun",
|
|
31
|
+
"typescript",
|
|
32
|
+
"type-safe",
|
|
33
|
+
"orm",
|
|
34
|
+
"zod",
|
|
35
|
+
"sql",
|
|
36
|
+
"query-builder",
|
|
37
|
+
"relationships"
|
|
38
|
+
],
|
|
39
|
+
"author": "7flash",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git@github.com:7flash/sqlite-zod-orm.git"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"bun-types": "latest"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"typescript": "^5.0.0"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"measure-fn": "^3.3.0",
|
|
53
|
+
"zod": "^3.25.67"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"bun": ">=1.0.0"
|
|
57
|
+
}
|
|
57
58
|
}
|
package/src/builder.ts
CHANGED
|
@@ -572,6 +572,34 @@ export class QueryBuilder<T extends Record<string, any>, TResult extends Record<
|
|
|
572
572
|
return (result[0] as any)?.c ?? 0;
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Atomically increment a numeric column for matching rows.
|
|
577
|
+
* Returns the number of affected rows.
|
|
578
|
+
* ```ts
|
|
579
|
+
* db.users.select().where({ id: 1 }).increment('score', 10)
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
increment(column: keyof T & string, amount: number = 1): number {
|
|
583
|
+
const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
|
|
584
|
+
const whereMatch = selectSql.match(/WHERE (.+?)(?:\s+ORDER|\s+LIMIT|\s+GROUP|\s+HAVING|$)/s);
|
|
585
|
+
const wherePart = whereMatch ? whereMatch[1] : '1=1';
|
|
586
|
+
|
|
587
|
+
const sql = `UPDATE "${this.tableName}" SET "${column}" = "${column}" + ? WHERE ${wherePart}`;
|
|
588
|
+
this.executor(sql, [amount, ...params], true);
|
|
589
|
+
const result = this.executor(`SELECT changes() as c`, [], true);
|
|
590
|
+
return (result[0] as any)?.c ?? 0;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Atomically decrement a numeric column for matching rows.
|
|
595
|
+
* Returns the number of affected rows.
|
|
596
|
+
* ```ts
|
|
597
|
+
* db.users.select().where({ id: 1 }).decrement('score', 5)
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
600
|
+
decrement(column: keyof T & string, amount: number = 1): number {
|
|
601
|
+
return this.increment(column, -amount);
|
|
602
|
+
}
|
|
575
603
|
|
|
576
604
|
// ---------- Thenable (async/await support) ----------
|
|
577
605
|
|
package/src/database.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* focused modules.
|
|
7
7
|
*/
|
|
8
8
|
import { Database as SqliteDatabase } from 'bun:sqlite';
|
|
9
|
+
import { createMeasure } from 'measure-fn';
|
|
9
10
|
import { z } from 'zod';
|
|
10
11
|
import { QueryBuilder, executeProxyQuery, createQueryBuilder, type ProxyQueryResult } from './query';
|
|
11
12
|
import type {
|
|
@@ -63,7 +64,22 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
63
64
|
/** Poll interval in ms. */
|
|
64
65
|
private _pollInterval: number;
|
|
65
66
|
|
|
67
|
+
/** Scoped measure-fn instance for instrumentation. */
|
|
68
|
+
private _measure: ReturnType<typeof createMeasure>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Conditional measurement helper — wraps with measure-fn only when debug is on.
|
|
72
|
+
* When debug is off, executes fn directly with zero overhead.
|
|
73
|
+
*/
|
|
74
|
+
private _m<T>(label: string, fn: () => T): T {
|
|
75
|
+
if (this._debug) return this._measure.measureSync.assert(label, fn);
|
|
76
|
+
return fn();
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
constructor(dbFile: string, schemas: Schemas, options: DatabaseOptions = {}) {
|
|
80
|
+
this._debug = options.debug === true;
|
|
81
|
+
this._measure = createMeasure('satidb');
|
|
82
|
+
|
|
67
83
|
this.db = new SqliteDatabase(dbFile);
|
|
68
84
|
if (options.wal !== false) this.db.run('PRAGMA journal_mode = WAL');
|
|
69
85
|
this.db.run('PRAGMA foreign_keys = ON');
|
|
@@ -72,7 +88,6 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
72
88
|
this._reactive = options.reactive !== false; // default true
|
|
73
89
|
this._timestamps = options.timestamps === true;
|
|
74
90
|
this._softDeletes = options.softDeletes === true;
|
|
75
|
-
this._debug = options.debug === true;
|
|
76
91
|
this._pollInterval = options.pollInterval ?? 100;
|
|
77
92
|
this.relationships = options.relations ? parseRelationsConfig(options.relations, schemas) : [];
|
|
78
93
|
|
|
@@ -91,18 +106,18 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
91
106
|
cascade: options.cascade ?? {},
|
|
92
107
|
};
|
|
93
108
|
|
|
94
|
-
this.initializeTables();
|
|
95
|
-
if (this._reactive) this.initializeChangeTracking();
|
|
96
|
-
this.runMigrations();
|
|
97
|
-
if (options.indexes) this.createIndexes(options.indexes);
|
|
98
|
-
if (options.unique) this.createUniqueConstraints(options.unique);
|
|
109
|
+
this._m('Init tables', () => this.initializeTables());
|
|
110
|
+
if (this._reactive) this._m('Change tracking', () => this.initializeChangeTracking());
|
|
111
|
+
this._m('Run migrations', () => this.runMigrations());
|
|
112
|
+
if (options.indexes) this._m('Create indexes', () => this.createIndexes(options.indexes!));
|
|
113
|
+
if (options.unique) this._m('Unique constraints', () => this.createUniqueConstraints(options.unique!));
|
|
99
114
|
|
|
100
115
|
// Create typed entity accessors (db.users, db.posts, etc.)
|
|
101
116
|
for (const entityName of Object.keys(schemas)) {
|
|
102
117
|
const key = entityName as keyof Schemas;
|
|
103
118
|
const accessor: EntityAccessor<Schemas[typeof key]> = {
|
|
104
|
-
insert: (data) => insert(this._ctx, entityName, data),
|
|
105
|
-
insertMany: (rows: any[]) => insertMany(this._ctx, entityName, rows),
|
|
119
|
+
insert: (data) => this._m(`${entityName}.insert`, () => insert(this._ctx, entityName, data)),
|
|
120
|
+
insertMany: (rows: any[]) => this._m(`${entityName}.insertMany(${rows.length})`, () => insertMany(this._ctx, entityName, rows)),
|
|
106
121
|
update: (idOrData: any, data?: any) => {
|
|
107
122
|
if (typeof idOrData === 'number') return update(this._ctx, entityName, idOrData, data);
|
|
108
123
|
return createUpdateBuilder(this._ctx, entityName, idOrData);
|
|
@@ -150,7 +165,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
150
165
|
}) as any,
|
|
151
166
|
restore: ((id: number) => {
|
|
152
167
|
if (!this._softDeletes) throw new Error('restore() requires softDeletes: true');
|
|
153
|
-
|
|
168
|
+
// debug log replaced by measure-fn instrumentation
|
|
154
169
|
this.db.query(`UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`).run(id);
|
|
155
170
|
}) as any,
|
|
156
171
|
select: (...cols: string[]) => createQueryBuilder(this._ctx, entityName, cols),
|