@loro-dev/flock 4.4.0 → 4.4.1
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.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/_moon_flock.ts +2920 -3107
- package/src/index.ts +39 -89
package/src/index.ts
CHANGED
|
@@ -29,6 +29,11 @@ import {
|
|
|
29
29
|
is_in_txn_ffi,
|
|
30
30
|
} from "./_moon_flock";
|
|
31
31
|
|
|
32
|
+
import {
|
|
33
|
+
EventBatcher,
|
|
34
|
+
type EventBatcherRuntime,
|
|
35
|
+
} from "../../packages/flock-sqlite/src/event-batcher";
|
|
36
|
+
|
|
32
37
|
type RawVersionVector = Record<string, [number, number]>;
|
|
33
38
|
type RawScanRow = { key: KeyPart[]; raw: ExportRecord; value?: Value };
|
|
34
39
|
type RawEventPayload = { data?: Value; metadata?: MetadataMap };
|
|
@@ -842,23 +847,26 @@ function isImportOptions(value: unknown): value is ImportOptions {
|
|
|
842
847
|
);
|
|
843
848
|
}
|
|
844
849
|
|
|
850
|
+
const defaultEventBatcherRuntime: EventBatcherRuntime = {
|
|
851
|
+
now: () => Date.now(),
|
|
852
|
+
setTimeout: (fn, ms) => setTimeout(fn, ms) as unknown,
|
|
853
|
+
clearTimeout: (handle) => clearTimeout(handle as any),
|
|
854
|
+
};
|
|
855
|
+
|
|
845
856
|
export class Flock {
|
|
846
857
|
private inner: ReturnType<typeof newFlock>;
|
|
847
858
|
private listeners: Set<(batch: EventBatch) => void> = new Set();
|
|
848
859
|
private nativeUnsubscribe: (() => void) | undefined;
|
|
849
|
-
|
|
850
|
-
private debounceState:
|
|
851
|
-
| {
|
|
852
|
-
timeout: number;
|
|
853
|
-
maxDebounceTime: number;
|
|
854
|
-
timerId: ReturnType<typeof setTimeout> | undefined;
|
|
855
|
-
maxTimerId: ReturnType<typeof setTimeout> | undefined;
|
|
856
|
-
pendingEvents: Event[];
|
|
857
|
-
}
|
|
858
|
-
| undefined;
|
|
860
|
+
private readonly eventBatcher: EventBatcher<Event>;
|
|
859
861
|
|
|
860
862
|
constructor(peerId?: string) {
|
|
861
863
|
this.inner = newFlock(normalizePeerId(peerId));
|
|
864
|
+
this.eventBatcher = new EventBatcher<Event>({
|
|
865
|
+
runtime: defaultEventBatcherRuntime,
|
|
866
|
+
emit: (source, events) => {
|
|
867
|
+
this.deliverBatch({ source, events });
|
|
868
|
+
},
|
|
869
|
+
});
|
|
862
870
|
}
|
|
863
871
|
|
|
864
872
|
private static fromInner(inner: ReturnType<typeof newFlock>): Flock {
|
|
@@ -1190,41 +1198,21 @@ export class Flock {
|
|
|
1190
1198
|
}
|
|
1191
1199
|
|
|
1192
1200
|
private handleBatch(batch: EventBatch): void {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
const wasEmpty = this.debounceState.pendingEvents.length === 0;
|
|
1196
|
-
this.debounceState.pendingEvents.push(...batch.events);
|
|
1197
|
-
this.resetDebounceTimer(wasEmpty);
|
|
1198
|
-
} else {
|
|
1199
|
-
// Normal mode: emit immediately
|
|
1200
|
-
this.emitBatch(batch);
|
|
1201
|
-
}
|
|
1201
|
+
const bufferable = batch.source === "local";
|
|
1202
|
+
this.eventBatcher.handleCommitEvents(batch.source, batch.events, bufferable);
|
|
1202
1203
|
}
|
|
1203
1204
|
|
|
1204
|
-
private
|
|
1205
|
-
|
|
1206
|
-
listener(batch);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
private resetDebounceTimer(isFirstEvent: boolean): void {
|
|
1211
|
-
if (this.debounceState === undefined) {
|
|
1205
|
+
private deliverBatch(batch: EventBatch): void {
|
|
1206
|
+
if (this.listeners.size === 0) {
|
|
1212
1207
|
return;
|
|
1213
1208
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
}, this.debounceState.timeout);
|
|
1222
|
-
|
|
1223
|
-
// Start max debounce timer on first pending event
|
|
1224
|
-
if (this.debounceState.maxTimerId === undefined && isFirstEvent) {
|
|
1225
|
-
this.debounceState.maxTimerId = setTimeout(() => {
|
|
1226
|
-
this.commit();
|
|
1227
|
-
}, this.debounceState.maxDebounceTime);
|
|
1209
|
+
const listeners = Array.from(this.listeners);
|
|
1210
|
+
for (const listener of listeners) {
|
|
1211
|
+
try {
|
|
1212
|
+
listener(batch);
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
void error;
|
|
1215
|
+
}
|
|
1228
1216
|
}
|
|
1229
1217
|
}
|
|
1230
1218
|
|
|
@@ -1273,19 +1261,7 @@ export class Flock {
|
|
|
1273
1261
|
"Cannot enable autoDebounceCommit while transaction is active",
|
|
1274
1262
|
);
|
|
1275
1263
|
}
|
|
1276
|
-
|
|
1277
|
-
throw new Error("autoDebounceCommit is already active");
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
const maxDebounceTime = options?.maxDebounceTime ?? 10000;
|
|
1281
|
-
|
|
1282
|
-
this.debounceState = {
|
|
1283
|
-
timeout,
|
|
1284
|
-
maxDebounceTime,
|
|
1285
|
-
timerId: undefined,
|
|
1286
|
-
maxTimerId: undefined,
|
|
1287
|
-
pendingEvents: [],
|
|
1288
|
-
};
|
|
1264
|
+
this.eventBatcher.autoDebounceCommit(timeout, options);
|
|
1289
1265
|
}
|
|
1290
1266
|
|
|
1291
1267
|
/**
|
|
@@ -1293,22 +1269,7 @@ export class Flock {
|
|
|
1293
1269
|
* No-op if autoDebounceCommit is not active.
|
|
1294
1270
|
*/
|
|
1295
1271
|
disableAutoDebounceCommit(): void {
|
|
1296
|
-
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
const { timerId, maxTimerId, pendingEvents } = this.debounceState;
|
|
1301
|
-
if (timerId !== undefined) {
|
|
1302
|
-
clearTimeout(timerId);
|
|
1303
|
-
}
|
|
1304
|
-
if (maxTimerId !== undefined) {
|
|
1305
|
-
clearTimeout(maxTimerId);
|
|
1306
|
-
}
|
|
1307
|
-
this.debounceState = undefined;
|
|
1308
|
-
|
|
1309
|
-
if (pendingEvents.length > 0) {
|
|
1310
|
-
this.emitBatch({ source: "local", events: pendingEvents });
|
|
1311
|
-
}
|
|
1272
|
+
this.eventBatcher.disableAutoDebounceCommit();
|
|
1312
1273
|
}
|
|
1313
1274
|
|
|
1314
1275
|
/**
|
|
@@ -1317,31 +1278,14 @@ export class Flock {
|
|
|
1317
1278
|
* No-op if autoDebounceCommit is not active or no events are pending.
|
|
1318
1279
|
*/
|
|
1319
1280
|
commit(): void {
|
|
1320
|
-
|
|
1321
|
-
return;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
const { timerId, maxTimerId, pendingEvents } = this.debounceState;
|
|
1325
|
-
if (timerId !== undefined) {
|
|
1326
|
-
clearTimeout(timerId);
|
|
1327
|
-
this.debounceState.timerId = undefined;
|
|
1328
|
-
}
|
|
1329
|
-
if (maxTimerId !== undefined) {
|
|
1330
|
-
clearTimeout(maxTimerId);
|
|
1331
|
-
this.debounceState.maxTimerId = undefined;
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
if (pendingEvents.length > 0) {
|
|
1335
|
-
this.emitBatch({ source: "local", events: pendingEvents });
|
|
1336
|
-
this.debounceState.pendingEvents = [];
|
|
1337
|
-
}
|
|
1281
|
+
this.eventBatcher.commit();
|
|
1338
1282
|
}
|
|
1339
1283
|
|
|
1340
1284
|
/**
|
|
1341
1285
|
* Check if auto-debounce mode is currently active.
|
|
1342
1286
|
*/
|
|
1343
1287
|
isAutoDebounceActive(): boolean {
|
|
1344
|
-
return this.
|
|
1288
|
+
return this.eventBatcher.isAutoDebounceActive();
|
|
1345
1289
|
}
|
|
1346
1290
|
|
|
1347
1291
|
/**
|
|
@@ -1359,6 +1303,7 @@ export class Flock {
|
|
|
1359
1303
|
* @returns The return value of the callback
|
|
1360
1304
|
* @throws Error if nested transaction attempted
|
|
1361
1305
|
* @throws Error if import is called during the transaction (auto-commits first)
|
|
1306
|
+
* @throws Error if called while autoDebounceCommit is active
|
|
1362
1307
|
*
|
|
1363
1308
|
* @example
|
|
1364
1309
|
* ```ts
|
|
@@ -1371,6 +1316,11 @@ export class Flock {
|
|
|
1371
1316
|
* ```
|
|
1372
1317
|
*/
|
|
1373
1318
|
txn<T>(callback: () => T): T {
|
|
1319
|
+
if (this.eventBatcher.isAutoDebounceActive()) {
|
|
1320
|
+
throw new Error(
|
|
1321
|
+
"Cannot start transaction while autoDebounceCommit is active",
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1374
1324
|
txn_begin_ffi(this.inner);
|
|
1375
1325
|
try {
|
|
1376
1326
|
const result = callback();
|