sqlite-zod-orm 3.5.2 → 3.6.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 +62 -1
- package/package.json +1 -1
- package/src/database.ts +93 -2
- package/src/index.ts +1 -1
- package/src/types.ts +25 -6
package/dist/index.js
CHANGED
|
@@ -4714,7 +4714,13 @@ class _Database {
|
|
|
4714
4714
|
upsert: (conditions, data) => this.upsert(entityName, data, conditions),
|
|
4715
4715
|
delete: (id) => this.delete(entityName, id),
|
|
4716
4716
|
select: (...cols) => this._createQueryBuilder(entityName, cols),
|
|
4717
|
-
on: (callback, options2) =>
|
|
4717
|
+
on: (event, callback, options2) => {
|
|
4718
|
+
if (event === "insert")
|
|
4719
|
+
return this._createOnStream(entityName, callback, options2?.interval);
|
|
4720
|
+
if (event === "change")
|
|
4721
|
+
return this._createChangeStream(entityName, callback, options2?.interval);
|
|
4722
|
+
throw new Error(`Unknown event type: '${event}'. Supported: 'insert', 'change'`);
|
|
4723
|
+
},
|
|
4718
4724
|
_tableName: entityName
|
|
4719
4725
|
};
|
|
4720
4726
|
this[key] = accessor;
|
|
@@ -4793,6 +4799,61 @@ class _Database {
|
|
|
4793
4799
|
stopped = true;
|
|
4794
4800
|
};
|
|
4795
4801
|
}
|
|
4802
|
+
_createChangeStream(entityName, callback, intervalOverride) {
|
|
4803
|
+
const interval = intervalOverride ?? this.pollInterval;
|
|
4804
|
+
const allRows = this.db.query(`SELECT * FROM "${entityName}" ORDER BY id ASC`).all();
|
|
4805
|
+
let snapshot = new Map;
|
|
4806
|
+
let snapshotEntities = new Map;
|
|
4807
|
+
for (const row of allRows) {
|
|
4808
|
+
snapshot.set(row.id, JSON.stringify(row));
|
|
4809
|
+
snapshotEntities.set(row.id, row);
|
|
4810
|
+
}
|
|
4811
|
+
let lastRevision = this._getRevision(entityName);
|
|
4812
|
+
let stopped = false;
|
|
4813
|
+
const poll = async () => {
|
|
4814
|
+
if (stopped)
|
|
4815
|
+
return;
|
|
4816
|
+
const currentRevision = this._getRevision(entityName);
|
|
4817
|
+
if (currentRevision !== lastRevision) {
|
|
4818
|
+
lastRevision = currentRevision;
|
|
4819
|
+
const currentRows = this.db.query(`SELECT * FROM "${entityName}" ORDER BY id ASC`).all();
|
|
4820
|
+
const currentMap = new Map;
|
|
4821
|
+
const currentEntities = new Map;
|
|
4822
|
+
for (const row of currentRows) {
|
|
4823
|
+
currentMap.set(row.id, JSON.stringify(row));
|
|
4824
|
+
currentEntities.set(row.id, row);
|
|
4825
|
+
}
|
|
4826
|
+
for (const [id, json] of currentMap) {
|
|
4827
|
+
if (stopped)
|
|
4828
|
+
return;
|
|
4829
|
+
if (!snapshot.has(id)) {
|
|
4830
|
+
const entity = this._attachMethods(entityName, transformFromStorage(currentEntities.get(id), this.schemas[entityName]));
|
|
4831
|
+
await callback({ type: "insert", row: entity });
|
|
4832
|
+
} else if (snapshot.get(id) !== json) {
|
|
4833
|
+
const entity = this._attachMethods(entityName, transformFromStorage(currentEntities.get(id), this.schemas[entityName]));
|
|
4834
|
+
const oldEntity = this._attachMethods(entityName, transformFromStorage(snapshotEntities.get(id), this.schemas[entityName]));
|
|
4835
|
+
await callback({ type: "update", row: entity, oldRow: oldEntity });
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
for (const [id] of snapshot) {
|
|
4839
|
+
if (stopped)
|
|
4840
|
+
return;
|
|
4841
|
+
if (!currentMap.has(id)) {
|
|
4842
|
+
const oldEntity = this._attachMethods(entityName, transformFromStorage(snapshotEntities.get(id), this.schemas[entityName]));
|
|
4843
|
+
await callback({ type: "delete", row: oldEntity, oldRow: oldEntity });
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
snapshot = currentMap;
|
|
4847
|
+
snapshotEntities = currentEntities;
|
|
4848
|
+
}
|
|
4849
|
+
if (!stopped)
|
|
4850
|
+
setTimeout(poll, interval);
|
|
4851
|
+
};
|
|
4852
|
+
setTimeout(poll, interval);
|
|
4853
|
+
return () => {
|
|
4854
|
+
stopped = true;
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4796
4857
|
insert(entityName, data) {
|
|
4797
4858
|
const schema = this.schemas[entityName];
|
|
4798
4859
|
const validatedData = asZodObject(schema).passthrough().parse(data);
|
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -59,8 +59,11 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
59
59
|
upsert: (conditions, data) => this.upsert(entityName, data, conditions),
|
|
60
60
|
delete: (id) => this.delete(entityName, id),
|
|
61
61
|
select: (...cols: string[]) => this._createQueryBuilder(entityName, cols),
|
|
62
|
-
on: (callback: (row: any) => void | Promise<void>, options?: { interval?: number }) =>
|
|
63
|
-
this._createOnStream(entityName, callback, options?.interval)
|
|
62
|
+
on: (event: string, callback: (row: any) => void | Promise<void>, options?: { interval?: number }) => {
|
|
63
|
+
if (event === 'insert') return this._createOnStream(entityName, callback, options?.interval);
|
|
64
|
+
if (event === 'change') return this._createChangeStream(entityName, callback, options?.interval);
|
|
65
|
+
throw new Error(`Unknown event type: '${event}'. Supported: 'insert', 'change'`);
|
|
66
|
+
},
|
|
64
67
|
_tableName: entityName,
|
|
65
68
|
};
|
|
66
69
|
(this as any)[key] = accessor;
|
|
@@ -201,6 +204,94 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
201
204
|
return () => { stopped = true; };
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Stream all mutations (insert / update / delete) as ChangeEvent objects.
|
|
209
|
+
*
|
|
210
|
+
* Maintains a full snapshot and diffs on each poll.
|
|
211
|
+
* Heavier than _createOnStream (which only tracks watermark), but catches all changes.
|
|
212
|
+
*/
|
|
213
|
+
public _createChangeStream(
|
|
214
|
+
entityName: string,
|
|
215
|
+
callback: (event: any) => void | Promise<void>,
|
|
216
|
+
intervalOverride?: number,
|
|
217
|
+
): () => void {
|
|
218
|
+
const interval = intervalOverride ?? this.pollInterval;
|
|
219
|
+
|
|
220
|
+
// Build initial snapshot: Map<id, serialized row>
|
|
221
|
+
const allRows = this.db.query(`SELECT * FROM "${entityName}" ORDER BY id ASC`).all() as any[];
|
|
222
|
+
let snapshot = new Map<number, string>();
|
|
223
|
+
let snapshotEntities = new Map<number, any>();
|
|
224
|
+
for (const row of allRows) {
|
|
225
|
+
snapshot.set(row.id, JSON.stringify(row));
|
|
226
|
+
snapshotEntities.set(row.id, row);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let lastRevision: string = this._getRevision(entityName);
|
|
230
|
+
let stopped = false;
|
|
231
|
+
|
|
232
|
+
const poll = async () => {
|
|
233
|
+
if (stopped) return;
|
|
234
|
+
|
|
235
|
+
const currentRevision = this._getRevision(entityName);
|
|
236
|
+
if (currentRevision !== lastRevision) {
|
|
237
|
+
lastRevision = currentRevision;
|
|
238
|
+
|
|
239
|
+
const currentRows = this.db.query(`SELECT * FROM "${entityName}" ORDER BY id ASC`).all() as any[];
|
|
240
|
+
const currentMap = new Map<number, string>();
|
|
241
|
+
const currentEntities = new Map<number, any>();
|
|
242
|
+
for (const row of currentRows) {
|
|
243
|
+
currentMap.set(row.id, JSON.stringify(row));
|
|
244
|
+
currentEntities.set(row.id, row);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Detect inserts and updates
|
|
248
|
+
for (const [id, json] of currentMap) {
|
|
249
|
+
if (stopped) return;
|
|
250
|
+
if (!snapshot.has(id)) {
|
|
251
|
+
// INSERT
|
|
252
|
+
const entity = this._attachMethods(
|
|
253
|
+
entityName,
|
|
254
|
+
transformFromStorage(currentEntities.get(id), this.schemas[entityName]!)
|
|
255
|
+
);
|
|
256
|
+
await callback({ type: 'insert', row: entity });
|
|
257
|
+
} else if (snapshot.get(id) !== json) {
|
|
258
|
+
// UPDATE
|
|
259
|
+
const entity = this._attachMethods(
|
|
260
|
+
entityName,
|
|
261
|
+
transformFromStorage(currentEntities.get(id), this.schemas[entityName]!)
|
|
262
|
+
);
|
|
263
|
+
const oldEntity = this._attachMethods(
|
|
264
|
+
entityName,
|
|
265
|
+
transformFromStorage(snapshotEntities.get(id), this.schemas[entityName]!)
|
|
266
|
+
);
|
|
267
|
+
await callback({ type: 'update', row: entity, oldRow: oldEntity });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Detect deletes
|
|
272
|
+
for (const [id] of snapshot) {
|
|
273
|
+
if (stopped) return;
|
|
274
|
+
if (!currentMap.has(id)) {
|
|
275
|
+
const oldEntity = this._attachMethods(
|
|
276
|
+
entityName,
|
|
277
|
+
transformFromStorage(snapshotEntities.get(id), this.schemas[entityName]!)
|
|
278
|
+
);
|
|
279
|
+
await callback({ type: 'delete', row: oldEntity, oldRow: oldEntity });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
snapshot = currentMap;
|
|
284
|
+
snapshotEntities = currentEntities;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!stopped) setTimeout(poll, interval);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
setTimeout(poll, interval);
|
|
291
|
+
|
|
292
|
+
return () => { stopped = true; };
|
|
293
|
+
}
|
|
294
|
+
|
|
204
295
|
// ===========================================================================
|
|
205
296
|
// CRUD
|
|
206
297
|
// ===========================================================================
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { Database } from './database';
|
|
|
7
7
|
export type { DatabaseType } from './database';
|
|
8
8
|
|
|
9
9
|
export type {
|
|
10
|
-
SchemaMap, DatabaseOptions, Relationship,
|
|
10
|
+
SchemaMap, DatabaseOptions, Relationship, ChangeEvent,
|
|
11
11
|
EntityAccessor, TypedAccessors, AugmentedEntity, UpdateBuilder,
|
|
12
12
|
InferSchema, EntityData, IndexDef,
|
|
13
13
|
ProxyColumns, ColumnRef,
|
package/src/types.ts
CHANGED
|
@@ -45,6 +45,13 @@ export type Relationship = {
|
|
|
45
45
|
foreignKey: string;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
/** Change event emitted by .on('change', callback) */
|
|
49
|
+
export type ChangeEvent<T = any> = {
|
|
50
|
+
type: 'insert' | 'update' | 'delete';
|
|
51
|
+
row: T;
|
|
52
|
+
oldRow?: T; // present on 'update' and 'delete'
|
|
53
|
+
};
|
|
54
|
+
|
|
48
55
|
// =============================================================================
|
|
49
56
|
// Type Helpers
|
|
50
57
|
// =============================================================================
|
|
@@ -149,12 +156,18 @@ export type NavEntityAccessor<
|
|
|
149
156
|
delete: (id: number) => void;
|
|
150
157
|
select: (...cols: (keyof z.infer<S[Table & keyof S]> & string)[]) => QueryBuilder<NavEntity<S, R, Table>>;
|
|
151
158
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
159
|
+
* Listen for table events.
|
|
160
|
+
*
|
|
161
|
+
* `'insert'` — streams new rows one at a time, in insertion order.
|
|
162
|
+
* `'change'` — streams all mutations: { type: 'insert'|'update'|'delete', row, oldRow? }.
|
|
163
|
+
*
|
|
154
164
|
* Callbacks are awaited — strict ordering guaranteed even with async handlers.
|
|
155
165
|
* @returns Unsubscribe function.
|
|
156
166
|
*/
|
|
157
|
-
on:
|
|
167
|
+
on: {
|
|
168
|
+
(event: 'insert', callback: (row: NavEntity<S, R, Table>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
169
|
+
(event: 'change', callback: (event: ChangeEvent<NavEntity<S, R, Table>>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
170
|
+
};
|
|
158
171
|
_tableName: string;
|
|
159
172
|
readonly _schema?: S[Table & keyof S];
|
|
160
173
|
};
|
|
@@ -181,12 +194,18 @@ export type EntityAccessor<S extends z.ZodType<any>> = {
|
|
|
181
194
|
delete: (id: number) => void;
|
|
182
195
|
select: (...cols: (keyof InferSchema<S> & string)[]) => QueryBuilder<AugmentedEntity<S>>;
|
|
183
196
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
197
|
+
* Listen for table events.
|
|
198
|
+
*
|
|
199
|
+
* `'insert'` — streams new rows one at a time, in insertion order.
|
|
200
|
+
* `'change'` — streams all mutations: { type: 'insert'|'update'|'delete', row, oldRow? }.
|
|
201
|
+
*
|
|
186
202
|
* Callbacks are awaited — strict ordering guaranteed even with async handlers.
|
|
187
203
|
* @returns Unsubscribe function.
|
|
188
204
|
*/
|
|
189
|
-
on:
|
|
205
|
+
on: {
|
|
206
|
+
(event: 'insert', callback: (row: AugmentedEntity<S>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
207
|
+
(event: 'change', callback: (event: ChangeEvent<AugmentedEntity<S>>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
208
|
+
};
|
|
190
209
|
_tableName: string;
|
|
191
210
|
readonly _schema?: S;
|
|
192
211
|
};
|