@peerbit/shared-log 16.0.17 → 16.0.18
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/src/index.d.ts +15 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +482 -50
- package/dist/src/index.js.map +1 -1
- package/package.json +12 -12
- package/src/index.ts +616 -61
package/dist/src/index.js
CHANGED
|
@@ -2476,29 +2476,319 @@ let SharedLog = (() => {
|
|
|
2476
2476
|
}
|
|
2477
2477
|
return true;
|
|
2478
2478
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2479
|
+
snapshotDeliveryOptions(deliveryArgument, reliability) {
|
|
2480
|
+
// Keep the selected AbortSignal object live: aborting it remains effective,
|
|
2481
|
+
// while reassigning any caller field cannot change this invocation.
|
|
2482
|
+
return Object.freeze({
|
|
2483
|
+
reliability,
|
|
2484
|
+
minAcks: deliveryArgument.minAcks,
|
|
2485
|
+
requireRecipients: deliveryArgument.requireRecipients,
|
|
2486
|
+
priority: deliveryArgument.priority,
|
|
2487
|
+
timeout: deliveryArgument.timeout,
|
|
2488
|
+
signal: deliveryArgument.signal,
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
snapshotAppendTrim(trim) {
|
|
2492
|
+
const type = trim.type;
|
|
2493
|
+
const filterArgument = trim.filter;
|
|
2494
|
+
const filter = filterArgument
|
|
2495
|
+
? Object.freeze({
|
|
2496
|
+
canTrim: filterArgument.canTrim,
|
|
2497
|
+
cacheId: filterArgument.cacheId,
|
|
2498
|
+
})
|
|
2499
|
+
: undefined;
|
|
2500
|
+
if (type === "time") {
|
|
2501
|
+
return Object.freeze({ type, maxAge: trim.maxAge, filter });
|
|
2502
|
+
}
|
|
2503
|
+
if (type === "length" || type === "bytelength") {
|
|
2504
|
+
return Object.freeze({
|
|
2505
|
+
type,
|
|
2506
|
+
to: trim.to,
|
|
2507
|
+
from: trim.from,
|
|
2508
|
+
filter,
|
|
2509
|
+
});
|
|
2483
2510
|
}
|
|
2484
|
-
|
|
2485
|
-
|
|
2511
|
+
throw new Error("Unsupported append trim type");
|
|
2512
|
+
}
|
|
2513
|
+
snapshotAppendEncryption(encryption) {
|
|
2514
|
+
return asTrustedLowerLog(this.log).snapshotAppendEncryptionForTrustedCaller(encryption);
|
|
2515
|
+
}
|
|
2516
|
+
captureFullAppendNextStorageReader(next) {
|
|
2517
|
+
if (next instanceof Entry) {
|
|
2518
|
+
const getStorageBytes = next.getStorageBytes;
|
|
2519
|
+
return () => Reflect.apply(getStorageBytes, next, []);
|
|
2520
|
+
}
|
|
2521
|
+
const candidate = next;
|
|
2522
|
+
const init = candidate.init;
|
|
2523
|
+
const getNext = candidate.getNext;
|
|
2524
|
+
const getClock = candidate.getClock;
|
|
2525
|
+
const getStorageBytes = candidate.getStorageBytes;
|
|
2526
|
+
const verifySignatures = candidate.verifySignatures;
|
|
2527
|
+
if (typeof init !== "function" ||
|
|
2528
|
+
typeof getNext !== "function" ||
|
|
2529
|
+
typeof getClock !== "function" ||
|
|
2530
|
+
typeof getStorageBytes !== "function" ||
|
|
2531
|
+
typeof verifySignatures !== "function") {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
return () => Reflect.apply(getStorageBytes, next, []);
|
|
2535
|
+
}
|
|
2536
|
+
snapshotFullAppendNext(next, getStorageBytes, canonicalHash) {
|
|
2537
|
+
let entrySize;
|
|
2538
|
+
try {
|
|
2539
|
+
entrySize = next.size;
|
|
2540
|
+
}
|
|
2541
|
+
catch {
|
|
2542
|
+
// A valid but not-yet-sized entry still has exact owned storage bytes below.
|
|
2543
|
+
}
|
|
2544
|
+
const createdLocally = next.createdLocally;
|
|
2545
|
+
let sourceBytes = Entry.getPreparedStorageBytes(next);
|
|
2546
|
+
if (!sourceBytes) {
|
|
2547
|
+
try {
|
|
2548
|
+
sourceBytes = getStorageBytes();
|
|
2549
|
+
}
|
|
2550
|
+
catch {
|
|
2551
|
+
// Native commit-only entries intentionally keep payload/signature bytes
|
|
2552
|
+
// outside the JS Entry. They are valid sortable parents but cannot be
|
|
2553
|
+
// imported into another log as full blocks, so snapshot them below as
|
|
2554
|
+
// shallow canonical references instead.
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
const bytes = Uint8Array.from(sourceBytes);
|
|
2559
|
+
const captured = deserialize(bytes, Entry);
|
|
2560
|
+
const decodedHash = captured.hash;
|
|
2561
|
+
if (decodedHash && decodedHash !== canonicalHash) {
|
|
2562
|
+
throw new Error(`Explicit append next bytes did not match captured hash ${canonicalHash}`);
|
|
2563
|
+
}
|
|
2564
|
+
captured.hash = "";
|
|
2565
|
+
Entry.prepareMultihashBytes(captured, bytes, canonicalHash);
|
|
2566
|
+
captured.hash = canonicalHash;
|
|
2567
|
+
captured.size = entrySize ?? bytes.byteLength;
|
|
2568
|
+
captured.createdLocally = createdLocally;
|
|
2569
|
+
captured.init({
|
|
2570
|
+
encoding: this.log.encoding,
|
|
2571
|
+
keychain: this.log.keychain,
|
|
2572
|
+
});
|
|
2573
|
+
return captured;
|
|
2574
|
+
}
|
|
2575
|
+
snapshotAppendNext(next) {
|
|
2576
|
+
const hash = next.hash;
|
|
2577
|
+
if (!hash) {
|
|
2578
|
+
throw new Error("Explicit append next requires a canonical hash");
|
|
2579
|
+
}
|
|
2580
|
+
const getStorageBytes = this.captureFullAppendNextStorageReader(next);
|
|
2581
|
+
if (getStorageBytes) {
|
|
2582
|
+
const captured = this.snapshotFullAppendNext(next, getStorageBytes, hash);
|
|
2583
|
+
if (captured)
|
|
2584
|
+
return captured;
|
|
2585
|
+
}
|
|
2586
|
+
const meta = next.meta;
|
|
2587
|
+
const gid = meta.gid;
|
|
2588
|
+
const nextHashes = [...meta.next];
|
|
2589
|
+
const type = meta.type;
|
|
2590
|
+
const sourceData = meta.data;
|
|
2591
|
+
const data = sourceData && Uint8Array.from(sourceData);
|
|
2592
|
+
const clock = meta.clock;
|
|
2593
|
+
const clockId = Uint8Array.from(clock.id);
|
|
2594
|
+
const sourceTimestamp = clock.timestamp;
|
|
2595
|
+
const timestamp = new Timestamp({
|
|
2596
|
+
wallTime: sourceTimestamp.wallTime,
|
|
2597
|
+
logical: sourceTimestamp.logical,
|
|
2598
|
+
});
|
|
2599
|
+
const candidate = next;
|
|
2600
|
+
let payloadSize = 0;
|
|
2601
|
+
try {
|
|
2602
|
+
const shallowPayloadSize = candidate.payloadSize;
|
|
2603
|
+
if (typeof shallowPayloadSize === "number") {
|
|
2604
|
+
payloadSize = shallowPayloadSize;
|
|
2605
|
+
}
|
|
2606
|
+
else {
|
|
2607
|
+
const fullPayloadSize = candidate.payloadByteLength;
|
|
2608
|
+
if (typeof fullPayloadSize === "number")
|
|
2609
|
+
payloadSize = fullPayloadSize;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
catch {
|
|
2613
|
+
// Hollow native parents need only their canonical sorting/link facts.
|
|
2614
|
+
}
|
|
2615
|
+
const candidateHead = candidate.head;
|
|
2616
|
+
const head = typeof candidateHead === "boolean" ? candidateHead : true;
|
|
2617
|
+
const captured = new ShallowEntry({
|
|
2618
|
+
hash,
|
|
2619
|
+
payloadSize,
|
|
2620
|
+
head,
|
|
2621
|
+
meta: new ShallowMeta({
|
|
2622
|
+
gid,
|
|
2623
|
+
next: nextHashes,
|
|
2624
|
+
type,
|
|
2625
|
+
data,
|
|
2626
|
+
clock: new LamportClock({ id: clockId, timestamp }),
|
|
2627
|
+
}),
|
|
2628
|
+
});
|
|
2629
|
+
Object.freeze(captured.meta.next);
|
|
2630
|
+
Object.freeze(captured.meta.clock.timestamp);
|
|
2631
|
+
Object.freeze(captured.meta.clock);
|
|
2632
|
+
Object.freeze(captured.meta);
|
|
2633
|
+
return Object.freeze(captured);
|
|
2634
|
+
}
|
|
2635
|
+
snapshotSupportedAppendOptions(options, delivery, includeDocumentShape, validateCapturedOptions) {
|
|
2636
|
+
// Copy the finite public option surface, not arbitrary enumerable caller
|
|
2637
|
+
// properties. This pins every value that the async append/document paths
|
|
2638
|
+
// reread while avoiding surprising evaluation of unrelated custom getters.
|
|
2639
|
+
const captured = Object.create(null);
|
|
2640
|
+
const durability = options.durability;
|
|
2641
|
+
const deferIndexWrite = options.deferIndexWrite;
|
|
2642
|
+
const meta = options.meta;
|
|
2643
|
+
const identity = options.identity;
|
|
2644
|
+
const signers = options.signers;
|
|
2645
|
+
const trim = options.trim;
|
|
2646
|
+
const encryption = options.encryption;
|
|
2647
|
+
const onChange = options.onChange;
|
|
2648
|
+
const canAppend = options.canAppend;
|
|
2649
|
+
const replicas = options.replicas;
|
|
2650
|
+
const replicate = options.replicate;
|
|
2651
|
+
const target = options.target;
|
|
2652
|
+
const unique = includeDocumentShape ? options.unique : undefined;
|
|
2653
|
+
const checkRemote = includeDocumentShape ? options.checkRemote : undefined;
|
|
2654
|
+
if (durability !== undefined)
|
|
2655
|
+
captured.durability = durability;
|
|
2656
|
+
if (deferIndexWrite !== undefined) {
|
|
2657
|
+
captured.deferIndexWrite = deferIndexWrite;
|
|
2658
|
+
}
|
|
2659
|
+
if (meta !== undefined) {
|
|
2660
|
+
const capturedMetaInput = Object.create(null);
|
|
2661
|
+
const type = meta.type;
|
|
2662
|
+
const gidSeed = meta.gidSeed;
|
|
2663
|
+
const hasData = "data" in meta;
|
|
2664
|
+
const data = meta.data;
|
|
2665
|
+
const timestamp = meta.timestamp;
|
|
2666
|
+
const next = meta.next;
|
|
2667
|
+
if (type !== undefined)
|
|
2668
|
+
capturedMetaInput.type = type;
|
|
2669
|
+
if (gidSeed !== undefined)
|
|
2670
|
+
capturedMetaInput.gidSeed = gidSeed;
|
|
2671
|
+
if (hasData)
|
|
2672
|
+
capturedMetaInput.data = data;
|
|
2673
|
+
if (timestamp !== undefined)
|
|
2674
|
+
capturedMetaInput.timestamp = timestamp;
|
|
2675
|
+
if (next !== undefined)
|
|
2676
|
+
capturedMetaInput.next = next;
|
|
2677
|
+
captured.meta = capturedMetaInput;
|
|
2678
|
+
}
|
|
2679
|
+
if (identity !== undefined)
|
|
2680
|
+
captured.identity = identity;
|
|
2681
|
+
if (signers !== undefined)
|
|
2682
|
+
captured.signers = signers;
|
|
2683
|
+
if (trim !== undefined)
|
|
2684
|
+
captured.trim = trim;
|
|
2685
|
+
if (encryption !== undefined)
|
|
2686
|
+
captured.encryption = encryption;
|
|
2687
|
+
if (onChange !== undefined)
|
|
2688
|
+
captured.onChange = onChange;
|
|
2689
|
+
if (canAppend !== undefined)
|
|
2690
|
+
captured.canAppend = canAppend;
|
|
2691
|
+
if (replicas !== undefined)
|
|
2692
|
+
captured.replicas = replicas;
|
|
2693
|
+
if (replicate !== undefined)
|
|
2694
|
+
captured.replicate = replicate;
|
|
2695
|
+
if (target !== undefined)
|
|
2696
|
+
captured.target = target;
|
|
2697
|
+
if (delivery !== undefined)
|
|
2698
|
+
captured.delivery = delivery;
|
|
2699
|
+
if (includeDocumentShape) {
|
|
2700
|
+
if (unique !== undefined)
|
|
2701
|
+
captured.unique = unique;
|
|
2702
|
+
if (checkRemote !== undefined)
|
|
2703
|
+
captured.checkRemote = checkRemote;
|
|
2704
|
+
}
|
|
2705
|
+
// Strict-native Documents validates the already-captured finite surface
|
|
2706
|
+
// before any accepted nested value is cloned or normalized. This keeps its
|
|
2707
|
+
// mode error stable even for malformed unsupported option values, without
|
|
2708
|
+
// rereading caller-owned top-level fields.
|
|
2709
|
+
validateCapturedOptions?.(captured);
|
|
2710
|
+
if (meta !== undefined) {
|
|
2711
|
+
const capturedMetaInput = captured.meta;
|
|
2712
|
+
const capturedMeta = Object.create(null);
|
|
2713
|
+
if (capturedMetaInput.type !== undefined) {
|
|
2714
|
+
capturedMeta.type = capturedMetaInput.type;
|
|
2715
|
+
}
|
|
2716
|
+
if (capturedMetaInput.gidSeed !== undefined) {
|
|
2717
|
+
capturedMeta.gidSeed = Uint8Array.from(capturedMetaInput.gidSeed);
|
|
2718
|
+
}
|
|
2719
|
+
if ("data" in capturedMetaInput) {
|
|
2720
|
+
capturedMeta.data =
|
|
2721
|
+
capturedMetaInput.data && Uint8Array.from(capturedMetaInput.data);
|
|
2722
|
+
}
|
|
2723
|
+
if (capturedMetaInput.timestamp !== undefined) {
|
|
2724
|
+
capturedMeta.timestamp = capturedMetaInput.timestamp.clone();
|
|
2725
|
+
}
|
|
2726
|
+
if (capturedMetaInput.next !== undefined) {
|
|
2727
|
+
capturedMeta.next = Object.freeze(capturedMetaInput.next.map((entry) => this.snapshotAppendNext(entry)));
|
|
2728
|
+
}
|
|
2729
|
+
captured.meta = capturedMeta;
|
|
2730
|
+
}
|
|
2731
|
+
if (signers !== undefined)
|
|
2732
|
+
captured.signers = [...signers];
|
|
2733
|
+
if (trim !== undefined)
|
|
2734
|
+
captured.trim = this.snapshotAppendTrim(trim);
|
|
2735
|
+
if (encryption !== undefined) {
|
|
2736
|
+
captured.encryption = this.snapshotAppendEncryption(encryption);
|
|
2737
|
+
}
|
|
2738
|
+
if (replicas !== undefined) {
|
|
2739
|
+
captured.replicas =
|
|
2740
|
+
typeof replicas === "number"
|
|
2741
|
+
? replicas
|
|
2742
|
+
: new AbsoluteReplicas(replicas.getValue(this));
|
|
2743
|
+
}
|
|
2744
|
+
return captured;
|
|
2745
|
+
}
|
|
2746
|
+
validatePersistedAppendInvocation(capturedOptions, delivery) {
|
|
2747
|
+
this._parseDeliveryOptions(delivery);
|
|
2748
|
+
const target = capturedOptions.target;
|
|
2486
2749
|
if (target !== undefined && target !== "replicators") {
|
|
2487
2750
|
throw new Error('persisted delivery requires target="replicators" (or an omitted target)');
|
|
2488
2751
|
}
|
|
2489
|
-
if (
|
|
2490
|
-
(!Number.isFinite(
|
|
2491
|
-
|
|
2492
|
-
|
|
2752
|
+
if (delivery.timeout != null &&
|
|
2753
|
+
(!Number.isFinite(delivery.timeout) ||
|
|
2754
|
+
delivery.timeout <= 0 ||
|
|
2755
|
+
delivery.timeout > MAX_PERSISTED_DELIVERY_TIMEOUT_MS)) {
|
|
2493
2756
|
throw new Error(`persisted delivery timeout must be a positive number no greater than ${MAX_PERSISTED_DELIVERY_TIMEOUT_MS}`);
|
|
2494
2757
|
}
|
|
2495
|
-
if (
|
|
2496
|
-
throw
|
|
2758
|
+
if (delivery.signal?.aborted) {
|
|
2759
|
+
throw delivery.signal.reason ?? new AbortError();
|
|
2497
2760
|
}
|
|
2498
|
-
return parsed.delivery;
|
|
2499
2761
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2762
|
+
capturePersistedAppendInvocation(options) {
|
|
2763
|
+
const deliveryArgument = options?.delivery;
|
|
2764
|
+
if (typeof deliveryArgument !== "object" || deliveryArgument === null) {
|
|
2765
|
+
return undefined;
|
|
2766
|
+
}
|
|
2767
|
+
const reliability = deliveryArgument.reliability;
|
|
2768
|
+
if (reliability !== "persisted") {
|
|
2769
|
+
return undefined;
|
|
2770
|
+
}
|
|
2771
|
+
const delivery = this.snapshotDeliveryOptions(deliveryArgument, reliability);
|
|
2772
|
+
const capturedOptions = this.snapshotSupportedAppendOptions(options, delivery, false);
|
|
2773
|
+
this.validatePersistedAppendInvocation(capturedOptions, delivery);
|
|
2774
|
+
return { options: capturedOptions, delivery };
|
|
2775
|
+
}
|
|
2776
|
+
snapshotDocumentAppendOptions(options, validateCapturedOptions) {
|
|
2777
|
+
if (!options)
|
|
2778
|
+
return;
|
|
2779
|
+
const deliveryArgument = options.delivery;
|
|
2780
|
+
let delivery = deliveryArgument;
|
|
2781
|
+
if (typeof deliveryArgument === "object" && deliveryArgument !== null) {
|
|
2782
|
+
const reliability = deliveryArgument.reliability;
|
|
2783
|
+
delivery = this.snapshotDeliveryOptions(deliveryArgument, reliability);
|
|
2784
|
+
}
|
|
2785
|
+
const capturedOptions = this.snapshotSupportedAppendOptions(options, delivery, true, validateCapturedOptions);
|
|
2786
|
+
if (typeof delivery === "object" &&
|
|
2787
|
+
delivery !== null &&
|
|
2788
|
+
delivery.reliability === "persisted") {
|
|
2789
|
+
this.validatePersistedAppendInvocation(capturedOptions, delivery);
|
|
2790
|
+
}
|
|
2791
|
+
return capturedOptions;
|
|
2502
2792
|
}
|
|
2503
2793
|
async _getSortedRouteHints(targetHash) {
|
|
2504
2794
|
const pubsub = this.node.services.pubsub;
|
|
@@ -3059,16 +3349,20 @@ let SharedLog = (() => {
|
|
|
3059
3349
|
dispose: () => clearTimeout(timeout),
|
|
3060
3350
|
};
|
|
3061
3351
|
}
|
|
3062
|
-
async planPersistedDeliveryLeaders(
|
|
3352
|
+
async planPersistedDeliveryLeaders(records, replicas, ownershipLifecycleController) {
|
|
3063
3353
|
if (this.findLeadersFromEntry !== SharedLog.prototype.findLeadersFromEntry) {
|
|
3064
3354
|
const leaders = [];
|
|
3065
|
-
for (const
|
|
3355
|
+
for (const record of records) {
|
|
3356
|
+
const entry = record.createFullPlanningSource?.();
|
|
3357
|
+
if (!entry) {
|
|
3358
|
+
throw new Error(`Persisted delivery requires canonical entry bytes for custom leader planning of ${record.canonicalHash}`);
|
|
3359
|
+
}
|
|
3066
3360
|
leaders.push(await this.findLeadersFromEntry(entry, replicas, { freshLeaderPlan: true }, ownershipLifecycleController));
|
|
3067
3361
|
}
|
|
3068
3362
|
return leaders;
|
|
3069
3363
|
}
|
|
3070
|
-
const items =
|
|
3071
|
-
entry,
|
|
3364
|
+
const items = records.map((record) => ({
|
|
3365
|
+
entry: record.createDefaultPlanningSource(),
|
|
3072
3366
|
replicas,
|
|
3073
3367
|
options: { freshLeaderPlan: true, persist: false },
|
|
3074
3368
|
}));
|
|
@@ -3100,13 +3394,13 @@ let SharedLog = (() => {
|
|
|
3100
3394
|
}
|
|
3101
3395
|
async settlePersistedDelivery(input, replicas, delivery, ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(), persistedDeadline, transferOnFirstRound = false) {
|
|
3102
3396
|
const minAcks = Math.floor(delivery.minAcks);
|
|
3103
|
-
const
|
|
3104
|
-
if (
|
|
3397
|
+
const records = new Map(input.map((record) => [record.canonicalHash, record]));
|
|
3398
|
+
if (records.size === 0)
|
|
3105
3399
|
return;
|
|
3106
|
-
const committedHashes = [...
|
|
3400
|
+
const committedHashes = [...records.keys()];
|
|
3107
3401
|
const ownedDeadline = !persistedDeadline;
|
|
3108
3402
|
const deadline = persistedDeadline ??
|
|
3109
|
-
this.createPersistedDeliveryDeadline(delivery, ownershipLifecycleController,
|
|
3403
|
+
this.createPersistedDeliveryDeadline(delivery, ownershipLifecycleController, records.size);
|
|
3110
3404
|
const signal = deadline.signal;
|
|
3111
3405
|
let maxAttemptMs = MAX_PERSISTED_RECEIPT_ATTEMPT_MS;
|
|
3112
3406
|
let initialTransferPending = transferOnFirstRound;
|
|
@@ -3155,7 +3449,7 @@ let SharedLog = (() => {
|
|
|
3155
3449
|
// transport epoch. A revision/session change purges them before they can
|
|
3156
3450
|
// survive an away-and-back leader transition or combine with a later peer.
|
|
3157
3451
|
const hashesByPeer = new Map();
|
|
3158
|
-
const entryArray = [...
|
|
3452
|
+
const entryArray = [...records.values()];
|
|
3159
3453
|
const leadersByEntry = await this.planPersistedDeliveryLeaders(entryArray, replicas, ownershipLifecycleController);
|
|
3160
3454
|
if (!isRoundOwnershipCurrent())
|
|
3161
3455
|
continue;
|
|
@@ -3168,7 +3462,7 @@ let SharedLog = (() => {
|
|
|
3168
3462
|
}
|
|
3169
3463
|
}
|
|
3170
3464
|
for (let index = 0; index < entryArray.length; index++) {
|
|
3171
|
-
const hash = entryArray[index].
|
|
3465
|
+
const hash = entryArray[index].canonicalHash;
|
|
3172
3466
|
const leaders = leadersByEntry[index];
|
|
3173
3467
|
if (signal.aborted) {
|
|
3174
3468
|
throw signal.reason ?? new AbortError();
|
|
@@ -3405,7 +3699,7 @@ let SharedLog = (() => {
|
|
|
3405
3699
|
}
|
|
3406
3700
|
return false;
|
|
3407
3701
|
}
|
|
3408
|
-
const hash = entryArray[index].
|
|
3702
|
+
const hash = entryArray[index].canonicalHash;
|
|
3409
3703
|
const leaders = validatedLeaders[index];
|
|
3410
3704
|
let valid = 0;
|
|
3411
3705
|
const acknowledgements = carriedAcknowledgements.get(hash);
|
|
@@ -7545,19 +7839,29 @@ let SharedLog = (() => {
|
|
|
7545
7839
|
}
|
|
7546
7840
|
async append(data, options) {
|
|
7547
7841
|
this.throwIfNativeDurableCommitFailed();
|
|
7548
|
-
const
|
|
7842
|
+
const persistedInvocation = this.capturePersistedAppendInvocation(options);
|
|
7843
|
+
options = persistedInvocation?.options ?? options;
|
|
7844
|
+
const persistedDelivery = persistedInvocation?.delivery;
|
|
7549
7845
|
const ownershipLifecycleController = this.captureReplicationOwnershipLifecycle();
|
|
7550
7846
|
if (this._isAdaptiveReplicating) {
|
|
7551
7847
|
this.markLocalAppendActivity();
|
|
7552
7848
|
}
|
|
7553
7849
|
const { appendOptions, minReplicasValue } = this.createLogAppendOptions(options, ownershipLifecycleController);
|
|
7554
7850
|
let committedHashes;
|
|
7851
|
+
let persistedAppendCommit;
|
|
7852
|
+
let persistedPlanningRecord;
|
|
7555
7853
|
if (persistedDelivery) {
|
|
7556
|
-
appendOptions.__peerbitOnLocalCommit = (hashes) => {
|
|
7854
|
+
appendOptions.__peerbitOnLocalCommit = (hashes, entries) => {
|
|
7557
7855
|
// The lower log reports the exact hashes immediately after their
|
|
7558
|
-
// irreversible local mutation and before
|
|
7559
|
-
//
|
|
7560
|
-
|
|
7856
|
+
// irreversible local mutation and before trim/change callbacks. Own the
|
|
7857
|
+
// canonical hash first, then detach all planning facts and storage bytes
|
|
7858
|
+
// synchronously so callback mutation cannot retarget the later quorum.
|
|
7859
|
+
committedHashes = Object.freeze([...hashes]);
|
|
7860
|
+
if (hashes.length !== 1 || entries?.length !== 1) {
|
|
7861
|
+
throw new Error("Persisted delivery requires exact lower-log entry commit evidence");
|
|
7862
|
+
}
|
|
7863
|
+
persistedAppendCommit = this.capturePersistedLocalAppendCommit(hashes[0], entries[0]);
|
|
7864
|
+
persistedPlanningRecord = this.createPersistedDeliveryPlanningRecord(persistedAppendCommit);
|
|
7561
7865
|
};
|
|
7562
7866
|
}
|
|
7563
7867
|
let persistedDeadline;
|
|
@@ -7568,19 +7872,23 @@ let SharedLog = (() => {
|
|
|
7568
7872
|
};
|
|
7569
7873
|
try {
|
|
7570
7874
|
const result = await this.log.append(data, appendOptions);
|
|
7571
|
-
committedHashes
|
|
7875
|
+
if (persistedDelivery && (!committedHashes || !persistedPlanningRecord)) {
|
|
7876
|
+
throw new Error("Lower log did not provide persisted-delivery commit evidence");
|
|
7877
|
+
}
|
|
7572
7878
|
persistedDeadline = persistedDelivery
|
|
7573
7879
|
? this.createPersistedDeliveryDeadline(persistedDelivery, ownershipLifecycleController, 1)
|
|
7574
7880
|
: undefined;
|
|
7575
7881
|
this.throwIfReplicationOwnershipLifecycleInactive(ownershipLifecycleController);
|
|
7576
7882
|
throwIfDeliveryAborted();
|
|
7577
|
-
|
|
7883
|
+
const processingEntry = persistedPlanningRecord?.createFullPlanningSource?.() ?? result.entry;
|
|
7884
|
+
await this.processLocalAppend(processingEntry, result.removed, options, {
|
|
7578
7885
|
minReplicasValue,
|
|
7886
|
+
appendFacts: persistedAppendCommit,
|
|
7579
7887
|
ownershipLifecycleController,
|
|
7580
7888
|
});
|
|
7581
7889
|
throwIfDeliveryAborted();
|
|
7582
7890
|
if (persistedDelivery && persistedDeadline) {
|
|
7583
|
-
await this.settlePersistedDelivery([
|
|
7891
|
+
await this.settlePersistedDelivery([persistedPlanningRecord], minReplicasValue, persistedDelivery, ownershipLifecycleController, persistedDeadline);
|
|
7584
7892
|
}
|
|
7585
7893
|
this.throwIfReplicationOwnershipLifecycleInactive(ownershipLifecycleController);
|
|
7586
7894
|
return result;
|
|
@@ -7596,7 +7904,7 @@ let SharedLog = (() => {
|
|
|
7596
7904
|
}
|
|
7597
7905
|
}
|
|
7598
7906
|
rejectPersistedDeliveryOnTrustedLocalAppend(options) {
|
|
7599
|
-
if (this.
|
|
7907
|
+
if (this.capturePersistedAppendInvocation(options)) {
|
|
7600
7908
|
throw new Error("trusted local append paths require delivery=false; call deliverPersistedEntries after the local commit");
|
|
7601
7909
|
}
|
|
7602
7910
|
}
|
|
@@ -9544,7 +9852,7 @@ let SharedLog = (() => {
|
|
|
9544
9852
|
}
|
|
9545
9853
|
async appendMany(data, options) {
|
|
9546
9854
|
this.throwIfNativeDurableCommitFailed();
|
|
9547
|
-
const persistedDelivery = this.
|
|
9855
|
+
const persistedDelivery = this.capturePersistedAppendInvocation(options);
|
|
9548
9856
|
if (data.length === 0) {
|
|
9549
9857
|
return { entries: [], removed: [] };
|
|
9550
9858
|
}
|
|
@@ -9599,13 +9907,16 @@ let SharedLog = (() => {
|
|
|
9599
9907
|
* higher-level transactional/batched writers so a receipt timeout never
|
|
9600
9908
|
* rolls back or hides their successful local commit.
|
|
9601
9909
|
*/
|
|
9602
|
-
async deliverPersistedPlanningEntries(
|
|
9910
|
+
async deliverPersistedPlanningEntries(resolveRecords, committedHashesInput, options) {
|
|
9911
|
+
const committedHashes = Object.freeze([...committedHashesInput]);
|
|
9603
9912
|
let persistedDeadline;
|
|
9604
9913
|
try {
|
|
9605
|
-
const
|
|
9606
|
-
if (!
|
|
9914
|
+
const persistedInvocation = this.capturePersistedAppendInvocation(options);
|
|
9915
|
+
if (!persistedInvocation) {
|
|
9607
9916
|
throw new Error('deliverPersistedEntries requires reliability="persisted"');
|
|
9608
9917
|
}
|
|
9918
|
+
options = persistedInvocation.options;
|
|
9919
|
+
const delivery = persistedInvocation.delivery;
|
|
9609
9920
|
const ownershipLifecycleController = this.captureReplicationOwnershipLifecycle();
|
|
9610
9921
|
const deadline = this.createPersistedDeliveryDeadline(delivery, ownershipLifecycleController, committedHashes.length);
|
|
9611
9922
|
persistedDeadline = deadline;
|
|
@@ -9617,13 +9928,21 @@ let SharedLog = (() => {
|
|
|
9617
9928
|
throw new TimeoutError(`Timed out waiting for ${Math.floor(delivery.minAcks)} persisted remote replicas.`);
|
|
9618
9929
|
}
|
|
9619
9930
|
};
|
|
9620
|
-
const
|
|
9931
|
+
const records = resolveRecords();
|
|
9621
9932
|
throwIfDeliveryAborted();
|
|
9622
|
-
if (
|
|
9933
|
+
if (records.length !== committedHashes.length) {
|
|
9934
|
+
throw new Error("Persisted delivery planning evidence count mismatch");
|
|
9935
|
+
}
|
|
9936
|
+
for (let index = 0; index < records.length; index++) {
|
|
9937
|
+
if (records[index].canonicalHash !== committedHashes[index]) {
|
|
9938
|
+
throw new Error(`Persisted delivery planning evidence did not match committed hash ${committedHashes[index]}`);
|
|
9939
|
+
}
|
|
9940
|
+
}
|
|
9941
|
+
if (records.length === 0)
|
|
9623
9942
|
return;
|
|
9624
9943
|
const { minReplicasValue } = this.createLogAppendOptions(options, ownershipLifecycleController);
|
|
9625
9944
|
throwIfDeliveryAborted();
|
|
9626
|
-
await this.settlePersistedDelivery(
|
|
9945
|
+
await this.settlePersistedDelivery(records, minReplicasValue, delivery, ownershipLifecycleController, deadline, true);
|
|
9627
9946
|
}
|
|
9628
9947
|
catch (error) {
|
|
9629
9948
|
throw new PersistedDeliveryError(error, committedHashes);
|
|
@@ -9632,29 +9951,32 @@ let SharedLog = (() => {
|
|
|
9632
9951
|
persistedDeadline?.dispose();
|
|
9633
9952
|
}
|
|
9634
9953
|
}
|
|
9635
|
-
|
|
9954
|
+
createPersistedDeliveryPlanningRecords(appendCommits, materializeEntries) {
|
|
9636
9955
|
let materializedEntries;
|
|
9637
9956
|
const requiresFullEntries = this.findLeadersFromEntry !== SharedLog.prototype.findLeadersFromEntry;
|
|
9638
|
-
const
|
|
9957
|
+
const getCommittedEntry = (appendCommit, index) => {
|
|
9639
9958
|
materializedEntries ??= materializeEntries();
|
|
9640
9959
|
const entry = materializedEntries[index];
|
|
9641
|
-
|
|
9960
|
+
const materializedHash = entry?.hash;
|
|
9961
|
+
if (!entry || materializedHash !== appendCommit.hash) {
|
|
9642
9962
|
throw new Error(`Persisted delivery materializer did not return committed entry ${appendCommit.hash}`);
|
|
9643
9963
|
}
|
|
9644
9964
|
return entry;
|
|
9645
9965
|
};
|
|
9646
9966
|
return appendCommits.map((appendCommit, index) => {
|
|
9647
|
-
|
|
9648
|
-
|
|
9967
|
+
let capturedCommit = this.snapshotPreparedLocalAppendCommit(appendCommit);
|
|
9968
|
+
if (requiresFullEntries && !capturedCommit.storageBytes) {
|
|
9969
|
+
capturedCommit = this.capturePersistedLocalAppendCommit(capturedCommit.hash, getCommittedEntry(capturedCommit, index));
|
|
9649
9970
|
}
|
|
9650
|
-
return
|
|
9971
|
+
return this.createPersistedDeliveryPlanningRecord(capturedCommit);
|
|
9651
9972
|
});
|
|
9652
9973
|
}
|
|
9653
9974
|
deliverPersistedAppendCommits(appendCommits, materializeEntries, options) {
|
|
9654
|
-
return this.deliverPersistedPlanningEntries(() => this.
|
|
9975
|
+
return this.deliverPersistedPlanningEntries(() => this.createPersistedDeliveryPlanningRecords(appendCommits, materializeEntries), appendCommits.map((appendCommit) => appendCommit.hash), options);
|
|
9655
9976
|
}
|
|
9656
9977
|
async deliverPersistedEntries(entries, options) {
|
|
9657
|
-
|
|
9978
|
+
const records = entries.map((entry) => this.snapshotPersistedDeliveryPlanningEntry(entry));
|
|
9979
|
+
return this.deliverPersistedPlanningEntries(() => records, records.map((record) => record.canonicalHash), options);
|
|
9658
9980
|
}
|
|
9659
9981
|
canCoalesceLocalAppendMany(entries, options) {
|
|
9660
9982
|
if (entries.length <= 1 ||
|
|
@@ -10259,7 +10581,11 @@ let SharedLog = (() => {
|
|
|
10259
10581
|
next: entry.meta.next,
|
|
10260
10582
|
wallTime: entry.meta.clock.timestamp.wallTime,
|
|
10261
10583
|
logical: entry.meta.clock.timestamp.logical,
|
|
10584
|
+
clockId: entry.meta.clock.id,
|
|
10585
|
+
type: entry.meta.type,
|
|
10586
|
+
metaData: entry.meta.data,
|
|
10262
10587
|
payloadSize: entry.payload.byteLength,
|
|
10588
|
+
entrySize: entry.size,
|
|
10263
10589
|
metaBytes: entry.getMetaBytes?.(),
|
|
10264
10590
|
hashNumber: nativeAppendPlan?.hashNumber,
|
|
10265
10591
|
coordinateFields: nativeAppendPlan?.preparedCoordinate.fields,
|
|
@@ -10272,6 +10598,9 @@ let SharedLog = (() => {
|
|
|
10272
10598
|
next: appendFacts.next,
|
|
10273
10599
|
wallTime: appendFacts.wallTime,
|
|
10274
10600
|
logical: appendFacts.logical,
|
|
10601
|
+
clockId: appendFacts.clockId,
|
|
10602
|
+
type: appendFacts.type,
|
|
10603
|
+
metaData: appendFacts.metaData,
|
|
10275
10604
|
payloadSize: appendFacts.payloadSize,
|
|
10276
10605
|
metaBytes: appendFacts.metaBytes,
|
|
10277
10606
|
hashNumber: nativeAppendPlan?.hashNumber,
|
|
@@ -10279,6 +10608,109 @@ let SharedLog = (() => {
|
|
|
10279
10608
|
nativeAppendPlan?.preparedCoordinate?.fields,
|
|
10280
10609
|
};
|
|
10281
10610
|
}
|
|
10611
|
+
snapshotPreparedLocalAppendCommit(appendCommit) {
|
|
10612
|
+
const coordinateFields = appendCommit.coordinateFields;
|
|
10613
|
+
return Object.freeze({
|
|
10614
|
+
...appendCommit,
|
|
10615
|
+
next: [...appendCommit.next],
|
|
10616
|
+
clockId: appendCommit.clockId && Uint8Array.from(appendCommit.clockId),
|
|
10617
|
+
metaData: appendCommit.metaData && Uint8Array.from(appendCommit.metaData),
|
|
10618
|
+
metaBytes: appendCommit.metaBytes && Uint8Array.from(appendCommit.metaBytes),
|
|
10619
|
+
storageBytes: appendCommit.storageBytes && Uint8Array.from(appendCommit.storageBytes),
|
|
10620
|
+
coordinateFields: coordinateFields
|
|
10621
|
+
? {
|
|
10622
|
+
...coordinateFields,
|
|
10623
|
+
coordinates: [...coordinateFields.coordinates],
|
|
10624
|
+
coordinateStrings: coordinateFields.coordinateStrings && [
|
|
10625
|
+
...coordinateFields.coordinateStrings,
|
|
10626
|
+
],
|
|
10627
|
+
metaBytes: Uint8Array.from(coordinateFields.metaBytes),
|
|
10628
|
+
}
|
|
10629
|
+
: undefined,
|
|
10630
|
+
documentPreviousContext: appendCommit.documentPreviousContext
|
|
10631
|
+
? { ...appendCommit.documentPreviousContext }
|
|
10632
|
+
: undefined,
|
|
10633
|
+
});
|
|
10634
|
+
}
|
|
10635
|
+
capturePersistedLocalAppendCommit(canonicalHash, entry) {
|
|
10636
|
+
const sourceHash = entry.hash;
|
|
10637
|
+
if (sourceHash !== canonicalHash) {
|
|
10638
|
+
throw new Error(`Lower-log commit evidence entry did not match committed hash ${canonicalHash}`);
|
|
10639
|
+
}
|
|
10640
|
+
const storageBytes = Entry.getPreparedStorageBytes(entry) ?? entry.getStorageBytes();
|
|
10641
|
+
return this.snapshotPreparedLocalAppendCommit({
|
|
10642
|
+
...this.createPreparedLocalAppendCommit(entry),
|
|
10643
|
+
hash: canonicalHash,
|
|
10644
|
+
storageBytes,
|
|
10645
|
+
});
|
|
10646
|
+
}
|
|
10647
|
+
createPersistedDeliveryPlanningRecord(appendCommitInput) {
|
|
10648
|
+
// Callers hand this helper an invocation-owned snapshot. Do not duplicate
|
|
10649
|
+
// full entry bytes here; a fresh copy is made only when a custom planner is
|
|
10650
|
+
// actually invoked (and again for each replan so planner mutation cannot
|
|
10651
|
+
// persist between rounds).
|
|
10652
|
+
const appendCommit = appendCommitInput;
|
|
10653
|
+
const canonicalHash = appendCommit.hash;
|
|
10654
|
+
const coordinateFields = appendCommit.coordinateFields;
|
|
10655
|
+
const createDefaultPlanningSource = coordinateFields
|
|
10656
|
+
? () => this._coordinates.materializeResidentCoordinateEntry({
|
|
10657
|
+
...coordinateFields,
|
|
10658
|
+
coordinates: [...coordinateFields.coordinates],
|
|
10659
|
+
coordinateStrings: coordinateFields.coordinateStrings && [
|
|
10660
|
+
...coordinateFields.coordinateStrings,
|
|
10661
|
+
],
|
|
10662
|
+
metaBytes: Uint8Array.from(coordinateFields.metaBytes),
|
|
10663
|
+
})
|
|
10664
|
+
: () => new ShallowEntry({
|
|
10665
|
+
hash: canonicalHash,
|
|
10666
|
+
head: true,
|
|
10667
|
+
payloadSize: appendCommit.payloadSize,
|
|
10668
|
+
meta: new ShallowMeta({
|
|
10669
|
+
gid: appendCommit.gid,
|
|
10670
|
+
next: [...appendCommit.next],
|
|
10671
|
+
type: appendCommit.type ?? EntryType.APPEND,
|
|
10672
|
+
data: appendCommit.metaData && Uint8Array.from(appendCommit.metaData),
|
|
10673
|
+
clock: new LamportClock({
|
|
10674
|
+
id: Uint8Array.from(appendCommit.clockId ?? new Uint8Array()),
|
|
10675
|
+
timestamp: new Timestamp({
|
|
10676
|
+
wallTime: appendCommit.wallTime,
|
|
10677
|
+
logical: appendCommit.logical,
|
|
10678
|
+
}),
|
|
10679
|
+
}),
|
|
10680
|
+
}),
|
|
10681
|
+
});
|
|
10682
|
+
const storageBytes = appendCommit.storageBytes;
|
|
10683
|
+
const createFullPlanningSource = storageBytes
|
|
10684
|
+
? () => {
|
|
10685
|
+
const bytes = Uint8Array.from(storageBytes);
|
|
10686
|
+
const entry = deserialize(bytes, Entry);
|
|
10687
|
+
const decodedHash = entry.hash;
|
|
10688
|
+
if (decodedHash && decodedHash !== canonicalHash) {
|
|
10689
|
+
throw new Error(`Persisted delivery planning bytes did not match committed hash ${canonicalHash}`);
|
|
10690
|
+
}
|
|
10691
|
+
if (!decodedHash) {
|
|
10692
|
+
Entry.prepareMultihashBytes(entry, bytes, canonicalHash);
|
|
10693
|
+
entry.hash = canonicalHash;
|
|
10694
|
+
}
|
|
10695
|
+
entry.size = appendCommit.entrySize ?? bytes.byteLength;
|
|
10696
|
+
entry.createdLocally = true;
|
|
10697
|
+
entry.init({
|
|
10698
|
+
encoding: this.log.encoding,
|
|
10699
|
+
keychain: this.log.keychain,
|
|
10700
|
+
});
|
|
10701
|
+
return entry;
|
|
10702
|
+
}
|
|
10703
|
+
: undefined;
|
|
10704
|
+
return Object.freeze({
|
|
10705
|
+
canonicalHash,
|
|
10706
|
+
createDefaultPlanningSource,
|
|
10707
|
+
createFullPlanningSource,
|
|
10708
|
+
});
|
|
10709
|
+
}
|
|
10710
|
+
snapshotPersistedDeliveryPlanningEntry(value) {
|
|
10711
|
+
const canonicalHash = value.hash;
|
|
10712
|
+
return this.createPersistedDeliveryPlanningRecord(this.capturePersistedLocalAppendCommit(canonicalHash, value));
|
|
10713
|
+
}
|
|
10282
10714
|
createPreparedLocalAppendCommits(entries, nativeAppendPlans) {
|
|
10283
10715
|
return entries.map((entry, index) => this.createPreparedLocalAppendCommit(entry, nativeAppendPlans?.[index]));
|
|
10284
10716
|
}
|