@loro-dev/flock 3.1.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/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_moon_flock.d.ts +6 -0
- package/src/_moon_flock.ts +8180 -7808
- package/src/index.ts +391 -49
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
|
|
|
@@ -57,7 +69,17 @@ export type VersionVectorEntry = {
|
|
|
57
69
|
logicalCounter: number;
|
|
58
70
|
};
|
|
59
71
|
|
|
60
|
-
export
|
|
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
|
+
}
|
|
61
83
|
|
|
62
84
|
export type Value =
|
|
63
85
|
| string
|
|
@@ -77,7 +99,10 @@ export type ExportRecord = {
|
|
|
77
99
|
m?: MetadataMap;
|
|
78
100
|
};
|
|
79
101
|
|
|
80
|
-
export type ExportBundle = {
|
|
102
|
+
export type ExportBundle = {
|
|
103
|
+
version: number;
|
|
104
|
+
entries: Record<string, ExportRecord>;
|
|
105
|
+
};
|
|
81
106
|
|
|
82
107
|
export type EntryClock = {
|
|
83
108
|
physicalTime: number;
|
|
@@ -85,6 +110,12 @@ export type EntryClock = {
|
|
|
85
110
|
peerId: string;
|
|
86
111
|
};
|
|
87
112
|
|
|
113
|
+
export type EntryInfo = {
|
|
114
|
+
data?: Value;
|
|
115
|
+
metadata: MetadataMap;
|
|
116
|
+
clock: EntryClock;
|
|
117
|
+
};
|
|
118
|
+
|
|
88
119
|
export type ExportPayload = {
|
|
89
120
|
data?: Value;
|
|
90
121
|
metadata?: MetadataMap;
|
|
@@ -182,6 +213,7 @@ export type EventBatch = {
|
|
|
182
213
|
};
|
|
183
214
|
|
|
184
215
|
const textEncoder = new TextEncoder();
|
|
216
|
+
const textDecoder = new TextDecoder();
|
|
185
217
|
|
|
186
218
|
function utf8ByteLength(value: string): number {
|
|
187
219
|
return textEncoder.encode(value).length;
|
|
@@ -193,7 +225,10 @@ function isValidPeerId(peerId: unknown): peerId is string {
|
|
|
193
225
|
|
|
194
226
|
function createRandomPeerId(): string {
|
|
195
227
|
const id = new Uint8Array(32);
|
|
196
|
-
if (
|
|
228
|
+
if (
|
|
229
|
+
typeof crypto !== "undefined" &&
|
|
230
|
+
typeof crypto.getRandomValues === "function"
|
|
231
|
+
) {
|
|
197
232
|
crypto.getRandomValues(id);
|
|
198
233
|
} else {
|
|
199
234
|
for (let i = 0; i < 32; i += 1) {
|
|
@@ -208,46 +243,274 @@ function normalizePeerId(peerId?: string): string {
|
|
|
208
243
|
return createRandomPeerId();
|
|
209
244
|
}
|
|
210
245
|
if (!isValidPeerId(peerId)) {
|
|
211
|
-
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.");
|
|
212
247
|
}
|
|
213
248
|
return peerId;
|
|
214
249
|
}
|
|
215
250
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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;
|
|
219
261
|
}
|
|
220
|
-
const
|
|
221
|
-
for (
|
|
222
|
-
|
|
223
|
-
|
|
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;
|
|
224
267
|
}
|
|
225
|
-
|
|
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)) {
|
|
226
281
|
continue;
|
|
227
282
|
}
|
|
228
283
|
const { physicalTime, logicalCounter } = entry;
|
|
229
|
-
if (
|
|
284
|
+
if (
|
|
285
|
+
typeof physicalTime !== "number" ||
|
|
286
|
+
!Number.isFinite(physicalTime) ||
|
|
287
|
+
typeof logicalCounter !== "number" ||
|
|
288
|
+
!Number.isFinite(logicalCounter)
|
|
289
|
+
) {
|
|
230
290
|
continue;
|
|
231
291
|
}
|
|
232
|
-
|
|
233
|
-
|
|
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;
|
|
234
307
|
}
|
|
235
|
-
|
|
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");
|
|
422
|
+
}
|
|
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];
|
|
236
494
|
}
|
|
237
495
|
return raw;
|
|
238
496
|
}
|
|
239
497
|
|
|
240
|
-
function normalizePruneBefore(
|
|
498
|
+
function normalizePruneBefore(
|
|
499
|
+
pruneTombstonesBefore?: number,
|
|
500
|
+
): number | undefined {
|
|
241
501
|
if (pruneTombstonesBefore === undefined) {
|
|
242
502
|
return undefined;
|
|
243
503
|
}
|
|
244
|
-
if (
|
|
504
|
+
if (
|
|
505
|
+
typeof pruneTombstonesBefore !== "number" ||
|
|
506
|
+
!Number.isFinite(pruneTombstonesBefore)
|
|
507
|
+
) {
|
|
245
508
|
return undefined;
|
|
246
509
|
}
|
|
247
510
|
return pruneTombstonesBefore;
|
|
248
511
|
}
|
|
249
512
|
|
|
250
|
-
function
|
|
513
|
+
function decodeVersionVectorFromRaw(raw: unknown): VersionVector {
|
|
251
514
|
if (raw === null || typeof raw !== "object") {
|
|
252
515
|
return {};
|
|
253
516
|
}
|
|
@@ -263,7 +526,10 @@ function decodeVersionVector(raw: unknown): VersionVector {
|
|
|
263
526
|
if (typeof physicalTime !== "number" || !Number.isFinite(physicalTime)) {
|
|
264
527
|
continue;
|
|
265
528
|
}
|
|
266
|
-
if (
|
|
529
|
+
if (
|
|
530
|
+
typeof logicalCounter !== "number" ||
|
|
531
|
+
!Number.isFinite(logicalCounter)
|
|
532
|
+
) {
|
|
267
533
|
continue;
|
|
268
534
|
}
|
|
269
535
|
result[peer] = {
|
|
@@ -284,6 +550,23 @@ function encodeBound(bound?: ScanBound): Record<string, unknown> | undefined {
|
|
|
284
550
|
return { kind: bound.kind, key: bound.key.slice() };
|
|
285
551
|
}
|
|
286
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
|
+
|
|
287
570
|
function decodeEventBatch(raw: unknown): EventBatch {
|
|
288
571
|
if (!raw || typeof raw !== "object") {
|
|
289
572
|
return { source: "local", events: [] };
|
|
@@ -338,8 +621,9 @@ function normalizeRawEventPayload(
|
|
|
338
621
|
return result;
|
|
339
622
|
}
|
|
340
623
|
|
|
341
|
-
const structuredCloneFn: (<T>(value: T) => T) | undefined =
|
|
342
|
-
|
|
624
|
+
const structuredCloneFn: (<T>(value: T) => T) | undefined = (
|
|
625
|
+
globalThis as typeof globalThis & { structuredClone?: <T>(value: T) => T }
|
|
626
|
+
).structuredClone;
|
|
343
627
|
|
|
344
628
|
function cloneJson<T>(value: T): T {
|
|
345
629
|
if (value === undefined) {
|
|
@@ -367,10 +651,16 @@ function cloneMetadata(metadata: unknown): MetadataMap | undefined {
|
|
|
367
651
|
return cloneJson(metadata as MetadataMap);
|
|
368
652
|
}
|
|
369
653
|
|
|
654
|
+
function normalizeMetadataMap(metadata: unknown): MetadataMap {
|
|
655
|
+
const cloned = cloneMetadata(metadata);
|
|
656
|
+
return cloned ?? {};
|
|
657
|
+
}
|
|
658
|
+
|
|
370
659
|
function decodeClock(record: ExportRecord): EntryClock {
|
|
371
660
|
const rawClock = typeof record.c === "string" ? record.c : "";
|
|
372
661
|
const firstComma = rawClock.indexOf(",");
|
|
373
|
-
const secondComma =
|
|
662
|
+
const secondComma =
|
|
663
|
+
firstComma === -1 ? -1 : rawClock.indexOf(",", firstComma + 1);
|
|
374
664
|
if (firstComma === -1 || secondComma === -1) {
|
|
375
665
|
return { physicalTime: 0, logicalCounter: 0, peerId: "" };
|
|
376
666
|
}
|
|
@@ -387,6 +677,29 @@ function decodeClock(record: ExportRecord): EntryClock {
|
|
|
387
677
|
};
|
|
388
678
|
}
|
|
389
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
|
+
|
|
390
703
|
function createExportPayload(record: ExportRecord): ExportPayload {
|
|
391
704
|
const payload: ExportPayload = {};
|
|
392
705
|
if (record.d !== undefined) {
|
|
@@ -399,10 +712,7 @@ function createExportPayload(record: ExportRecord): ExportPayload {
|
|
|
399
712
|
return payload;
|
|
400
713
|
}
|
|
401
714
|
|
|
402
|
-
function createPutPayload(
|
|
403
|
-
value: Value,
|
|
404
|
-
metadata?: MetadataMap,
|
|
405
|
-
): ExportPayload {
|
|
715
|
+
function createPutPayload(value: Value, metadata?: MetadataMap): ExportPayload {
|
|
406
716
|
const payload: ExportPayload = { data: cloneJson(value) };
|
|
407
717
|
const cleanMetadata = cloneMetadata(metadata);
|
|
408
718
|
if (cleanMetadata !== undefined) {
|
|
@@ -511,10 +821,7 @@ function isExportOptions(value: unknown): value is ExportOptions {
|
|
|
511
821
|
value !== null &&
|
|
512
822
|
(Object.prototype.hasOwnProperty.call(value, "hooks") ||
|
|
513
823
|
Object.prototype.hasOwnProperty.call(value, "from") ||
|
|
514
|
-
Object.prototype.hasOwnProperty.call(
|
|
515
|
-
value,
|
|
516
|
-
"pruneTombstonesBefore",
|
|
517
|
-
) ||
|
|
824
|
+
Object.prototype.hasOwnProperty.call(value, "pruneTombstonesBefore") ||
|
|
518
825
|
Object.prototype.hasOwnProperty.call(value, "peerId"))
|
|
519
826
|
);
|
|
520
827
|
}
|
|
@@ -564,7 +871,13 @@ export class Flock {
|
|
|
564
871
|
now?: number,
|
|
565
872
|
): void {
|
|
566
873
|
const metadataClone = cloneMetadata(metadata);
|
|
567
|
-
put_with_meta_ffi(
|
|
874
|
+
put_with_meta_ffi(
|
|
875
|
+
this.inner,
|
|
876
|
+
key,
|
|
877
|
+
JSON.stringify(value),
|
|
878
|
+
metadataClone,
|
|
879
|
+
now,
|
|
880
|
+
);
|
|
568
881
|
}
|
|
569
882
|
|
|
570
883
|
private async putWithMetaWithHooks(
|
|
@@ -601,15 +914,19 @@ export class Flock {
|
|
|
601
914
|
|
|
602
915
|
/**
|
|
603
916
|
* Put a value into the flock. If the given entry already exists, this insert will be skipped.
|
|
604
|
-
* @param key
|
|
605
|
-
* @param value
|
|
606
|
-
* @param now
|
|
917
|
+
* @param key
|
|
918
|
+
* @param value
|
|
919
|
+
* @param now
|
|
607
920
|
*/
|
|
608
921
|
put(key: KeyPart[], value: Value, now?: number): void {
|
|
609
922
|
put_json_ffi(this.inner, key, JSON.stringify(value), now);
|
|
610
923
|
}
|
|
611
924
|
|
|
612
|
-
putWithMeta(
|
|
925
|
+
putWithMeta(
|
|
926
|
+
key: KeyPart[],
|
|
927
|
+
value: Value,
|
|
928
|
+
options?: PutWithMetaOptions,
|
|
929
|
+
): void | Promise<void> {
|
|
613
930
|
const opts = options ?? {};
|
|
614
931
|
if (opts.hooks?.transform) {
|
|
615
932
|
return this.putWithMetaWithHooks(key, value, opts);
|
|
@@ -623,8 +940,8 @@ export class Flock {
|
|
|
623
940
|
|
|
624
941
|
/**
|
|
625
942
|
* Delete a value from the flock. If the given entry does not exist, this delete will be skipped.
|
|
626
|
-
* @param key
|
|
627
|
-
* @param now
|
|
943
|
+
* @param key
|
|
944
|
+
* @param now
|
|
628
945
|
*/
|
|
629
946
|
delete(key: KeyPart[], now?: number): void {
|
|
630
947
|
delete_ffi(this.inner, key, now);
|
|
@@ -634,12 +951,24 @@ export class Flock {
|
|
|
634
951
|
return get_ffi(this.inner, key) as Value | undefined;
|
|
635
952
|
}
|
|
636
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
|
+
|
|
637
966
|
merge(other: Flock): void {
|
|
638
967
|
merge(this.inner, other.inner);
|
|
639
968
|
}
|
|
640
969
|
|
|
641
970
|
version(): VersionVector {
|
|
642
|
-
return
|
|
971
|
+
return decodeVersionVectorFromRaw(version_ffi(this.inner));
|
|
643
972
|
}
|
|
644
973
|
|
|
645
974
|
private exportJsonInternal(
|
|
@@ -648,16 +977,19 @@ export class Flock {
|
|
|
648
977
|
peerId?: string,
|
|
649
978
|
): ExportBundle {
|
|
650
979
|
const pruneBefore = normalizePruneBefore(pruneTombstonesBefore);
|
|
651
|
-
const normalizedPeerId =
|
|
980
|
+
const normalizedPeerId =
|
|
981
|
+
peerId !== undefined && isValidPeerId(peerId) ? peerId : undefined;
|
|
652
982
|
return export_json_ffi(
|
|
653
983
|
this.inner,
|
|
654
|
-
|
|
984
|
+
encodeVersionVectorForFfi(from),
|
|
655
985
|
pruneBefore,
|
|
656
986
|
normalizedPeerId,
|
|
657
987
|
) as ExportBundle;
|
|
658
988
|
}
|
|
659
989
|
|
|
660
|
-
private async exportJsonWithHooks(
|
|
990
|
+
private async exportJsonWithHooks(
|
|
991
|
+
options: ExportOptions,
|
|
992
|
+
): Promise<ExportBundle> {
|
|
661
993
|
const base = this.exportJsonInternal(
|
|
662
994
|
options.from,
|
|
663
995
|
options.pruneTombstonesBefore,
|
|
@@ -684,10 +1016,7 @@ export class Flock {
|
|
|
684
1016
|
|
|
685
1017
|
exportJson(): ExportBundle;
|
|
686
1018
|
exportJson(from: VersionVector): ExportBundle;
|
|
687
|
-
exportJson(
|
|
688
|
-
from: VersionVector,
|
|
689
|
-
pruneTombstonesBefore: number,
|
|
690
|
-
): ExportBundle;
|
|
1019
|
+
exportJson(from: VersionVector, pruneTombstonesBefore: number): ExportBundle;
|
|
691
1020
|
exportJson(options: ExportOptions): Promise<ExportBundle>;
|
|
692
1021
|
exportJson(
|
|
693
1022
|
arg?: VersionVector | ExportOptions,
|
|
@@ -703,11 +1032,15 @@ export class Flock {
|
|
|
703
1032
|
}
|
|
704
1033
|
|
|
705
1034
|
private importJsonInternal(bundle: ExportBundle): ImportReport {
|
|
706
|
-
const report = import_json_ffi(this.inner, bundle) as
|
|
1035
|
+
const report = import_json_ffi(this.inner, bundle) as
|
|
1036
|
+
| RawImportReport
|
|
1037
|
+
| undefined;
|
|
707
1038
|
return decodeImportReport(report);
|
|
708
1039
|
}
|
|
709
1040
|
|
|
710
|
-
private async importJsonWithHooks(
|
|
1041
|
+
private async importJsonWithHooks(
|
|
1042
|
+
options: ImportOptions,
|
|
1043
|
+
): Promise<ImportReport> {
|
|
711
1044
|
const preprocess = options.hooks?.preprocess;
|
|
712
1045
|
const working = preprocess ? cloneBundle(options.bundle) : options.bundle;
|
|
713
1046
|
const skippedByHooks: Array<{ key: KeyPart[]; reason: string }> = [];
|
|
@@ -747,6 +1080,13 @@ export class Flock {
|
|
|
747
1080
|
return this.importJsonInternal(arg);
|
|
748
1081
|
}
|
|
749
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
|
+
|
|
750
1090
|
getMaxPhysicalTime(): number {
|
|
751
1091
|
return Number(get_max_physical_time_ffi(this.inner));
|
|
752
1092
|
}
|
|
@@ -787,7 +1127,9 @@ export class Flock {
|
|
|
787
1127
|
const start = encodeBound(options.start);
|
|
788
1128
|
const end = encodeBound(options.end);
|
|
789
1129
|
const prefix = options.prefix ? options.prefix.slice() : undefined;
|
|
790
|
-
const rows = scan_ffi(this.inner, start, end, prefix) as
|
|
1130
|
+
const rows = scan_ffi(this.inner, start, end, prefix) as
|
|
1131
|
+
| RawScanRow[]
|
|
1132
|
+
| undefined;
|
|
791
1133
|
if (!Array.isArray(rows)) {
|
|
792
1134
|
return [];
|
|
793
1135
|
}
|