sqlite-zod-orm 3.12.0 → 3.13.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 +77 -7
- package/package.json +1 -1
- package/src/builder.ts +22 -0
- package/src/context.ts +4 -1
- package/src/crud.ts +73 -6
- package/src/database.ts +10 -1
- package/src/types.ts +23 -0
package/dist/index.js
CHANGED
|
@@ -4517,6 +4517,15 @@ class QueryBuilder {
|
|
|
4517
4517
|
const data = this.all();
|
|
4518
4518
|
return { data, total, page, perPage, pages };
|
|
4519
4519
|
}
|
|
4520
|
+
countGrouped() {
|
|
4521
|
+
if (this.iqo.groupBy.length === 0) {
|
|
4522
|
+
throw new Error("countGrouped() requires at least one groupBy() call");
|
|
4523
|
+
}
|
|
4524
|
+
const groupCols = this.iqo.groupBy.map((c) => `"${c}"`).join(", ");
|
|
4525
|
+
const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
|
|
4526
|
+
const aggSql = selectSql.replace(/^SELECT .+? FROM/, `SELECT ${groupCols}, COUNT(*) as count FROM`);
|
|
4527
|
+
return this.executor(aggSql, params, true);
|
|
4528
|
+
}
|
|
4520
4529
|
then(onfulfilled, onrejected) {
|
|
4521
4530
|
try {
|
|
4522
4531
|
const result = this.all();
|
|
@@ -4902,7 +4911,14 @@ function findMany(ctx, entityName, conditions = {}) {
|
|
|
4902
4911
|
}
|
|
4903
4912
|
function insert(ctx, entityName, data) {
|
|
4904
4913
|
const schema = ctx.schemas[entityName];
|
|
4905
|
-
|
|
4914
|
+
let inputData = { ...data };
|
|
4915
|
+
const hooks = ctx.hooks[entityName];
|
|
4916
|
+
if (hooks?.beforeInsert) {
|
|
4917
|
+
const result2 = hooks.beforeInsert(inputData);
|
|
4918
|
+
if (result2)
|
|
4919
|
+
inputData = result2;
|
|
4920
|
+
}
|
|
4921
|
+
const validatedData = asZodObject(schema).passthrough().parse(inputData);
|
|
4906
4922
|
const transformed = transformForStorage(validatedData);
|
|
4907
4923
|
if (ctx.timestamps) {
|
|
4908
4924
|
const now = new Date().toISOString();
|
|
@@ -4918,11 +4934,20 @@ function insert(ctx, entityName, data) {
|
|
|
4918
4934
|
const newEntity = getById(ctx, entityName, result.lastInsertRowid);
|
|
4919
4935
|
if (!newEntity)
|
|
4920
4936
|
throw new Error("Failed to retrieve entity after insertion");
|
|
4937
|
+
if (hooks?.afterInsert)
|
|
4938
|
+
hooks.afterInsert(newEntity);
|
|
4921
4939
|
return newEntity;
|
|
4922
4940
|
}
|
|
4923
4941
|
function update(ctx, entityName, id, data) {
|
|
4924
4942
|
const schema = ctx.schemas[entityName];
|
|
4925
|
-
|
|
4943
|
+
let inputData = { ...data };
|
|
4944
|
+
const hooks = ctx.hooks[entityName];
|
|
4945
|
+
if (hooks?.beforeUpdate) {
|
|
4946
|
+
const result = hooks.beforeUpdate(inputData, id);
|
|
4947
|
+
if (result)
|
|
4948
|
+
inputData = result;
|
|
4949
|
+
}
|
|
4950
|
+
const validatedData = asZodObject(schema).partial().parse(inputData);
|
|
4926
4951
|
const transformed = transformForStorage(validatedData);
|
|
4927
4952
|
if (Object.keys(transformed).length === 0 && !ctx.timestamps)
|
|
4928
4953
|
return getById(ctx, entityName, id);
|
|
@@ -4934,7 +4959,10 @@ function update(ctx, entityName, id, data) {
|
|
|
4934
4959
|
if (ctx.debug)
|
|
4935
4960
|
console.log("[satidb]", sql, [...Object.values(transformed), id]);
|
|
4936
4961
|
ctx.db.query(sql).run(...Object.values(transformed), id);
|
|
4937
|
-
|
|
4962
|
+
const updated = getById(ctx, entityName, id);
|
|
4963
|
+
if (hooks?.afterUpdate && updated)
|
|
4964
|
+
hooks.afterUpdate(updated);
|
|
4965
|
+
return updated;
|
|
4938
4966
|
}
|
|
4939
4967
|
function updateWhere(ctx, entityName, data, conditions) {
|
|
4940
4968
|
const schema = ctx.schemas[entityName];
|
|
@@ -4974,7 +5002,15 @@ function upsert(ctx, entityName, data, conditions = {}) {
|
|
|
4974
5002
|
return insert(ctx, entityName, insertData);
|
|
4975
5003
|
}
|
|
4976
5004
|
function deleteEntity(ctx, entityName, id) {
|
|
5005
|
+
const hooks = ctx.hooks[entityName];
|
|
5006
|
+
if (hooks?.beforeDelete) {
|
|
5007
|
+
const result = hooks.beforeDelete(id);
|
|
5008
|
+
if (result === false)
|
|
5009
|
+
return;
|
|
5010
|
+
}
|
|
4977
5011
|
ctx.db.query(`DELETE FROM "${entityName}" WHERE id = ?`).run(id);
|
|
5012
|
+
if (hooks?.afterDelete)
|
|
5013
|
+
hooks.afterDelete(id);
|
|
4978
5014
|
}
|
|
4979
5015
|
function deleteWhere(ctx, entityName, conditions) {
|
|
4980
5016
|
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
@@ -5010,10 +5046,17 @@ function insertMany(ctx, entityName, rows) {
|
|
|
5010
5046
|
return [];
|
|
5011
5047
|
const schema = ctx.schemas[entityName];
|
|
5012
5048
|
const zodSchema = asZodObject(schema).passthrough();
|
|
5049
|
+
const hooks = ctx.hooks[entityName];
|
|
5013
5050
|
const txn = ctx.db.transaction(() => {
|
|
5014
5051
|
const ids2 = [];
|
|
5015
|
-
for (
|
|
5016
|
-
|
|
5052
|
+
for (let data of rows) {
|
|
5053
|
+
let inputData = { ...data };
|
|
5054
|
+
if (hooks?.beforeInsert) {
|
|
5055
|
+
const result2 = hooks.beforeInsert(inputData);
|
|
5056
|
+
if (result2)
|
|
5057
|
+
inputData = result2;
|
|
5058
|
+
}
|
|
5059
|
+
const validatedData = zodSchema.parse(inputData);
|
|
5017
5060
|
const transformed = transformForStorage(validatedData);
|
|
5018
5061
|
if (ctx.timestamps) {
|
|
5019
5062
|
const now = new Date().toISOString();
|
|
@@ -5029,7 +5072,24 @@ function insertMany(ctx, entityName, rows) {
|
|
|
5029
5072
|
return ids2;
|
|
5030
5073
|
});
|
|
5031
5074
|
const ids = txn();
|
|
5032
|
-
|
|
5075
|
+
const entities = ids.map((id) => getById(ctx, entityName, id)).filter(Boolean);
|
|
5076
|
+
if (hooks?.afterInsert) {
|
|
5077
|
+
for (const entity of entities)
|
|
5078
|
+
hooks.afterInsert(entity);
|
|
5079
|
+
}
|
|
5080
|
+
return entities;
|
|
5081
|
+
}
|
|
5082
|
+
function upsertMany(ctx, entityName, rows, conditions = {}) {
|
|
5083
|
+
if (rows.length === 0)
|
|
5084
|
+
return [];
|
|
5085
|
+
const txn = ctx.db.transaction(() => {
|
|
5086
|
+
const results = [];
|
|
5087
|
+
for (const data of rows) {
|
|
5088
|
+
results.push(upsert(ctx, entityName, data, conditions));
|
|
5089
|
+
}
|
|
5090
|
+
return results;
|
|
5091
|
+
});
|
|
5092
|
+
return txn();
|
|
5033
5093
|
}
|
|
5034
5094
|
|
|
5035
5095
|
// src/entity.ts
|
|
@@ -5101,7 +5161,8 @@ class _Database {
|
|
|
5101
5161
|
buildWhereClause: (conds, prefix) => buildWhereClause(conds, prefix),
|
|
5102
5162
|
debug: this._debug,
|
|
5103
5163
|
timestamps: this._timestamps,
|
|
5104
|
-
softDeletes: this._softDeletes
|
|
5164
|
+
softDeletes: this._softDeletes,
|
|
5165
|
+
hooks: options.hooks ?? {}
|
|
5105
5166
|
};
|
|
5106
5167
|
this.initializeTables();
|
|
5107
5168
|
if (this._reactive)
|
|
@@ -5122,11 +5183,20 @@ class _Database {
|
|
|
5122
5183
|
return createUpdateBuilder(this._ctx, entityName, idOrData);
|
|
5123
5184
|
},
|
|
5124
5185
|
upsert: (conditions, data) => upsert(this._ctx, entityName, data, conditions),
|
|
5186
|
+
upsertMany: (rows, conditions) => upsertMany(this._ctx, entityName, rows, conditions),
|
|
5125
5187
|
delete: (id) => {
|
|
5126
5188
|
if (typeof id === "number") {
|
|
5189
|
+
const hooks = this._ctx.hooks[entityName];
|
|
5190
|
+
if (hooks?.beforeDelete) {
|
|
5191
|
+
const result = hooks.beforeDelete(id);
|
|
5192
|
+
if (result === false)
|
|
5193
|
+
return;
|
|
5194
|
+
}
|
|
5127
5195
|
if (this._softDeletes) {
|
|
5128
5196
|
const now = new Date().toISOString();
|
|
5129
5197
|
this.db.query(`UPDATE "${entityName}" SET "deletedAt" = ? WHERE id = ?`).run(now, id);
|
|
5198
|
+
if (hooks?.afterDelete)
|
|
5199
|
+
hooks.afterDelete(id);
|
|
5130
5200
|
return;
|
|
5131
5201
|
}
|
|
5132
5202
|
return deleteEntity(this._ctx, entityName, id);
|
package/package.json
CHANGED
package/src/builder.ts
CHANGED
|
@@ -394,6 +394,28 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
394
394
|
return { data, total, page, perPage, pages };
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Count rows per group. Must call `.groupBy()` first.
|
|
399
|
+
* Returns an array of objects with the grouped column(s) and a `count` field.
|
|
400
|
+
*
|
|
401
|
+
* ```ts
|
|
402
|
+
* db.users.select('role').groupBy('role').countGrouped()
|
|
403
|
+
* // → [{ role: 'admin', count: 5 }, { role: 'member', count: 12 }]
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
countGrouped(): (Record<string, any> & { count: number })[] {
|
|
407
|
+
if (this.iqo.groupBy.length === 0) {
|
|
408
|
+
throw new Error('countGrouped() requires at least one groupBy() call');
|
|
409
|
+
}
|
|
410
|
+
const groupCols = this.iqo.groupBy.map(c => `"${c}"`).join(', ');
|
|
411
|
+
const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
|
|
412
|
+
const aggSql = selectSql.replace(
|
|
413
|
+
/^SELECT .+? FROM/,
|
|
414
|
+
`SELECT ${groupCols}, COUNT(*) as count FROM`
|
|
415
|
+
);
|
|
416
|
+
return this.executor(aggSql, params, true) as any;
|
|
417
|
+
}
|
|
418
|
+
|
|
397
419
|
|
|
398
420
|
|
|
399
421
|
// ---------- Thenable (async/await support) ----------
|
package/src/context.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* can access the Database's internals without importing the full class.
|
|
6
6
|
*/
|
|
7
7
|
import type { Database as SqliteDatabase } from 'bun:sqlite';
|
|
8
|
-
import type { SchemaMap, Relationship, AugmentedEntity } from './types';
|
|
8
|
+
import type { SchemaMap, Relationship, AugmentedEntity, TableHooks } from './types';
|
|
9
9
|
|
|
10
10
|
export interface DatabaseContext {
|
|
11
11
|
/** The raw bun:sqlite Database handle. */
|
|
@@ -31,4 +31,7 @@ export interface DatabaseContext {
|
|
|
31
31
|
|
|
32
32
|
/** Whether soft deletes are enabled (deletedAt column). */
|
|
33
33
|
softDeletes: boolean;
|
|
34
|
+
|
|
35
|
+
/** Lifecycle hooks keyed by table name. */
|
|
36
|
+
hooks: Record<string, TableHooks>;
|
|
34
37
|
}
|
package/src/crud.ts
CHANGED
|
@@ -40,7 +40,16 @@ export function findMany(ctx: DatabaseContext, entityName: string, conditions: R
|
|
|
40
40
|
|
|
41
41
|
export function insert<T extends Record<string, any>>(ctx: DatabaseContext, entityName: string, data: Omit<T, 'id'>): AugmentedEntity<any> {
|
|
42
42
|
const schema = ctx.schemas[entityName]!;
|
|
43
|
-
|
|
43
|
+
let inputData = { ...data } as Record<string, any>;
|
|
44
|
+
|
|
45
|
+
// beforeInsert hook — can transform data
|
|
46
|
+
const hooks = ctx.hooks[entityName];
|
|
47
|
+
if (hooks?.beforeInsert) {
|
|
48
|
+
const result = hooks.beforeInsert(inputData);
|
|
49
|
+
if (result) inputData = result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const validatedData = asZodObject(schema).passthrough().parse(inputData);
|
|
44
53
|
const transformed = transformForStorage(validatedData);
|
|
45
54
|
|
|
46
55
|
// Auto-inject timestamps
|
|
@@ -62,12 +71,24 @@ export function insert<T extends Record<string, any>>(ctx: DatabaseContext, enti
|
|
|
62
71
|
const newEntity = getById(ctx, entityName, result.lastInsertRowid as number);
|
|
63
72
|
if (!newEntity) throw new Error('Failed to retrieve entity after insertion');
|
|
64
73
|
|
|
74
|
+
// afterInsert hook
|
|
75
|
+
if (hooks?.afterInsert) hooks.afterInsert(newEntity);
|
|
76
|
+
|
|
65
77
|
return newEntity;
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
export function update<T extends Record<string, any>>(ctx: DatabaseContext, entityName: string, id: number, data: Partial<Omit<T, 'id'>>): AugmentedEntity<any> | null {
|
|
69
81
|
const schema = ctx.schemas[entityName]!;
|
|
70
|
-
|
|
82
|
+
let inputData = { ...data } as Record<string, any>;
|
|
83
|
+
|
|
84
|
+
// beforeUpdate hook — can transform data
|
|
85
|
+
const hooks = ctx.hooks[entityName];
|
|
86
|
+
if (hooks?.beforeUpdate) {
|
|
87
|
+
const result = hooks.beforeUpdate(inputData, id);
|
|
88
|
+
if (result) inputData = result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const validatedData = asZodObject(schema).partial().parse(inputData);
|
|
71
92
|
const transformed = transformForStorage(validatedData);
|
|
72
93
|
if (Object.keys(transformed).length === 0 && !ctx.timestamps) return getById(ctx, entityName, id);
|
|
73
94
|
|
|
@@ -81,7 +102,12 @@ export function update<T extends Record<string, any>>(ctx: DatabaseContext, enti
|
|
|
81
102
|
if (ctx.debug) console.log('[satidb]', sql, [...Object.values(transformed), id]);
|
|
82
103
|
ctx.db.query(sql).run(...Object.values(transformed), id);
|
|
83
104
|
|
|
84
|
-
|
|
105
|
+
const updated = getById(ctx, entityName, id);
|
|
106
|
+
|
|
107
|
+
// afterUpdate hook
|
|
108
|
+
if (hooks?.afterUpdate && updated) hooks.afterUpdate(updated);
|
|
109
|
+
|
|
110
|
+
return updated;
|
|
85
111
|
}
|
|
86
112
|
|
|
87
113
|
export function updateWhere(ctx: DatabaseContext, entityName: string, data: Record<string, any>, conditions: Record<string, any>): number {
|
|
@@ -131,7 +157,17 @@ export function upsert<T extends Record<string, any>>(ctx: DatabaseContext, enti
|
|
|
131
157
|
}
|
|
132
158
|
|
|
133
159
|
export function deleteEntity(ctx: DatabaseContext, entityName: string, id: number): void {
|
|
160
|
+
// beforeDelete hook — return false to cancel
|
|
161
|
+
const hooks = ctx.hooks[entityName];
|
|
162
|
+
if (hooks?.beforeDelete) {
|
|
163
|
+
const result = hooks.beforeDelete(id);
|
|
164
|
+
if (result === false) return;
|
|
165
|
+
}
|
|
166
|
+
|
|
134
167
|
ctx.db.query(`DELETE FROM "${entityName}" WHERE id = ?`).run(id);
|
|
168
|
+
|
|
169
|
+
// afterDelete hook
|
|
170
|
+
if (hooks?.afterDelete) hooks.afterDelete(id);
|
|
135
171
|
}
|
|
136
172
|
|
|
137
173
|
/** Delete all rows matching the given conditions. Returns the number of rows affected. */
|
|
@@ -169,11 +205,20 @@ export function insertMany<T extends Record<string, any>>(ctx: DatabaseContext,
|
|
|
169
205
|
if (rows.length === 0) return [];
|
|
170
206
|
const schema = ctx.schemas[entityName]!;
|
|
171
207
|
const zodSchema = asZodObject(schema).passthrough();
|
|
208
|
+
const hooks = ctx.hooks[entityName];
|
|
172
209
|
|
|
173
210
|
const txn = ctx.db.transaction(() => {
|
|
174
211
|
const ids: number[] = [];
|
|
175
|
-
for (
|
|
176
|
-
|
|
212
|
+
for (let data of rows) {
|
|
213
|
+
let inputData = { ...data } as Record<string, any>;
|
|
214
|
+
|
|
215
|
+
// beforeInsert hook
|
|
216
|
+
if (hooks?.beforeInsert) {
|
|
217
|
+
const result = hooks.beforeInsert(inputData);
|
|
218
|
+
if (result) inputData = result;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const validatedData = zodSchema.parse(inputData);
|
|
177
222
|
const transformed = transformForStorage(validatedData);
|
|
178
223
|
|
|
179
224
|
if (ctx.timestamps) {
|
|
@@ -194,5 +239,27 @@ export function insertMany<T extends Record<string, any>>(ctx: DatabaseContext,
|
|
|
194
239
|
});
|
|
195
240
|
|
|
196
241
|
const ids = txn();
|
|
197
|
-
|
|
242
|
+
const entities = ids.map((id: number) => getById(ctx, entityName, id)!).filter(Boolean);
|
|
243
|
+
|
|
244
|
+
// afterInsert hooks
|
|
245
|
+
if (hooks?.afterInsert) {
|
|
246
|
+
for (const entity of entities) hooks.afterInsert(entity);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return entities;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Upsert multiple rows in a single transaction. */
|
|
253
|
+
export function upsertMany<T extends Record<string, any>>(ctx: DatabaseContext, entityName: string, rows: any[], conditions: Record<string, any> = {}): AugmentedEntity<any>[] {
|
|
254
|
+
if (rows.length === 0) return [];
|
|
255
|
+
|
|
256
|
+
const txn = ctx.db.transaction(() => {
|
|
257
|
+
const results: AugmentedEntity<any>[] = [];
|
|
258
|
+
for (const data of rows) {
|
|
259
|
+
results.push(upsert(ctx, entityName, data, conditions));
|
|
260
|
+
}
|
|
261
|
+
return results;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return txn();
|
|
198
265
|
}
|
package/src/database.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { DatabaseContext } from './context';
|
|
|
24
24
|
import { buildWhereClause } from './helpers';
|
|
25
25
|
import { attachMethods } from './entity';
|
|
26
26
|
import {
|
|
27
|
-
insert, insertMany, update, upsert, deleteEntity, createDeleteBuilder,
|
|
27
|
+
insert, insertMany, update, upsert, upsertMany, deleteEntity, createDeleteBuilder,
|
|
28
28
|
getById, getOne, findMany, updateWhere, createUpdateBuilder,
|
|
29
29
|
} from './crud';
|
|
30
30
|
|
|
@@ -86,6 +86,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
86
86
|
debug: this._debug,
|
|
87
87
|
timestamps: this._timestamps,
|
|
88
88
|
softDeletes: this._softDeletes,
|
|
89
|
+
hooks: options.hooks ?? {},
|
|
89
90
|
};
|
|
90
91
|
|
|
91
92
|
this.initializeTables();
|
|
@@ -105,12 +106,20 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
105
106
|
return createUpdateBuilder(this._ctx, entityName, idOrData);
|
|
106
107
|
},
|
|
107
108
|
upsert: (conditions, data) => upsert(this._ctx, entityName, data, conditions),
|
|
109
|
+
upsertMany: (rows: any[], conditions?: any) => upsertMany(this._ctx, entityName, rows, conditions),
|
|
108
110
|
delete: ((id?: any) => {
|
|
109
111
|
if (typeof id === 'number') {
|
|
112
|
+
// beforeDelete hook — return false to cancel
|
|
113
|
+
const hooks = this._ctx.hooks[entityName];
|
|
114
|
+
if (hooks?.beforeDelete) {
|
|
115
|
+
const result = hooks.beforeDelete(id);
|
|
116
|
+
if (result === false) return;
|
|
117
|
+
}
|
|
110
118
|
if (this._softDeletes) {
|
|
111
119
|
// Soft delete: set deletedAt instead of removing
|
|
112
120
|
const now = new Date().toISOString();
|
|
113
121
|
this.db.query(`UPDATE "${entityName}" SET "deletedAt" = ? WHERE id = ?`).run(now, id);
|
|
122
|
+
if (hooks?.afterDelete) hooks.afterDelete(id);
|
|
114
123
|
return;
|
|
115
124
|
}
|
|
116
125
|
return deleteEntity(this._ctx, entityName, id);
|
package/src/types.ts
CHANGED
|
@@ -19,6 +19,16 @@ export const asZodObject = (s: z.ZodType<any>) => s as unknown as z.ZodObject<an
|
|
|
19
19
|
/** Index definition: single column or composite columns */
|
|
20
20
|
export type IndexDef = string | string[];
|
|
21
21
|
|
|
22
|
+
/** Lifecycle hooks for a single table. */
|
|
23
|
+
export type TableHooks = {
|
|
24
|
+
beforeInsert?: (data: Record<string, any>) => Record<string, any> | void;
|
|
25
|
+
afterInsert?: (entity: Record<string, any>) => void;
|
|
26
|
+
beforeUpdate?: (data: Record<string, any>, id: number) => Record<string, any> | void;
|
|
27
|
+
afterUpdate?: (entity: Record<string, any>) => void;
|
|
28
|
+
beforeDelete?: (id: number) => false | void;
|
|
29
|
+
afterDelete?: (id: number) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
22
32
|
export type DatabaseOptions<R extends RelationsConfig = RelationsConfig> = {
|
|
23
33
|
indexes?: Record<string, IndexDef[]>;
|
|
24
34
|
/**
|
|
@@ -65,6 +75,17 @@ export type DatabaseOptions<R extends RelationsConfig = RelationsConfig> = {
|
|
|
65
75
|
* Default: `false`.
|
|
66
76
|
*/
|
|
67
77
|
debug?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Lifecycle hooks per table. Each hook receives data and can transform it.
|
|
80
|
+
*
|
|
81
|
+
* - `beforeInsert(data)` — called before insert, return modified data or void
|
|
82
|
+
* - `afterInsert(entity)` — called after insert with the persisted entity
|
|
83
|
+
* - `beforeUpdate(data, id)` — called before update, return modified data or void
|
|
84
|
+
* - `afterUpdate(entity)` — called after update with the updated entity
|
|
85
|
+
* - `beforeDelete(id)` — called before delete, return false to cancel
|
|
86
|
+
* - `afterDelete(id)` — called after delete
|
|
87
|
+
*/
|
|
88
|
+
hooks?: Record<string, TableHooks>;
|
|
68
89
|
};
|
|
69
90
|
|
|
70
91
|
export type Relationship = {
|
|
@@ -184,6 +205,7 @@ export type NavEntityAccessor<
|
|
|
184
205
|
update: ((id: number, data: Partial<Omit<z.input<S[Table & keyof S]>, 'id'>>) => NavEntity<S, R, Table> | null)
|
|
185
206
|
& ((data: Partial<Omit<z.input<S[Table & keyof S]>, 'id'>>) => UpdateBuilder<NavEntity<S, R, Table>>);
|
|
186
207
|
upsert: (conditions?: Partial<z.infer<S[Table & keyof S]>>, data?: Partial<z.infer<S[Table & keyof S]>>) => NavEntity<S, R, Table>;
|
|
208
|
+
upsertMany: (rows: Partial<z.infer<S[Table & keyof S]>>[], conditions?: Partial<z.infer<S[Table & keyof S]>>) => NavEntity<S, R, Table>[];
|
|
187
209
|
delete: ((id: number) => void) & (() => DeleteBuilder<NavEntity<S, R, Table>>);
|
|
188
210
|
restore: (id: number) => void;
|
|
189
211
|
select: (...cols: (keyof z.infer<S[Table & keyof S]> & string)[]) => QueryBuilder<NavEntity<S, R, Table>>;
|
|
@@ -215,6 +237,7 @@ export type EntityAccessor<S extends z.ZodType<any>> = {
|
|
|
215
237
|
insertMany: (rows: EntityData<S>[]) => AugmentedEntity<S>[];
|
|
216
238
|
update: ((id: number, data: Partial<EntityData<S>>) => AugmentedEntity<S> | null) & ((data: Partial<EntityData<S>>) => UpdateBuilder<AugmentedEntity<S>>);
|
|
217
239
|
upsert: (conditions?: Partial<InferSchema<S>>, data?: Partial<InferSchema<S>>) => AugmentedEntity<S>;
|
|
240
|
+
upsertMany: (rows: Partial<InferSchema<S>>[], conditions?: Partial<InferSchema<S>>) => AugmentedEntity<S>[];
|
|
218
241
|
delete: ((id: number) => void) & (() => DeleteBuilder<AugmentedEntity<S>>);
|
|
219
242
|
/** Undo a soft delete by setting deletedAt = null. Requires softDeletes. */
|
|
220
243
|
restore: (id: number) => void;
|