sqlite-zod-orm 3.5.1 → 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/README.md +1 -0
- package/dist/index.js +71 -6
- package/package.json +1 -1
- package/src/database.ts +98 -5
- package/src/index.ts +1 -1
- package/src/query-builder.ts +4 -1
- package/src/types.ts +30 -6
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -177,7 +177,8 @@ class QueryBuilder {
|
|
|
177
177
|
conditionResolver;
|
|
178
178
|
revisionGetter;
|
|
179
179
|
eagerLoader;
|
|
180
|
-
|
|
180
|
+
defaultPollInterval;
|
|
181
|
+
constructor(tableName, executor, singleExecutor, joinResolver, conditionResolver, revisionGetter, eagerLoader, pollInterval) {
|
|
181
182
|
this.tableName = tableName;
|
|
182
183
|
this.executor = executor;
|
|
183
184
|
this.singleExecutor = singleExecutor;
|
|
@@ -185,6 +186,7 @@ class QueryBuilder {
|
|
|
185
186
|
this.conditionResolver = conditionResolver ?? null;
|
|
186
187
|
this.revisionGetter = revisionGetter ?? null;
|
|
187
188
|
this.eagerLoader = eagerLoader ?? null;
|
|
189
|
+
this.defaultPollInterval = pollInterval ?? 500;
|
|
188
190
|
this.iqo = {
|
|
189
191
|
selects: [],
|
|
190
192
|
wheres: [],
|
|
@@ -357,7 +359,7 @@ class QueryBuilder {
|
|
|
357
359
|
return results[0]?.count ?? 0;
|
|
358
360
|
}
|
|
359
361
|
subscribe(callback, options = {}) {
|
|
360
|
-
const { interval =
|
|
362
|
+
const { interval = this.defaultPollInterval, immediate = true } = options;
|
|
361
363
|
const fingerprintSQL = this.buildFingerprintSQL();
|
|
362
364
|
let lastFingerprint = null;
|
|
363
365
|
const poll = () => {
|
|
@@ -4686,6 +4688,7 @@ class _Database {
|
|
|
4686
4688
|
schemas;
|
|
4687
4689
|
relationships;
|
|
4688
4690
|
options;
|
|
4691
|
+
pollInterval;
|
|
4689
4692
|
_revisions = {};
|
|
4690
4693
|
constructor(dbFile, schemas, options = {}) {
|
|
4691
4694
|
this.db = new SqliteDatabase(dbFile);
|
|
@@ -4693,6 +4696,7 @@ class _Database {
|
|
|
4693
4696
|
this.db.run("PRAGMA foreign_keys = ON");
|
|
4694
4697
|
this.schemas = schemas;
|
|
4695
4698
|
this.options = options;
|
|
4699
|
+
this.pollInterval = options.pollInterval ?? 500;
|
|
4696
4700
|
this.relationships = options.relations ? parseRelationsConfig(options.relations, schemas) : [];
|
|
4697
4701
|
this.initializeTables();
|
|
4698
4702
|
this.runMigrations();
|
|
@@ -4710,7 +4714,13 @@ class _Database {
|
|
|
4710
4714
|
upsert: (conditions, data) => this.upsert(entityName, data, conditions),
|
|
4711
4715
|
delete: (id) => this.delete(entityName, id),
|
|
4712
4716
|
select: (...cols) => this._createQueryBuilder(entityName, cols),
|
|
4713
|
-
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
|
+
},
|
|
4714
4724
|
_tableName: entityName
|
|
4715
4725
|
};
|
|
4716
4726
|
this[key] = accessor;
|
|
@@ -4760,8 +4770,8 @@ class _Database {
|
|
|
4760
4770
|
const dataVersion = this.db.query("PRAGMA data_version").get()?.data_version ?? 0;
|
|
4761
4771
|
return `${rev}:${dataVersion}`;
|
|
4762
4772
|
}
|
|
4763
|
-
_createOnStream(entityName, callback,
|
|
4764
|
-
const
|
|
4773
|
+
_createOnStream(entityName, callback, intervalOverride) {
|
|
4774
|
+
const interval = intervalOverride ?? this.pollInterval;
|
|
4765
4775
|
const maxRow = this.db.query(`SELECT MAX(id) as _max FROM "${entityName}"`).get();
|
|
4766
4776
|
let lastMaxId = maxRow?._max ?? 0;
|
|
4767
4777
|
let lastRevision = this._getRevision(entityName);
|
|
@@ -4789,6 +4799,61 @@ class _Database {
|
|
|
4789
4799
|
stopped = true;
|
|
4790
4800
|
};
|
|
4791
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
|
+
}
|
|
4792
4857
|
insert(entityName, data) {
|
|
4793
4858
|
const schema = this.schemas[entityName];
|
|
4794
4859
|
const validatedData = asZodObject(schema).passthrough().parse(data);
|
|
@@ -5044,7 +5109,7 @@ class _Database {
|
|
|
5044
5109
|
}
|
|
5045
5110
|
return null;
|
|
5046
5111
|
};
|
|
5047
|
-
const builder = new QueryBuilder(entityName, executor, singleExecutor, joinResolver, conditionResolver, revisionGetter, eagerLoader);
|
|
5112
|
+
const builder = new QueryBuilder(entityName, executor, singleExecutor, joinResolver, conditionResolver, revisionGetter, eagerLoader, this.pollInterval);
|
|
5048
5113
|
if (initialCols.length > 0)
|
|
5049
5114
|
builder.select(...initialCols);
|
|
5050
5115
|
return builder;
|
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -29,6 +29,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
29
29
|
private schemas: Schemas;
|
|
30
30
|
private relationships: Relationship[];
|
|
31
31
|
private options: DatabaseOptions;
|
|
32
|
+
private pollInterval: number;
|
|
32
33
|
|
|
33
34
|
/** In-memory revision counter per table — bumps on every write (insert/update/delete).
|
|
34
35
|
* Used by QueryBuilder.subscribe() fingerprint to detect ALL changes with zero overhead. */
|
|
@@ -40,6 +41,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
40
41
|
this.db.run('PRAGMA foreign_keys = ON');
|
|
41
42
|
this.schemas = schemas;
|
|
42
43
|
this.options = options;
|
|
44
|
+
this.pollInterval = options.pollInterval ?? 500;
|
|
43
45
|
this.relationships = options.relations ? parseRelationsConfig(options.relations, schemas) : [];
|
|
44
46
|
this.initializeTables();
|
|
45
47
|
this.runMigrations();
|
|
@@ -57,8 +59,11 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
57
59
|
upsert: (conditions, data) => this.upsert(entityName, data, conditions),
|
|
58
60
|
delete: (id) => this.delete(entityName, id),
|
|
59
61
|
select: (...cols: string[]) => this._createQueryBuilder(entityName, cols),
|
|
60
|
-
on: (callback: (row: any) => void
|
|
61
|
-
this._createOnStream(entityName, callback, options)
|
|
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
|
+
},
|
|
62
67
|
_tableName: entityName,
|
|
63
68
|
};
|
|
64
69
|
(this as any)[key] = accessor;
|
|
@@ -152,9 +157,9 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
152
157
|
public _createOnStream(
|
|
153
158
|
entityName: string,
|
|
154
159
|
callback: (row: any) => void | Promise<void>,
|
|
155
|
-
|
|
160
|
+
intervalOverride?: number,
|
|
156
161
|
): () => void {
|
|
157
|
-
const
|
|
162
|
+
const interval = intervalOverride ?? this.pollInterval;
|
|
158
163
|
|
|
159
164
|
// Initialize watermark to current max id (only emit NEW rows)
|
|
160
165
|
const maxRow = this.db.query(`SELECT MAX(id) as _max FROM "${entityName}"`).get() as any;
|
|
@@ -199,6 +204,94 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
199
204
|
return () => { stopped = true; };
|
|
200
205
|
}
|
|
201
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
|
+
|
|
202
295
|
// ===========================================================================
|
|
203
296
|
// CRUD
|
|
204
297
|
// ===========================================================================
|
|
@@ -547,7 +640,7 @@ class _Database<Schemas extends SchemaMap> {
|
|
|
547
640
|
return null;
|
|
548
641
|
};
|
|
549
642
|
|
|
550
|
-
const builder = new QueryBuilder(entityName, executor, singleExecutor, joinResolver, conditionResolver, revisionGetter, eagerLoader);
|
|
643
|
+
const builder = new QueryBuilder(entityName, executor, singleExecutor, joinResolver, conditionResolver, revisionGetter, eagerLoader, this.pollInterval);
|
|
551
644
|
if (initialCols.length > 0) builder.select(...initialCols);
|
|
552
645
|
return builder;
|
|
553
646
|
}
|
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/query-builder.ts
CHANGED
|
@@ -174,6 +174,7 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
174
174
|
private conditionResolver: ((conditions: Record<string, any>) => Record<string, any>) | null;
|
|
175
175
|
private revisionGetter: (() => string) | null;
|
|
176
176
|
private eagerLoader: ((parentTable: string, relation: string, parentIds: number[]) => { key: string; groups: Map<number, any[]> } | null) | null;
|
|
177
|
+
private defaultPollInterval: number;
|
|
177
178
|
|
|
178
179
|
constructor(
|
|
179
180
|
tableName: string,
|
|
@@ -183,6 +184,7 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
183
184
|
conditionResolver?: ((conditions: Record<string, any>) => Record<string, any>) | null,
|
|
184
185
|
revisionGetter?: (() => string) | null,
|
|
185
186
|
eagerLoader?: ((parentTable: string, relation: string, parentIds: number[]) => { key: string; groups: Map<number, any[]> } | null) | null,
|
|
187
|
+
pollInterval?: number,
|
|
186
188
|
) {
|
|
187
189
|
this.tableName = tableName;
|
|
188
190
|
this.executor = executor;
|
|
@@ -191,6 +193,7 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
191
193
|
this.conditionResolver = conditionResolver ?? null;
|
|
192
194
|
this.revisionGetter = revisionGetter ?? null;
|
|
193
195
|
this.eagerLoader = eagerLoader ?? null;
|
|
196
|
+
this.defaultPollInterval = pollInterval ?? 500;
|
|
194
197
|
this.iqo = {
|
|
195
198
|
selects: [],
|
|
196
199
|
wheres: [],
|
|
@@ -497,7 +500,7 @@ export class QueryBuilder<T extends Record<string, any>> {
|
|
|
497
500
|
callback: (rows: T[]) => void,
|
|
498
501
|
options: { interval?: number; immediate?: boolean } = {},
|
|
499
502
|
): () => void {
|
|
500
|
-
const { interval =
|
|
503
|
+
const { interval = this.defaultPollInterval, immediate = true } = options;
|
|
501
504
|
|
|
502
505
|
// Build the fingerprint SQL (COUNT + MAX(id)) using the same WHERE
|
|
503
506
|
const fingerprintSQL = this.buildFingerprintSQL();
|
package/src/types.ts
CHANGED
|
@@ -30,6 +30,11 @@ export type DatabaseOptions<R extends RelationsConfig = RelationsConfig> = {
|
|
|
30
30
|
* `books: { author_id: 'authors' }` → FOREIGN KEY, lazy nav, fluent join.
|
|
31
31
|
*/
|
|
32
32
|
relations?: R;
|
|
33
|
+
/**
|
|
34
|
+
* Global polling interval (ms) for `.on()` and `.subscribe()`.
|
|
35
|
+
* Can be overridden per-call. Default: 500ms.
|
|
36
|
+
*/
|
|
37
|
+
pollInterval?: number;
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
export type Relationship = {
|
|
@@ -40,6 +45,13 @@ export type Relationship = {
|
|
|
40
45
|
foreignKey: string;
|
|
41
46
|
};
|
|
42
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
|
+
|
|
43
55
|
// =============================================================================
|
|
44
56
|
// Type Helpers
|
|
45
57
|
// =============================================================================
|
|
@@ -144,12 +156,18 @@ export type NavEntityAccessor<
|
|
|
144
156
|
delete: (id: number) => void;
|
|
145
157
|
select: (...cols: (keyof z.infer<S[Table & keyof S]> & string)[]) => QueryBuilder<NavEntity<S, R, Table>>;
|
|
146
158
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
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
|
+
*
|
|
149
164
|
* Callbacks are awaited — strict ordering guaranteed even with async handlers.
|
|
150
165
|
* @returns Unsubscribe function.
|
|
151
166
|
*/
|
|
152
|
-
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
|
+
};
|
|
153
171
|
_tableName: string;
|
|
154
172
|
readonly _schema?: S[Table & keyof S];
|
|
155
173
|
};
|
|
@@ -176,12 +194,18 @@ export type EntityAccessor<S extends z.ZodType<any>> = {
|
|
|
176
194
|
delete: (id: number) => void;
|
|
177
195
|
select: (...cols: (keyof InferSchema<S> & string)[]) => QueryBuilder<AugmentedEntity<S>>;
|
|
178
196
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
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
|
+
*
|
|
181
202
|
* Callbacks are awaited — strict ordering guaranteed even with async handlers.
|
|
182
203
|
* @returns Unsubscribe function.
|
|
183
204
|
*/
|
|
184
|
-
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
|
+
};
|
|
185
209
|
_tableName: string;
|
|
186
210
|
readonly _schema?: S;
|
|
187
211
|
};
|