@loro-dev/flock 3.0.0 → 4.0.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/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  newFlock,
3
3
  get_ffi,
4
+ get_entry_ffi,
4
5
  put_json_ffi,
5
6
  put_with_meta_ffi,
6
7
  delete_ffi,
@@ -8,6 +9,7 @@ import {
8
9
  set_peer_id,
9
10
  export_json_ffi,
10
11
  import_json_ffi,
12
+ import_json_str_ffi,
11
13
  version_ffi,
12
14
  get_max_physical_time_ffi,
13
15
  peer_id_ffi,
@@ -32,6 +34,16 @@ type RawEventEntry = {
32
34
  payload?: RawEventPayload;
33
35
  };
34
36
  type RawEventBatch = { source?: string; events?: RawEventEntry[] };
37
+ type RawEntryClock = {
38
+ physicalTime?: number;
39
+ logicalCounter?: number;
40
+ peerId?: string;
41
+ };
42
+ type RawEntryInfo = {
43
+ data?: Value;
44
+ metadata?: MetadataMap;
45
+ clock?: RawEntryClock;
46
+ };
35
47
 
36
48
  type MaybePromise<T> = T | Promise<T>;
37
49
 
@@ -39,6 +51,7 @@ type ExportOptions = {
39
51
  from?: VersionVector;
40
52
  hooks?: ExportHooks;
41
53
  pruneTombstonesBefore?: number;
54
+ peerId?: string;
42
55
  };
43
56
 
44
57
  type ImportOptions = {
@@ -56,7 +69,17 @@ export type VersionVectorEntry = {
56
69
  logicalCounter: number;
57
70
  };
58
71
 
59
- export type VersionVector = Record<string, VersionVectorEntry>;
72
+ export interface VersionVector {
73
+ [peer: string]: VersionVectorEntry | undefined;
74
+ }
75
+
76
+ export function encodeVersionVector(vector: VersionVector): Uint8Array {
77
+ return encodeVersionVectorBinary(vector);
78
+ }
79
+
80
+ export function decodeVersionVector(bytes: Uint8Array): VersionVector {
81
+ return decodeVersionVectorBinary(bytes);
82
+ }
60
83
 
61
84
  export type Value =
62
85
  | string
@@ -76,7 +99,10 @@ export type ExportRecord = {
76
99
  m?: MetadataMap;
77
100
  };
78
101
 
79
- export type ExportBundle = { version: number, entries: Record<string, ExportRecord> };
102
+ export type ExportBundle = {
103
+ version: number;
104
+ entries: Record<string, ExportRecord>;
105
+ };
80
106
 
81
107
  export type EntryClock = {
82
108
  physicalTime: number;
@@ -84,6 +110,12 @@ export type EntryClock = {
84
110
  peerId: string;
85
111
  };
86
112
 
113
+ export type EntryInfo = {
114
+ data?: Value;
115
+ metadata: MetadataMap;
116
+ clock: EntryClock;
117
+ };
118
+
87
119
  export type ExportPayload = {
88
120
  data?: Value;
89
121
  metadata?: MetadataMap;
@@ -181,6 +213,7 @@ export type EventBatch = {
181
213
  };
182
214
 
183
215
  const textEncoder = new TextEncoder();
216
+ const textDecoder = new TextDecoder();
184
217
 
185
218
  function utf8ByteLength(value: string): number {
186
219
  return textEncoder.encode(value).length;
@@ -192,7 +225,10 @@ function isValidPeerId(peerId: unknown): peerId is string {
192
225
 
193
226
  function createRandomPeerId(): string {
194
227
  const id = new Uint8Array(32);
195
- if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
228
+ if (
229
+ typeof crypto !== "undefined" &&
230
+ typeof crypto.getRandomValues === "function"
231
+ ) {
196
232
  crypto.getRandomValues(id);
197
233
  } else {
198
234
  for (let i = 0; i < 32; i += 1) {
@@ -207,46 +243,274 @@ function normalizePeerId(peerId?: string): string {
207
243
  return createRandomPeerId();
208
244
  }
209
245
  if (!isValidPeerId(peerId)) {
210
- throw new TypeError("peerId must be a UTF-8 string under 128 bytes");
246
+ throw new TypeError("peerId must be a UTF-8 string under 128 bytes.");
211
247
  }
212
248
  return peerId;
213
249
  }
214
250
 
215
- function encodeVersionVector(vv?: VersionVector): RawVersionVector | undefined {
216
- if (!vv) {
217
- return undefined;
251
+ type EncodableVersionVectorEntry = {
252
+ peer: string;
253
+ peerBytes: Uint8Array;
254
+ timestamp: number;
255
+ counter: number;
256
+ };
257
+
258
+ function comparePeerBytes(a: Uint8Array, b: Uint8Array): number {
259
+ if (a === b) {
260
+ return 0;
218
261
  }
219
- const raw: RawVersionVector = {};
220
- for (const [peer, entry] of Object.entries(vv)) {
221
- if (!entry) {
222
- continue;
262
+ const limit = Math.min(a.length, b.length);
263
+ for (let i = 0; i < limit; i += 1) {
264
+ const diff = a[i] - b[i];
265
+ if (diff !== 0) {
266
+ return diff;
223
267
  }
224
- if (!isValidPeerId(peer)) {
268
+ }
269
+ return a.length - b.length;
270
+ }
271
+
272
+ function collectEncodableVersionVectorEntries(
273
+ vv?: VersionVector,
274
+ ): EncodableVersionVectorEntry[] {
275
+ if (!vv || typeof vv !== "object") {
276
+ return [];
277
+ }
278
+ const entries: EncodableVersionVectorEntry[] = [];
279
+ for (const [peer, entry] of Object.entries(vv)) {
280
+ if (!entry || !isValidPeerId(peer)) {
225
281
  continue;
226
282
  }
227
283
  const { physicalTime, logicalCounter } = entry;
228
- if (typeof physicalTime !== "number" || Number.isNaN(physicalTime)) {
284
+ if (
285
+ typeof physicalTime !== "number" ||
286
+ !Number.isFinite(physicalTime) ||
287
+ typeof logicalCounter !== "number" ||
288
+ !Number.isFinite(logicalCounter)
289
+ ) {
229
290
  continue;
230
291
  }
231
- if (!Number.isFinite(logicalCounter)) {
232
- continue;
292
+ const peerBytes = textEncoder.encode(peer);
293
+ entries.push({
294
+ peer,
295
+ peerBytes,
296
+ timestamp: Math.trunc(physicalTime),
297
+ counter: Math.max(0, Math.trunc(logicalCounter)),
298
+ });
299
+ }
300
+ entries.sort((a, b) => {
301
+ if (a.timestamp !== b.timestamp) {
302
+ return a.timestamp - b.timestamp;
303
+ }
304
+ const peerCmp = comparePeerBytes(a.peerBytes, b.peerBytes);
305
+ if (peerCmp !== 0) {
306
+ return peerCmp;
307
+ }
308
+ return a.counter - b.counter;
309
+ });
310
+ return entries;
311
+ }
312
+
313
+ function writeUnsignedLeb128(value: number, out: number[]): void {
314
+ if (!Number.isFinite(value) || value < 0) {
315
+ throw new TypeError("leb128 values must be finite and non-negative");
316
+ }
317
+ let remaining = Math.trunc(value);
318
+ if (remaining === 0) {
319
+ out.push(0);
320
+ return;
321
+ }
322
+ while (remaining > 0) {
323
+ const byte = remaining % 0x80;
324
+ remaining = Math.floor(remaining / 0x80);
325
+ out.push(remaining > 0 ? byte | 0x80 : byte);
326
+ }
327
+ }
328
+
329
+ function writeVarStringBytes(bytes: Uint8Array, out: number[]): void {
330
+ writeUnsignedLeb128(bytes.length, out);
331
+ for (let i = 0; i < bytes.length; i += 1) {
332
+ out.push(bytes[i]);
333
+ }
334
+ }
335
+
336
+ const VERSION_VECTOR_MAGIC = new Uint8Array([86, 69, 86, 69]); // "VEVE"
337
+
338
+ function encodeVersionVectorBinary(vv?: VersionVector): Uint8Array {
339
+ const entries = collectEncodableVersionVectorEntries(vv);
340
+ const buffer: number[] = Array.from(VERSION_VECTOR_MAGIC);
341
+ if (entries.length === 0) {
342
+ return Uint8Array.from(buffer);
343
+ }
344
+
345
+ let lastTimestamp = 0;
346
+ for (let i = 0; i < entries.length; i += 1) {
347
+ const entry = entries[i];
348
+ if (entry.timestamp < 0) {
349
+ throw new TypeError("timestamp must be non-negative");
350
+ }
351
+ if (i === 0) {
352
+ writeUnsignedLeb128(entry.timestamp, buffer);
353
+ lastTimestamp = entry.timestamp;
354
+ } else {
355
+ const delta = entry.timestamp - lastTimestamp;
356
+ if (delta < 0) {
357
+ throw new TypeError("version vector timestamps must be non-decreasing");
358
+ }
359
+ writeUnsignedLeb128(delta, buffer);
360
+ lastTimestamp = entry.timestamp;
361
+ }
362
+
363
+ writeUnsignedLeb128(entry.counter, buffer);
364
+ writeVarStringBytes(entry.peerBytes, buffer);
365
+ }
366
+
367
+ return Uint8Array.from(buffer);
368
+ }
369
+
370
+ function decodeUnsignedLeb128(
371
+ bytes: Uint8Array,
372
+ offset: number,
373
+ ): [number, number] {
374
+ let result = 0;
375
+ let shift = 0;
376
+ let consumed = 0;
377
+ while (offset + consumed < bytes.length) {
378
+ const byte = bytes[offset + consumed];
379
+ consumed += 1;
380
+ result |= (byte & 0x7f) << shift;
381
+ if ((byte & 0x80) === 0) {
382
+ break;
383
+ }
384
+ shift += 7;
385
+ }
386
+ return [result, consumed];
387
+ }
388
+
389
+ function decodeVarString(bytes: Uint8Array, offset: number): [string, number] {
390
+ const [length, used] = decodeUnsignedLeb128(bytes, offset);
391
+ const start = offset + used;
392
+ const end = start + length;
393
+ if (end > bytes.length) {
394
+ throw new TypeError("varString length exceeds buffer");
395
+ }
396
+ const slice = bytes.subarray(start, end);
397
+ return [textDecoder.decode(slice), used + length];
398
+ }
399
+
400
+ function hasMagic(bytes: Uint8Array): boolean {
401
+ return (
402
+ bytes.length >= 4 &&
403
+ bytes[0] === VERSION_VECTOR_MAGIC[0] &&
404
+ bytes[1] === VERSION_VECTOR_MAGIC[1] &&
405
+ bytes[2] === VERSION_VECTOR_MAGIC[2] &&
406
+ bytes[3] === VERSION_VECTOR_MAGIC[3]
407
+ );
408
+ }
409
+
410
+ function decodeLegacyVersionVector(bytes: Uint8Array): VersionVector {
411
+ let offset = 0;
412
+ const [count, usedCount] = decodeUnsignedLeb128(bytes, offset);
413
+ offset += usedCount;
414
+ const [baseTimestamp, usedBase] = decodeUnsignedLeb128(bytes, offset);
415
+ offset += usedBase;
416
+ const vv: VersionVector = {};
417
+ for (let i = 0; i < count; i += 1) {
418
+ const [peer, usedPeer] = decodeVarString(bytes, offset);
419
+ offset += usedPeer;
420
+ if (!isValidPeerId(peer)) {
421
+ throw new TypeError("invalid peer id in encoded version vector");
233
422
  }
234
- raw[peer] = [physicalTime, Math.trunc(logicalCounter)];
423
+ const [delta, usedDelta] = decodeUnsignedLeb128(bytes, offset);
424
+ offset += usedDelta;
425
+ const [counter, usedCounter] = decodeUnsignedLeb128(bytes, offset);
426
+ offset += usedCounter;
427
+ vv[peer] = {
428
+ physicalTime: baseTimestamp + delta,
429
+ logicalCounter: counter,
430
+ };
431
+ }
432
+ return vv;
433
+ }
434
+
435
+ function decodeNewVersionVector(bytes: Uint8Array): VersionVector {
436
+ let offset = 4;
437
+ const vv: VersionVector = {};
438
+ if (offset === bytes.length) {
439
+ return vv;
440
+ }
441
+
442
+ const [firstTimestamp, usedTs] = decodeUnsignedLeb128(bytes, offset);
443
+ offset += usedTs;
444
+ const [firstCounter, usedCounter] = decodeUnsignedLeb128(bytes, offset);
445
+ offset += usedCounter;
446
+ const [firstPeer, usedPeer] = decodeVarString(bytes, offset);
447
+ offset += usedPeer;
448
+ if (!isValidPeerId(firstPeer)) {
449
+ throw new TypeError("invalid peer id in encoded version vector");
450
+ }
451
+ vv[firstPeer] = {
452
+ physicalTime: firstTimestamp,
453
+ logicalCounter: firstCounter,
454
+ };
455
+
456
+ let lastTimestamp = firstTimestamp;
457
+ while (offset < bytes.length) {
458
+ const [delta, usedDelta] = decodeUnsignedLeb128(bytes, offset);
459
+ offset += usedDelta;
460
+ const [counter, usedCtr] = decodeUnsignedLeb128(bytes, offset);
461
+ offset += usedCtr;
462
+ const [peer, usedPeerLen] = decodeVarString(bytes, offset);
463
+ offset += usedPeerLen;
464
+ if (!isValidPeerId(peer)) {
465
+ throw new TypeError("invalid peer id in encoded version vector");
466
+ }
467
+ const timestamp = lastTimestamp + delta;
468
+ if (timestamp < lastTimestamp) {
469
+ throw new TypeError("version vector timestamps must be non-decreasing");
470
+ }
471
+ vv[peer] = { physicalTime: timestamp, logicalCounter: counter };
472
+ lastTimestamp = timestamp;
473
+ }
474
+
475
+ return vv;
476
+ }
477
+
478
+ function decodeVersionVectorBinary(bytes: Uint8Array): VersionVector {
479
+ if (hasMagic(bytes)) {
480
+ return decodeNewVersionVector(bytes);
481
+ }
482
+ return decodeLegacyVersionVector(bytes);
483
+ }
484
+
485
+ function encodeVersionVectorForFfi(
486
+ vv?: VersionVector,
487
+ ): RawVersionVector | undefined {
488
+ if (!vv) {
489
+ return undefined;
490
+ }
491
+ const raw: RawVersionVector = {};
492
+ for (const entry of collectEncodableVersionVectorEntries(vv)) {
493
+ raw[entry.peer] = [entry.timestamp, entry.counter];
235
494
  }
236
495
  return raw;
237
496
  }
238
497
 
239
- function normalizePruneBefore(pruneTombstonesBefore?: number): number | undefined {
498
+ function normalizePruneBefore(
499
+ pruneTombstonesBefore?: number,
500
+ ): number | undefined {
240
501
  if (pruneTombstonesBefore === undefined) {
241
502
  return undefined;
242
503
  }
243
- if (typeof pruneTombstonesBefore !== "number" || !Number.isFinite(pruneTombstonesBefore)) {
504
+ if (
505
+ typeof pruneTombstonesBefore !== "number" ||
506
+ !Number.isFinite(pruneTombstonesBefore)
507
+ ) {
244
508
  return undefined;
245
509
  }
246
510
  return pruneTombstonesBefore;
247
511
  }
248
512
 
249
- function decodeVersionVector(raw: unknown): VersionVector {
513
+ function decodeVersionVectorFromRaw(raw: unknown): VersionVector {
250
514
  if (raw === null || typeof raw !== "object") {
251
515
  return {};
252
516
  }
@@ -262,7 +526,10 @@ function decodeVersionVector(raw: unknown): VersionVector {
262
526
  if (typeof physicalTime !== "number" || !Number.isFinite(physicalTime)) {
263
527
  continue;
264
528
  }
265
- if (typeof logicalCounter !== "number" || !Number.isFinite(logicalCounter)) {
529
+ if (
530
+ typeof logicalCounter !== "number" ||
531
+ !Number.isFinite(logicalCounter)
532
+ ) {
266
533
  continue;
267
534
  }
268
535
  result[peer] = {
@@ -283,6 +550,23 @@ function encodeBound(bound?: ScanBound): Record<string, unknown> | undefined {
283
550
  return { kind: bound.kind, key: bound.key.slice() };
284
551
  }
285
552
 
553
+ function decodeEntryInfo(raw: unknown): EntryInfo | undefined {
554
+ if (!raw || typeof raw !== "object") {
555
+ return undefined;
556
+ }
557
+ const info = raw as RawEntryInfo;
558
+ const clock = normalizeEntryClock(info.clock);
559
+ if (!clock) {
560
+ return undefined;
561
+ }
562
+ const metadata = normalizeMetadataMap(info.metadata);
563
+ const result: EntryInfo = { metadata, clock };
564
+ if ("data" in info) {
565
+ result.data = cloneJson(info.data as Value);
566
+ }
567
+ return result;
568
+ }
569
+
286
570
  function decodeEventBatch(raw: unknown): EventBatch {
287
571
  if (!raw || typeof raw !== "object") {
288
572
  return { source: "local", events: [] };
@@ -337,8 +621,9 @@ function normalizeRawEventPayload(
337
621
  return result;
338
622
  }
339
623
 
340
- const structuredCloneFn: (<T>(value: T) => T) | undefined =
341
- (globalThis as typeof globalThis & { structuredClone?: <T>(value: T) => T }).structuredClone;
624
+ const structuredCloneFn: (<T>(value: T) => T) | undefined = (
625
+ globalThis as typeof globalThis & { structuredClone?: <T>(value: T) => T }
626
+ ).structuredClone;
342
627
 
343
628
  function cloneJson<T>(value: T): T {
344
629
  if (value === undefined) {
@@ -366,10 +651,16 @@ function cloneMetadata(metadata: unknown): MetadataMap | undefined {
366
651
  return cloneJson(metadata as MetadataMap);
367
652
  }
368
653
 
654
+ function normalizeMetadataMap(metadata: unknown): MetadataMap {
655
+ const cloned = cloneMetadata(metadata);
656
+ return cloned ?? {};
657
+ }
658
+
369
659
  function decodeClock(record: ExportRecord): EntryClock {
370
660
  const rawClock = typeof record.c === "string" ? record.c : "";
371
661
  const firstComma = rawClock.indexOf(",");
372
- const secondComma = firstComma === -1 ? -1 : rawClock.indexOf(",", firstComma + 1);
662
+ const secondComma =
663
+ firstComma === -1 ? -1 : rawClock.indexOf(",", firstComma + 1);
373
664
  if (firstComma === -1 || secondComma === -1) {
374
665
  return { physicalTime: 0, logicalCounter: 0, peerId: "" };
375
666
  }
@@ -386,6 +677,29 @@ function decodeClock(record: ExportRecord): EntryClock {
386
677
  };
387
678
  }
388
679
 
680
+ function normalizeEntryClock(
681
+ clock: RawEntryClock | undefined,
682
+ ): EntryClock | undefined {
683
+ if (!clock || typeof clock !== "object") {
684
+ return undefined;
685
+ }
686
+ const { physicalTime, logicalCounter, peerId } = clock;
687
+ if (typeof physicalTime !== "number" || !Number.isFinite(physicalTime)) {
688
+ return undefined;
689
+ }
690
+ if (typeof logicalCounter !== "number" || !Number.isFinite(logicalCounter)) {
691
+ return undefined;
692
+ }
693
+ if (!isValidPeerId(peerId)) {
694
+ return undefined;
695
+ }
696
+ return {
697
+ physicalTime,
698
+ logicalCounter: Math.trunc(logicalCounter),
699
+ peerId,
700
+ };
701
+ }
702
+
389
703
  function createExportPayload(record: ExportRecord): ExportPayload {
390
704
  const payload: ExportPayload = {};
391
705
  if (record.d !== undefined) {
@@ -398,10 +712,7 @@ function createExportPayload(record: ExportRecord): ExportPayload {
398
712
  return payload;
399
713
  }
400
714
 
401
- function createPutPayload(
402
- value: Value,
403
- metadata?: MetadataMap,
404
- ): ExportPayload {
715
+ function createPutPayload(value: Value, metadata?: MetadataMap): ExportPayload {
405
716
  const payload: ExportPayload = { data: cloneJson(value) };
406
717
  const cleanMetadata = cloneMetadata(metadata);
407
718
  if (cleanMetadata !== undefined) {
@@ -510,10 +821,8 @@ function isExportOptions(value: unknown): value is ExportOptions {
510
821
  value !== null &&
511
822
  (Object.prototype.hasOwnProperty.call(value, "hooks") ||
512
823
  Object.prototype.hasOwnProperty.call(value, "from") ||
513
- Object.prototype.hasOwnProperty.call(
514
- value,
515
- "pruneTombstonesBefore",
516
- ))
824
+ Object.prototype.hasOwnProperty.call(value, "pruneTombstonesBefore") ||
825
+ Object.prototype.hasOwnProperty.call(value, "peerId"))
517
826
  );
518
827
  }
519
828
 
@@ -562,7 +871,13 @@ export class Flock {
562
871
  now?: number,
563
872
  ): void {
564
873
  const metadataClone = cloneMetadata(metadata);
565
- put_with_meta_ffi(this.inner, key, JSON.stringify(value), metadataClone, now);
874
+ put_with_meta_ffi(
875
+ this.inner,
876
+ key,
877
+ JSON.stringify(value),
878
+ metadataClone,
879
+ now,
880
+ );
566
881
  }
567
882
 
568
883
  private async putWithMetaWithHooks(
@@ -599,15 +914,19 @@ export class Flock {
599
914
 
600
915
  /**
601
916
  * Put a value into the flock. If the given entry already exists, this insert will be skipped.
602
- * @param key
603
- * @param value
604
- * @param now
917
+ * @param key
918
+ * @param value
919
+ * @param now
605
920
  */
606
921
  put(key: KeyPart[], value: Value, now?: number): void {
607
922
  put_json_ffi(this.inner, key, JSON.stringify(value), now);
608
923
  }
609
924
 
610
- putWithMeta(key: KeyPart[], value: Value, options?: PutWithMetaOptions): void | Promise<void> {
925
+ putWithMeta(
926
+ key: KeyPart[],
927
+ value: Value,
928
+ options?: PutWithMetaOptions,
929
+ ): void | Promise<void> {
611
930
  const opts = options ?? {};
612
931
  if (opts.hooks?.transform) {
613
932
  return this.putWithMetaWithHooks(key, value, opts);
@@ -621,8 +940,8 @@ export class Flock {
621
940
 
622
941
  /**
623
942
  * Delete a value from the flock. If the given entry does not exist, this delete will be skipped.
624
- * @param key
625
- * @param now
943
+ * @param key
944
+ * @param now
626
945
  */
627
946
  delete(key: KeyPart[], now?: number): void {
628
947
  delete_ffi(this.inner, key, now);
@@ -632,30 +951,49 @@ export class Flock {
632
951
  return get_ffi(this.inner, key) as Value | undefined;
633
952
  }
634
953
 
954
+ /**
955
+ * Returns the full entry payload (data, metadata, and clock) for a key.
956
+ *
957
+ * Unlike `get`, this distinguishes between a missing key (`undefined`) and a
958
+ * tombstone (returns the clock and metadata with `data` omitted). Metadata is
959
+ * cloned and defaults to `{}` when absent.
960
+ */
961
+ getEntry(key: KeyPart[]): EntryInfo | undefined {
962
+ const raw = get_entry_ffi(this.inner, key) as RawEntryInfo | undefined;
963
+ return decodeEntryInfo(raw);
964
+ }
965
+
635
966
  merge(other: Flock): void {
636
967
  merge(this.inner, other.inner);
637
968
  }
638
969
 
639
970
  version(): VersionVector {
640
- return decodeVersionVector(version_ffi(this.inner));
971
+ return decodeVersionVectorFromRaw(version_ffi(this.inner));
641
972
  }
642
973
 
643
974
  private exportJsonInternal(
644
975
  from?: VersionVector,
645
976
  pruneTombstonesBefore?: number,
977
+ peerId?: string,
646
978
  ): ExportBundle {
647
979
  const pruneBefore = normalizePruneBefore(pruneTombstonesBefore);
980
+ const normalizedPeerId =
981
+ peerId !== undefined && isValidPeerId(peerId) ? peerId : undefined;
648
982
  return export_json_ffi(
649
983
  this.inner,
650
- encodeVersionVector(from),
984
+ encodeVersionVectorForFfi(from),
651
985
  pruneBefore,
986
+ normalizedPeerId,
652
987
  ) as ExportBundle;
653
988
  }
654
989
 
655
- private async exportJsonWithHooks(options: ExportOptions): Promise<ExportBundle> {
990
+ private async exportJsonWithHooks(
991
+ options: ExportOptions,
992
+ ): Promise<ExportBundle> {
656
993
  const base = this.exportJsonInternal(
657
994
  options.from,
658
995
  options.pruneTombstonesBefore,
996
+ options.peerId,
659
997
  );
660
998
  const transform = options.hooks?.transform;
661
999
  if (!transform) {
@@ -678,10 +1016,7 @@ export class Flock {
678
1016
 
679
1017
  exportJson(): ExportBundle;
680
1018
  exportJson(from: VersionVector): ExportBundle;
681
- exportJson(
682
- from: VersionVector,
683
- pruneTombstonesBefore: number,
684
- ): ExportBundle;
1019
+ exportJson(from: VersionVector, pruneTombstonesBefore: number): ExportBundle;
685
1020
  exportJson(options: ExportOptions): Promise<ExportBundle>;
686
1021
  exportJson(
687
1022
  arg?: VersionVector | ExportOptions,
@@ -697,11 +1032,15 @@ export class Flock {
697
1032
  }
698
1033
 
699
1034
  private importJsonInternal(bundle: ExportBundle): ImportReport {
700
- const report = import_json_ffi(this.inner, bundle) as RawImportReport | undefined;
1035
+ const report = import_json_ffi(this.inner, bundle) as
1036
+ | RawImportReport
1037
+ | undefined;
701
1038
  return decodeImportReport(report);
702
1039
  }
703
1040
 
704
- private async importJsonWithHooks(options: ImportOptions): Promise<ImportReport> {
1041
+ private async importJsonWithHooks(
1042
+ options: ImportOptions,
1043
+ ): Promise<ImportReport> {
705
1044
  const preprocess = options.hooks?.preprocess;
706
1045
  const working = preprocess ? cloneBundle(options.bundle) : options.bundle;
707
1046
  const skippedByHooks: Array<{ key: KeyPart[]; reason: string }> = [];
@@ -741,6 +1080,13 @@ export class Flock {
741
1080
  return this.importJsonInternal(arg);
742
1081
  }
743
1082
 
1083
+ importJsonStr(bundle: string): ImportReport {
1084
+ const report = import_json_str_ffi(this.inner, bundle) as
1085
+ | RawImportReport
1086
+ | undefined;
1087
+ return decodeImportReport(report);
1088
+ }
1089
+
744
1090
  getMaxPhysicalTime(): number {
745
1091
  return Number(get_max_physical_time_ffi(this.inner));
746
1092
  }
@@ -781,7 +1127,9 @@ export class Flock {
781
1127
  const start = encodeBound(options.start);
782
1128
  const end = encodeBound(options.end);
783
1129
  const prefix = options.prefix ? options.prefix.slice() : undefined;
784
- const rows = scan_ffi(this.inner, start, end, prefix) as RawScanRow[] | undefined;
1130
+ const rows = scan_ffi(this.inner, start, end, prefix) as
1131
+ | RawScanRow[]
1132
+ | undefined;
785
1133
  if (!Array.isArray(rows)) {
786
1134
  return [];
787
1135
  }