@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.js CHANGED
@@ -2476,29 +2476,319 @@ let SharedLog = (() => {
2476
2476
  }
2477
2477
  return true;
2478
2478
  }
2479
- getPersistedDeliveryOptions(options) {
2480
- const delivery = options?.delivery;
2481
- if (typeof delivery !== "object" || delivery.reliability !== "persisted") {
2482
- return undefined;
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
- const parsed = this._parseDeliveryOptions(options?.delivery);
2485
- const target = options?.target;
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 (parsed.delivery?.timeout != null &&
2490
- (!Number.isFinite(parsed.delivery.timeout) ||
2491
- parsed.delivery.timeout <= 0 ||
2492
- parsed.delivery.timeout > MAX_PERSISTED_DELIVERY_TIMEOUT_MS)) {
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 (parsed.delivery?.signal?.aborted) {
2496
- throw parsed.delivery.signal.reason ?? new AbortError();
2758
+ if (delivery.signal?.aborted) {
2759
+ throw delivery.signal.reason ?? new AbortError();
2497
2760
  }
2498
- return parsed.delivery;
2499
2761
  }
2500
- assertPersistedDeliveryOptions(options) {
2501
- this.getPersistedDeliveryOptions(options);
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(entries, replicas, ownershipLifecycleController) {
3352
+ async planPersistedDeliveryLeaders(records, replicas, ownershipLifecycleController) {
3063
3353
  if (this.findLeadersFromEntry !== SharedLog.prototype.findLeadersFromEntry) {
3064
3354
  const leaders = [];
3065
- for (const entry of entries) {
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 = entries.map((entry) => ({
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 entries = new Map(input.map((entry) => [entry.hash, entry]));
3104
- if (entries.size === 0)
3397
+ const records = new Map(input.map((record) => [record.canonicalHash, record]));
3398
+ if (records.size === 0)
3105
3399
  return;
3106
- const committedHashes = [...entries.keys()];
3400
+ const committedHashes = [...records.keys()];
3107
3401
  const ownedDeadline = !persistedDeadline;
3108
3402
  const deadline = persistedDeadline ??
3109
- this.createPersistedDeliveryDeadline(delivery, ownershipLifecycleController, entries.size);
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 = [...entries.values()];
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].hash;
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].hash;
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 persistedDelivery = this.getPersistedDeliveryOptions(options);
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 entry initialization, trim,
7559
- // or change callbacks can reject the append.
7560
- committedHashes = hashes;
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 ??= [result.entry.hash];
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
- await this.processLocalAppend(result.entry, result.removed, options, {
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([result.entry], minReplicasValue, persistedDelivery, ownershipLifecycleController, persistedDeadline);
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.getPersistedDeliveryOptions(options)) {
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.getPersistedDeliveryOptions(options);
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(resolveEntries, committedHashes, options) {
9910
+ async deliverPersistedPlanningEntries(resolveRecords, committedHashesInput, options) {
9911
+ const committedHashes = Object.freeze([...committedHashesInput]);
9603
9912
  let persistedDeadline;
9604
9913
  try {
9605
- const delivery = this.getPersistedDeliveryOptions(options);
9606
- if (!delivery) {
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 entries = resolveEntries();
9931
+ const records = resolveRecords();
9621
9932
  throwIfDeliveryAborted();
9622
- if (entries.length === 0)
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(entries, minReplicasValue, delivery, ownershipLifecycleController, deadline, true);
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
- createPersistedDeliveryPlanningEntries(appendCommits, materializeEntries) {
9954
+ createPersistedDeliveryPlanningRecords(appendCommits, materializeEntries) {
9636
9955
  let materializedEntries;
9637
9956
  const requiresFullEntries = this.findLeadersFromEntry !== SharedLog.prototype.findLeadersFromEntry;
9638
- const getMaterializedEntry = (appendCommit, index) => {
9957
+ const getCommittedEntry = (appendCommit, index) => {
9639
9958
  materializedEntries ??= materializeEntries();
9640
9959
  const entry = materializedEntries[index];
9641
- if (!entry || entry.hash !== appendCommit.hash) {
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
- if (!requiresFullEntries && appendCommit.coordinateFields) {
9648
- return this._coordinates.materializeResidentCoordinateEntry(appendCommit.coordinateFields);
9967
+ let capturedCommit = this.snapshotPreparedLocalAppendCommit(appendCommit);
9968
+ if (requiresFullEntries && !capturedCommit.storageBytes) {
9969
+ capturedCommit = this.capturePersistedLocalAppendCommit(capturedCommit.hash, getCommittedEntry(capturedCommit, index));
9649
9970
  }
9650
- return getMaterializedEntry(appendCommit, index);
9971
+ return this.createPersistedDeliveryPlanningRecord(capturedCommit);
9651
9972
  });
9652
9973
  }
9653
9974
  deliverPersistedAppendCommits(appendCommits, materializeEntries, options) {
9654
- return this.deliverPersistedPlanningEntries(() => this.createPersistedDeliveryPlanningEntries(appendCommits, materializeEntries), appendCommits.map((appendCommit) => appendCommit.hash), options);
9975
+ return this.deliverPersistedPlanningEntries(() => this.createPersistedDeliveryPlanningRecords(appendCommits, materializeEntries), appendCommits.map((appendCommit) => appendCommit.hash), options);
9655
9976
  }
9656
9977
  async deliverPersistedEntries(entries, options) {
9657
- return this.deliverPersistedPlanningEntries(() => entries, entries.map((entry) => entry.hash), options);
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
  }