sqlite-zod-orm 3.6.0 → 3.6.2
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 +64 -32
- package/package.json +1 -1
- package/src/database.ts +103 -55
- package/src/index.ts +1 -1
- package/src/types.ts +10 -12
package/dist/index.js
CHANGED
|
@@ -4717,9 +4717,9 @@ class _Database {
|
|
|
4717
4717
|
on: (event, callback, options2) => {
|
|
4718
4718
|
if (event === "insert")
|
|
4719
4719
|
return this._createOnStream(entityName, callback, options2?.interval);
|
|
4720
|
-
if (event === "
|
|
4721
|
-
return this._createChangeStream(entityName, callback, options2?.interval);
|
|
4722
|
-
throw new Error(`Unknown event type: '${event}'. Supported: 'insert', '
|
|
4720
|
+
if (event === "update" || event === "delete")
|
|
4721
|
+
return this._createChangeStream(entityName, event, callback, options2?.interval);
|
|
4722
|
+
throw new Error(`Unknown event type: '${event}'. Supported: 'insert', 'update', 'delete'`);
|
|
4723
4723
|
},
|
|
4724
4724
|
_tableName: entityName
|
|
4725
4725
|
};
|
|
@@ -4799,23 +4799,33 @@ class _Database {
|
|
|
4799
4799
|
stopped = true;
|
|
4800
4800
|
};
|
|
4801
4801
|
}
|
|
4802
|
-
|
|
4803
|
-
|
|
4802
|
+
_watchers = new Map;
|
|
4803
|
+
_getWatcher(entityName) {
|
|
4804
|
+
if (this._watchers.has(entityName))
|
|
4805
|
+
return this._watchers.get(entityName);
|
|
4804
4806
|
const allRows = this.db.query(`SELECT * FROM "${entityName}" ORDER BY id ASC`).all();
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
+
const snapshot = new Map;
|
|
4808
|
+
const snapshotEntities = new Map;
|
|
4807
4809
|
for (const row of allRows) {
|
|
4808
4810
|
snapshot.set(row.id, JSON.stringify(row));
|
|
4809
4811
|
snapshotEntities.set(row.id, row);
|
|
4810
4812
|
}
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
+
const watcher = {
|
|
4814
|
+
updateListeners: new Set,
|
|
4815
|
+
deleteListeners: new Set,
|
|
4816
|
+
snapshot,
|
|
4817
|
+
snapshotEntities,
|
|
4818
|
+
lastRevision: this._getRevision(entityName),
|
|
4819
|
+
stopped: false
|
|
4820
|
+
};
|
|
4821
|
+
this._watchers.set(entityName, watcher);
|
|
4822
|
+
const interval = this.pollInterval;
|
|
4813
4823
|
const poll = async () => {
|
|
4814
|
-
if (stopped)
|
|
4824
|
+
if (watcher.stopped)
|
|
4815
4825
|
return;
|
|
4816
4826
|
const currentRevision = this._getRevision(entityName);
|
|
4817
|
-
if (currentRevision !== lastRevision) {
|
|
4818
|
-
lastRevision = currentRevision;
|
|
4827
|
+
if (currentRevision !== watcher.lastRevision) {
|
|
4828
|
+
watcher.lastRevision = currentRevision;
|
|
4819
4829
|
const currentRows = this.db.query(`SELECT * FROM "${entityName}" ORDER BY id ASC`).all();
|
|
4820
4830
|
const currentMap = new Map;
|
|
4821
4831
|
const currentEntities = new Map;
|
|
@@ -4823,35 +4833,57 @@ class _Database {
|
|
|
4823
4833
|
currentMap.set(row.id, JSON.stringify(row));
|
|
4824
4834
|
currentEntities.set(row.id, row);
|
|
4825
4835
|
}
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
+
if (watcher.updateListeners.size > 0) {
|
|
4837
|
+
for (const [id, json] of currentMap) {
|
|
4838
|
+
if (watcher.stopped)
|
|
4839
|
+
return;
|
|
4840
|
+
if (watcher.snapshot.has(id) && watcher.snapshot.get(id) !== json) {
|
|
4841
|
+
const entity = this._attachMethods(entityName, transformFromStorage(currentEntities.get(id), this.schemas[entityName]));
|
|
4842
|
+
const oldEntity = this._attachMethods(entityName, transformFromStorage(watcher.snapshotEntities.get(id), this.schemas[entityName]));
|
|
4843
|
+
for (const cb of watcher.updateListeners) {
|
|
4844
|
+
await cb(entity, oldEntity);
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4836
4847
|
}
|
|
4837
4848
|
}
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4849
|
+
if (watcher.deleteListeners.size > 0) {
|
|
4850
|
+
for (const [id] of watcher.snapshot) {
|
|
4851
|
+
if (watcher.stopped)
|
|
4852
|
+
return;
|
|
4853
|
+
if (!currentMap.has(id)) {
|
|
4854
|
+
const oldEntity = this._attachMethods(entityName, transformFromStorage(watcher.snapshotEntities.get(id), this.schemas[entityName]));
|
|
4855
|
+
for (const cb of watcher.deleteListeners) {
|
|
4856
|
+
await cb(oldEntity);
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4844
4859
|
}
|
|
4845
4860
|
}
|
|
4846
|
-
snapshot = currentMap;
|
|
4847
|
-
snapshotEntities = currentEntities;
|
|
4861
|
+
watcher.snapshot = currentMap;
|
|
4862
|
+
watcher.snapshotEntities = currentEntities;
|
|
4848
4863
|
}
|
|
4849
|
-
if (!stopped)
|
|
4864
|
+
if (!watcher.stopped)
|
|
4850
4865
|
setTimeout(poll, interval);
|
|
4851
4866
|
};
|
|
4852
4867
|
setTimeout(poll, interval);
|
|
4868
|
+
return watcher;
|
|
4869
|
+
}
|
|
4870
|
+
_createChangeStream(entityName, eventType, callback, _intervalOverride) {
|
|
4871
|
+
const watcher = this._getWatcher(entityName);
|
|
4872
|
+
if (eventType === "update") {
|
|
4873
|
+
watcher.updateListeners.add(callback);
|
|
4874
|
+
} else {
|
|
4875
|
+
watcher.deleteListeners.add(callback);
|
|
4876
|
+
}
|
|
4853
4877
|
return () => {
|
|
4854
|
-
|
|
4878
|
+
if (eventType === "update") {
|
|
4879
|
+
watcher.updateListeners.delete(callback);
|
|
4880
|
+
} else {
|
|
4881
|
+
watcher.deleteListeners.delete(callback);
|
|
4882
|
+
}
|
|
4883
|
+
if (watcher.updateListeners.size === 0 && watcher.deleteListeners.size === 0) {
|
|
4884
|
+
watcher.stopped = true;
|
|
4885
|
+
this._watchers.delete(entityName);
|
|
4886
|
+
}
|
|
4855
4887
|
};
|
|
4856
4888
|
}
|
|
4857
4889
|
insert(entityName, data) {
|
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -59,10 +59,10 @@ 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: (event: string, callback: (
|
|
62
|
+
on: (event: string, callback: (...args: any[]) => void | Promise<void>, options?: { interval?: number }) => {
|
|
63
63
|
if (event === 'insert') return this._createOnStream(entityName, callback, options?.interval);
|
|
64
|
-
if (event === '
|
|
65
|
-
throw new Error(`Unknown event type: '${event}'. Supported: 'insert', '
|
|
64
|
+
if (event === 'update' || event === 'delete') return this._createChangeStream(entityName, event, callback, options?.interval);
|
|
65
|
+
throw new Error(`Unknown event type: '${event}'. Supported: 'insert', 'update', 'delete'`);
|
|
66
66
|
},
|
|
67
67
|
_tableName: entityName,
|
|
68
68
|
};
|
|
@@ -204,37 +204,54 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
204
204
|
return () => { stopped = true; };
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
// ===========================================================================
|
|
208
|
+
// Table Watcher — shared snapshot-diff engine for update/delete events
|
|
209
|
+
// ===========================================================================
|
|
210
|
+
|
|
211
|
+
/** One watcher per table — shared by all update/delete listeners. */
|
|
212
|
+
private _watchers = new Map<string, {
|
|
213
|
+
updateListeners: Set<(row: any, oldRow: any) => void | Promise<void>>;
|
|
214
|
+
deleteListeners: Set<(row: any) => void | Promise<void>>;
|
|
215
|
+
snapshot: Map<number, string>;
|
|
216
|
+
snapshotEntities: Map<number, any>;
|
|
217
|
+
lastRevision: string;
|
|
218
|
+
stopped: boolean;
|
|
219
|
+
}>();
|
|
220
|
+
|
|
207
221
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* Maintains a full snapshot and diffs on each poll.
|
|
211
|
-
* Heavier than _createOnStream (which only tracks watermark), but catches all changes.
|
|
222
|
+
* Get or create a shared table watcher. One SELECT * per change event,
|
|
223
|
+
* no matter how many update/delete listeners are registered.
|
|
212
224
|
*/
|
|
213
|
-
|
|
214
|
-
entityName
|
|
215
|
-
callback: (event: any) => void | Promise<void>,
|
|
216
|
-
intervalOverride?: number,
|
|
217
|
-
): () => void {
|
|
218
|
-
const interval = intervalOverride ?? this.pollInterval;
|
|
225
|
+
private _getWatcher(entityName: string) {
|
|
226
|
+
if (this._watchers.has(entityName)) return this._watchers.get(entityName)!;
|
|
219
227
|
|
|
220
|
-
// Build initial snapshot
|
|
228
|
+
// Build initial snapshot
|
|
221
229
|
const allRows = this.db.query(`SELECT * FROM "${entityName}" ORDER BY id ASC`).all() as any[];
|
|
222
|
-
|
|
223
|
-
|
|
230
|
+
const snapshot = new Map<number, string>();
|
|
231
|
+
const snapshotEntities = new Map<number, any>();
|
|
224
232
|
for (const row of allRows) {
|
|
225
233
|
snapshot.set(row.id, JSON.stringify(row));
|
|
226
234
|
snapshotEntities.set(row.id, row);
|
|
227
235
|
}
|
|
228
236
|
|
|
229
|
-
|
|
230
|
-
|
|
237
|
+
const watcher = {
|
|
238
|
+
updateListeners: new Set<(row: any, oldRow: any) => void | Promise<void>>(),
|
|
239
|
+
deleteListeners: new Set<(row: any) => void | Promise<void>>(),
|
|
240
|
+
snapshot,
|
|
241
|
+
snapshotEntities,
|
|
242
|
+
lastRevision: this._getRevision(entityName),
|
|
243
|
+
stopped: false,
|
|
244
|
+
};
|
|
245
|
+
this._watchers.set(entityName, watcher);
|
|
231
246
|
|
|
247
|
+
// Single poll loop for all listeners on this table
|
|
248
|
+
const interval = this.pollInterval;
|
|
232
249
|
const poll = async () => {
|
|
233
|
-
if (stopped) return;
|
|
250
|
+
if (watcher.stopped) return;
|
|
234
251
|
|
|
235
252
|
const currentRevision = this._getRevision(entityName);
|
|
236
|
-
if (currentRevision !== lastRevision) {
|
|
237
|
-
lastRevision = currentRevision;
|
|
253
|
+
if (currentRevision !== watcher.lastRevision) {
|
|
254
|
+
watcher.lastRevision = currentRevision;
|
|
238
255
|
|
|
239
256
|
const currentRows = this.db.query(`SELECT * FROM "${entityName}" ORDER BY id ASC`).all() as any[];
|
|
240
257
|
const currentMap = new Map<number, string>();
|
|
@@ -244,52 +261,83 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
244
261
|
currentEntities.set(row.id, row);
|
|
245
262
|
}
|
|
246
263
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
entityName,
|
|
265
|
-
transformFromStorage(snapshotEntities.get(id), this.schemas[entityName]!)
|
|
266
|
-
);
|
|
267
|
-
await callback({ type: 'update', row: entity, oldRow: oldEntity });
|
|
264
|
+
// Dispatch updates
|
|
265
|
+
if (watcher.updateListeners.size > 0) {
|
|
266
|
+
for (const [id, json] of currentMap) {
|
|
267
|
+
if (watcher.stopped) return;
|
|
268
|
+
if (watcher.snapshot.has(id) && watcher.snapshot.get(id) !== json) {
|
|
269
|
+
const entity = this._attachMethods(
|
|
270
|
+
entityName,
|
|
271
|
+
transformFromStorage(currentEntities.get(id), this.schemas[entityName]!)
|
|
272
|
+
);
|
|
273
|
+
const oldEntity = this._attachMethods(
|
|
274
|
+
entityName,
|
|
275
|
+
transformFromStorage(watcher.snapshotEntities.get(id), this.schemas[entityName]!)
|
|
276
|
+
);
|
|
277
|
+
for (const cb of watcher.updateListeners) {
|
|
278
|
+
await cb(entity, oldEntity);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
268
281
|
}
|
|
269
282
|
}
|
|
270
283
|
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
284
|
+
// Dispatch deletes
|
|
285
|
+
if (watcher.deleteListeners.size > 0) {
|
|
286
|
+
for (const [id] of watcher.snapshot) {
|
|
287
|
+
if (watcher.stopped) return;
|
|
288
|
+
if (!currentMap.has(id)) {
|
|
289
|
+
const oldEntity = this._attachMethods(
|
|
290
|
+
entityName,
|
|
291
|
+
transformFromStorage(watcher.snapshotEntities.get(id), this.schemas[entityName]!)
|
|
292
|
+
);
|
|
293
|
+
for (const cb of watcher.deleteListeners) {
|
|
294
|
+
await cb(oldEntity);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
280
297
|
}
|
|
281
298
|
}
|
|
282
299
|
|
|
283
|
-
snapshot = currentMap;
|
|
284
|
-
snapshotEntities = currentEntities;
|
|
300
|
+
watcher.snapshot = currentMap;
|
|
301
|
+
watcher.snapshotEntities = currentEntities;
|
|
285
302
|
}
|
|
286
303
|
|
|
287
|
-
if (!stopped) setTimeout(poll, interval);
|
|
304
|
+
if (!watcher.stopped) setTimeout(poll, interval);
|
|
288
305
|
};
|
|
289
306
|
|
|
290
307
|
setTimeout(poll, interval);
|
|
308
|
+
return watcher;
|
|
309
|
+
}
|
|
291
310
|
|
|
292
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Register an update or delete listener on the shared table watcher.
|
|
313
|
+
* Returns an unsubscribe function that auto-stops the watcher when empty.
|
|
314
|
+
*/
|
|
315
|
+
public _createChangeStream(
|
|
316
|
+
entityName: string,
|
|
317
|
+
eventType: 'update' | 'delete',
|
|
318
|
+
callback: (...args: any[]) => void | Promise<void>,
|
|
319
|
+
_intervalOverride?: number, // reserved, uses global pollInterval
|
|
320
|
+
): () => void {
|
|
321
|
+
const watcher = this._getWatcher(entityName);
|
|
322
|
+
|
|
323
|
+
if (eventType === 'update') {
|
|
324
|
+
watcher.updateListeners.add(callback as any);
|
|
325
|
+
} else {
|
|
326
|
+
watcher.deleteListeners.add(callback as any);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return () => {
|
|
330
|
+
if (eventType === 'update') {
|
|
331
|
+
watcher.updateListeners.delete(callback as any);
|
|
332
|
+
} else {
|
|
333
|
+
watcher.deleteListeners.delete(callback as any);
|
|
334
|
+
}
|
|
335
|
+
// Auto-stop watcher when all listeners removed
|
|
336
|
+
if (watcher.updateListeners.size === 0 && watcher.deleteListeners.size === 0) {
|
|
337
|
+
watcher.stopped = true;
|
|
338
|
+
this._watchers.delete(entityName);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
293
341
|
}
|
|
294
342
|
|
|
295
343
|
// ===========================================================================
|
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,
|
|
11
11
|
EntityAccessor, TypedAccessors, AugmentedEntity, UpdateBuilder,
|
|
12
12
|
InferSchema, EntityData, IndexDef,
|
|
13
13
|
ProxyColumns, ColumnRef,
|
package/src/types.ts
CHANGED
|
@@ -45,12 +45,6 @@ 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
50
|
// Type Helpers
|
|
@@ -158,15 +152,17 @@ export type NavEntityAccessor<
|
|
|
158
152
|
/**
|
|
159
153
|
* Listen for table events.
|
|
160
154
|
*
|
|
161
|
-
* `'insert'` — streams new rows one at a time
|
|
162
|
-
* `'
|
|
155
|
+
* `'insert'` — streams new rows, one at a time.
|
|
156
|
+
* `'update'` — fires on row changes with (newRow, oldRow).
|
|
157
|
+
* `'delete'` — fires when a row is removed.
|
|
163
158
|
*
|
|
164
159
|
* Callbacks are awaited — strict ordering guaranteed even with async handlers.
|
|
165
160
|
* @returns Unsubscribe function.
|
|
166
161
|
*/
|
|
167
162
|
on: {
|
|
168
163
|
(event: 'insert', callback: (row: NavEntity<S, R, Table>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
169
|
-
(event: '
|
|
164
|
+
(event: 'update', callback: (row: NavEntity<S, R, Table>, oldRow: NavEntity<S, R, Table>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
165
|
+
(event: 'delete', callback: (row: NavEntity<S, R, Table>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
170
166
|
};
|
|
171
167
|
_tableName: string;
|
|
172
168
|
readonly _schema?: S[Table & keyof S];
|
|
@@ -196,15 +192,17 @@ export type EntityAccessor<S extends z.ZodType<any>> = {
|
|
|
196
192
|
/**
|
|
197
193
|
* Listen for table events.
|
|
198
194
|
*
|
|
199
|
-
* `'insert'` — streams new rows one at a time
|
|
200
|
-
* `'
|
|
195
|
+
* `'insert'` — streams new rows, one at a time.
|
|
196
|
+
* `'update'` — fires on row changes with (newRow, oldRow).
|
|
197
|
+
* `'delete'` — fires when a row is removed.
|
|
201
198
|
*
|
|
202
199
|
* Callbacks are awaited — strict ordering guaranteed even with async handlers.
|
|
203
200
|
* @returns Unsubscribe function.
|
|
204
201
|
*/
|
|
205
202
|
on: {
|
|
206
203
|
(event: 'insert', callback: (row: AugmentedEntity<S>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
207
|
-
(event: '
|
|
204
|
+
(event: 'update', callback: (row: AugmentedEntity<S>, oldRow: AugmentedEntity<S>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
205
|
+
(event: 'delete', callback: (row: AugmentedEntity<S>) => void | Promise<void>, options?: { interval?: number }): () => void;
|
|
208
206
|
};
|
|
209
207
|
_tableName: string;
|
|
210
208
|
readonly _schema?: S;
|