sqlite-zod-orm 3.24.0 → 3.25.0

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