@loro-dev/flock 4.4.0 → 4.4.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/README.md +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +12 -6
- package/dist/index.d.ts +12 -6
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_moon_flock.ts +3642 -4260
- package/src/index.ts +51 -91
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 {
|
|
@@ -984,13 +992,21 @@ export class Flock {
|
|
|
984
992
|
}
|
|
985
993
|
|
|
986
994
|
merge(other: Flock): void {
|
|
995
|
+
this.eventBatcher.beforeImport();
|
|
987
996
|
merge(this.inner, other.inner);
|
|
988
997
|
}
|
|
989
998
|
|
|
990
999
|
/**
|
|
991
1000
|
* Returns the exclusive version vector, which only includes peers that have
|
|
992
|
-
* at least one entry in the current state.
|
|
993
|
-
*
|
|
1001
|
+
* at least one entry in the current state.
|
|
1002
|
+
*
|
|
1003
|
+
* Complexity: O(M + V log M + R (log L + log S)).
|
|
1004
|
+
* - M = memtablePeerCount
|
|
1005
|
+
* - V = vvPeerCount
|
|
1006
|
+
* - L = memtableLen
|
|
1007
|
+
* - R = scanned candidate rows in KV_BY_PEER_CLOCK
|
|
1008
|
+
* - S = storage key count in KV_BY_KEY
|
|
1009
|
+
* No full O(memtableSize) pre-scan is performed.
|
|
994
1010
|
*
|
|
995
1011
|
* Use this version when sending to other peers for incremental sync.
|
|
996
1012
|
*/
|
|
@@ -1069,6 +1085,7 @@ export class Flock {
|
|
|
1069
1085
|
}
|
|
1070
1086
|
|
|
1071
1087
|
private importJsonInternal(bundle: ExportBundle): ImportReport {
|
|
1088
|
+
this.eventBatcher.beforeImport();
|
|
1072
1089
|
const report = import_json_ffi(this.inner, bundle) as
|
|
1073
1090
|
| RawImportReport
|
|
1074
1091
|
| undefined;
|
|
@@ -1118,6 +1135,7 @@ export class Flock {
|
|
|
1118
1135
|
}
|
|
1119
1136
|
|
|
1120
1137
|
importJsonStr(bundle: string): ImportReport {
|
|
1138
|
+
this.eventBatcher.beforeImport();
|
|
1121
1139
|
const report = import_json_str_ffi(this.inner, bundle) as
|
|
1122
1140
|
| RawImportReport
|
|
1123
1141
|
| undefined;
|
|
@@ -1190,41 +1208,21 @@ export class Flock {
|
|
|
1190
1208
|
}
|
|
1191
1209
|
|
|
1192
1210
|
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
|
-
}
|
|
1211
|
+
const bufferable = batch.source === "local";
|
|
1212
|
+
this.eventBatcher.handleCommitEvents(batch.source, batch.events, bufferable);
|
|
1202
1213
|
}
|
|
1203
1214
|
|
|
1204
|
-
private
|
|
1205
|
-
|
|
1206
|
-
listener(batch);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
private resetDebounceTimer(isFirstEvent: boolean): void {
|
|
1211
|
-
if (this.debounceState === undefined) {
|
|
1215
|
+
private deliverBatch(batch: EventBatch): void {
|
|
1216
|
+
if (this.listeners.size === 0) {
|
|
1212
1217
|
return;
|
|
1213
1218
|
}
|
|
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);
|
|
1219
|
+
const listeners = Array.from(this.listeners);
|
|
1220
|
+
for (const listener of listeners) {
|
|
1221
|
+
try {
|
|
1222
|
+
listener(batch);
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
void error;
|
|
1225
|
+
}
|
|
1228
1226
|
}
|
|
1229
1227
|
}
|
|
1230
1228
|
|
|
@@ -1273,19 +1271,7 @@ export class Flock {
|
|
|
1273
1271
|
"Cannot enable autoDebounceCommit while transaction is active",
|
|
1274
1272
|
);
|
|
1275
1273
|
}
|
|
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
|
-
};
|
|
1274
|
+
this.eventBatcher.autoDebounceCommit(timeout, options);
|
|
1289
1275
|
}
|
|
1290
1276
|
|
|
1291
1277
|
/**
|
|
@@ -1293,22 +1279,7 @@ export class Flock {
|
|
|
1293
1279
|
* No-op if autoDebounceCommit is not active.
|
|
1294
1280
|
*/
|
|
1295
1281
|
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
|
-
}
|
|
1282
|
+
this.eventBatcher.disableAutoDebounceCommit();
|
|
1312
1283
|
}
|
|
1313
1284
|
|
|
1314
1285
|
/**
|
|
@@ -1317,31 +1288,14 @@ export class Flock {
|
|
|
1317
1288
|
* No-op if autoDebounceCommit is not active or no events are pending.
|
|
1318
1289
|
*/
|
|
1319
1290
|
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
|
-
}
|
|
1291
|
+
this.eventBatcher.commit();
|
|
1338
1292
|
}
|
|
1339
1293
|
|
|
1340
1294
|
/**
|
|
1341
1295
|
* Check if auto-debounce mode is currently active.
|
|
1342
1296
|
*/
|
|
1343
1297
|
isAutoDebounceActive(): boolean {
|
|
1344
|
-
return this.
|
|
1298
|
+
return this.eventBatcher.isAutoDebounceActive();
|
|
1345
1299
|
}
|
|
1346
1300
|
|
|
1347
1301
|
/**
|
|
@@ -1359,6 +1313,7 @@ export class Flock {
|
|
|
1359
1313
|
* @returns The return value of the callback
|
|
1360
1314
|
* @throws Error if nested transaction attempted
|
|
1361
1315
|
* @throws Error if import is called during the transaction (auto-commits first)
|
|
1316
|
+
* @throws Error if called while autoDebounceCommit is active
|
|
1362
1317
|
*
|
|
1363
1318
|
* @example
|
|
1364
1319
|
* ```ts
|
|
@@ -1371,6 +1326,11 @@ export class Flock {
|
|
|
1371
1326
|
* ```
|
|
1372
1327
|
*/
|
|
1373
1328
|
txn<T>(callback: () => T): T {
|
|
1329
|
+
if (this.eventBatcher.isAutoDebounceActive()) {
|
|
1330
|
+
throw new Error(
|
|
1331
|
+
"Cannot start transaction while autoDebounceCommit is active",
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1374
1334
|
txn_begin_ffi(this.inner);
|
|
1375
1335
|
try {
|
|
1376
1336
|
const result = callback();
|