@loro-dev/flock 4.3.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 +47 -0
- package/dist/index.d.ts +47 -0
- 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 +123 -6
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,11 +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>;
|
|
858
|
+
private listeners: Set<(batch: EventBatch) => void> = new Set();
|
|
859
|
+
private nativeUnsubscribe: (() => void) | undefined;
|
|
860
|
+
private readonly eventBatcher: EventBatcher<Event>;
|
|
847
861
|
|
|
848
862
|
constructor(peerId?: string) {
|
|
849
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
|
+
});
|
|
850
870
|
}
|
|
851
871
|
|
|
852
872
|
private static fromInner(inner: ReturnType<typeof newFlock>): Flock {
|
|
@@ -1167,14 +1187,105 @@ export class Flock {
|
|
|
1167
1187
|
}));
|
|
1168
1188
|
}
|
|
1169
1189
|
|
|
1190
|
+
private ensureNativeSubscription(): void {
|
|
1191
|
+
if (this.nativeUnsubscribe !== undefined) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
this.nativeUnsubscribe = subscribe_ffi(this.inner, (payload: unknown) => {
|
|
1195
|
+
const batch = decodeEventBatch(payload);
|
|
1196
|
+
this.handleBatch(batch);
|
|
1197
|
+
}) as () => void;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
private handleBatch(batch: EventBatch): void {
|
|
1201
|
+
const bufferable = batch.source === "local";
|
|
1202
|
+
this.eventBatcher.handleCommitEvents(batch.source, batch.events, bufferable);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
private deliverBatch(batch: EventBatch): void {
|
|
1206
|
+
if (this.listeners.size === 0) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
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
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1170
1219
|
subscribe(listener: (batch: EventBatch) => void): () => void {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1220
|
+
this.listeners.add(listener);
|
|
1221
|
+
this.ensureNativeSubscription();
|
|
1222
|
+
|
|
1223
|
+
return () => {
|
|
1224
|
+
this.listeners.delete(listener);
|
|
1225
|
+
// Optionally clean up native subscription when no listeners remain
|
|
1226
|
+
if (this.listeners.size === 0 && this.nativeUnsubscribe !== undefined) {
|
|
1227
|
+
this.nativeUnsubscribe();
|
|
1228
|
+
this.nativeUnsubscribe = undefined;
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* Enable auto-debounce mode. Events will be accumulated and emitted after
|
|
1235
|
+
* the specified timeout of inactivity. Each new operation resets the timer.
|
|
1236
|
+
*
|
|
1237
|
+
* Use `commit()` to force immediate emission of pending events.
|
|
1238
|
+
* Use `disableAutoDebounceCommit()` to disable and emit pending events.
|
|
1239
|
+
*
|
|
1240
|
+
* @param timeout - Debounce timeout in milliseconds
|
|
1241
|
+
* @param options - Optional configuration object with maxDebounceTime (default: 10000ms)
|
|
1242
|
+
* @throws Error if called while a transaction is active
|
|
1243
|
+
* @throws Error if autoDebounceCommit is already active
|
|
1244
|
+
*
|
|
1245
|
+
* @example
|
|
1246
|
+
* ```ts
|
|
1247
|
+
* flock.autoDebounceCommit(100);
|
|
1248
|
+
* flock.put(["a"], 1);
|
|
1249
|
+
* flock.put(["b"], 2);
|
|
1250
|
+
* // No events emitted yet...
|
|
1251
|
+
* // After 100ms of inactivity, subscribers receive single EventBatch
|
|
1252
|
+
* // If operations keep coming, commit happens after maxDebounceTime (10s default)
|
|
1253
|
+
* ```
|
|
1254
|
+
*/
|
|
1255
|
+
autoDebounceCommit(
|
|
1256
|
+
timeout: number,
|
|
1257
|
+
options?: { maxDebounceTime?: number },
|
|
1258
|
+
): void {
|
|
1259
|
+
if (this.isInTxn()) {
|
|
1260
|
+
throw new Error(
|
|
1261
|
+
"Cannot enable autoDebounceCommit while transaction is active",
|
|
1262
|
+
);
|
|
1176
1263
|
}
|
|
1177
|
-
|
|
1264
|
+
this.eventBatcher.autoDebounceCommit(timeout, options);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* Disable auto-debounce mode and emit any pending events immediately.
|
|
1269
|
+
* No-op if autoDebounceCommit is not active.
|
|
1270
|
+
*/
|
|
1271
|
+
disableAutoDebounceCommit(): void {
|
|
1272
|
+
this.eventBatcher.disableAutoDebounceCommit();
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Force immediate emission of any pending debounced events.
|
|
1277
|
+
* Does not disable auto-debounce mode - new operations will continue to be debounced.
|
|
1278
|
+
* No-op if autoDebounceCommit is not active or no events are pending.
|
|
1279
|
+
*/
|
|
1280
|
+
commit(): void {
|
|
1281
|
+
this.eventBatcher.commit();
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Check if auto-debounce mode is currently active.
|
|
1286
|
+
*/
|
|
1287
|
+
isAutoDebounceActive(): boolean {
|
|
1288
|
+
return this.eventBatcher.isAutoDebounceActive();
|
|
1178
1289
|
}
|
|
1179
1290
|
|
|
1180
1291
|
/**
|
|
@@ -1192,6 +1303,7 @@ export class Flock {
|
|
|
1192
1303
|
* @returns The return value of the callback
|
|
1193
1304
|
* @throws Error if nested transaction attempted
|
|
1194
1305
|
* @throws Error if import is called during the transaction (auto-commits first)
|
|
1306
|
+
* @throws Error if called while autoDebounceCommit is active
|
|
1195
1307
|
*
|
|
1196
1308
|
* @example
|
|
1197
1309
|
* ```ts
|
|
@@ -1204,6 +1316,11 @@ export class Flock {
|
|
|
1204
1316
|
* ```
|
|
1205
1317
|
*/
|
|
1206
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
|
+
}
|
|
1207
1324
|
txn_begin_ffi(this.inner);
|
|
1208
1325
|
try {
|
|
1209
1326
|
const result = callback();
|