sqlite-zod-orm 3.23.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.
Files changed (3) hide show
  1. package/dist/index.js +398 -10
  2. package/package.json +57 -56
  3. 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, {
@@ -5265,7 +5648,15 @@ class _Database {
5265
5648
  _changeWatermark = 0;
5266
5649
  _pollTimer = null;
5267
5650
  _pollInterval;
5651
+ _measure;
5652
+ _m(label, fn) {
5653
+ if (this._debug)
5654
+ return this._measure.measureSync.assert(label, fn);
5655
+ return fn();
5656
+ }
5268
5657
  constructor(dbFile, schemas, options = {}) {
5658
+ this._debug = options.debug === true;
5659
+ this._measure = createMeasure("satidb");
5269
5660
  this.db = new SqliteDatabase(dbFile);
5270
5661
  if (options.wal !== false)
5271
5662
  this.db.run("PRAGMA journal_mode = WAL");
@@ -5275,7 +5666,6 @@ class _Database {
5275
5666
  this._reactive = options.reactive !== false;
5276
5667
  this._timestamps = options.timestamps === true;
5277
5668
  this._softDeletes = options.softDeletes === true;
5278
- this._debug = options.debug === true;
5279
5669
  this._pollInterval = options.pollInterval ?? 100;
5280
5670
  this.relationships = options.relations ? parseRelationsConfig(options.relations, schemas) : [];
5281
5671
  this._ctx = {
@@ -5291,19 +5681,19 @@ class _Database {
5291
5681
  computed: options.computed ?? {},
5292
5682
  cascade: options.cascade ?? {}
5293
5683
  };
5294
- this.initializeTables();
5684
+ this._m("Init tables", () => this.initializeTables());
5295
5685
  if (this._reactive)
5296
- this.initializeChangeTracking();
5297
- this.runMigrations();
5686
+ this._m("Change tracking", () => this.initializeChangeTracking());
5687
+ this._m("Run migrations", () => this.runMigrations());
5298
5688
  if (options.indexes)
5299
- this.createIndexes(options.indexes);
5689
+ this._m("Create indexes", () => this.createIndexes(options.indexes));
5300
5690
  if (options.unique)
5301
- this.createUniqueConstraints(options.unique);
5691
+ this._m("Unique constraints", () => this.createUniqueConstraints(options.unique));
5302
5692
  for (const entityName of Object.keys(schemas)) {
5303
5693
  const key = entityName;
5304
5694
  const accessor = {
5305
- insert: (data) => insert(this._ctx, entityName, data),
5306
- 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)),
5307
5697
  update: (idOrData, data) => {
5308
5698
  if (typeof idOrData === "number")
5309
5699
  return update(this._ctx, entityName, idOrData, data);
@@ -5348,8 +5738,6 @@ class _Database {
5348
5738
  restore: (id) => {
5349
5739
  if (!this._softDeletes)
5350
5740
  throw new Error("restore() requires softDeletes: true");
5351
- if (this._debug)
5352
- console.log("[satidb]", `UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`, [id]);
5353
5741
  this.db.query(`UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`).run(id);
5354
5742
  },
5355
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.23.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
- "zod": "^3.25.67"
53
- },
54
- "engines": {
55
- "bun": ">=1.0.0"
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/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
- if (this._debug) console.log('[satidb]', `UPDATE "${entityName}" SET "deletedAt" = NULL WHERE id = ?`, [id]);
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),