@peerbit/shared-log 16.0.23 → 16.0.25

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.
@@ -112,6 +112,11 @@ export type ReplicationInfoV2LocalCapabilityAdvertisement = {
112
112
  firstAttempt?: Promise<void>;
113
113
  };
114
114
 
115
+ type ReplicationInfoV2RemoteFullRearm = {
116
+ lastAttemptAt: number;
117
+ inFlight?: Promise<void>;
118
+ };
119
+
115
120
  export type ReplicationInfoV2LocalCapabilityRefresh = {
116
121
  receiverTransportSession: bigint;
117
122
  requestNotBeforeMs: number;
@@ -183,6 +188,7 @@ export type ReplicationInfoV2ReceiveDeps = {
183
188
  peerSession: object;
184
189
  receiveEpoch: object | null;
185
190
  signal: AbortSignal;
191
+ requestRemoteFullRearm?: boolean;
186
192
  }) => Promise<ReplicationInfoV2LocalCapabilityRefresh | undefined>;
187
193
  onRequestError?: (error: unknown) => void;
188
194
  onLocalCapabilityError?: (error: unknown) => void;
@@ -217,6 +223,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
217
223
  string,
218
224
  ReplicationInfoV2LocalCapabilityAdvertisement
219
225
  >;
226
+ _remoteFullRearmBySession!: WeakMap<object, ReplicationInfoV2RemoteFullRearm>;
220
227
  _reservedAdmissionsByPeer!: Map<string, ReplicationInfoV2ReceiveAdmission>;
221
228
 
222
229
  private readonly now: () => number;
@@ -243,6 +250,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
243
250
  this._localCapabilityReadyBySession = new WeakMap();
244
251
  this._localCapabilityContextBySession = new WeakMap();
245
252
  this._localCapabilityAdvertisementsByPeer = new Map();
253
+ this._remoteFullRearmBySession = new WeakMap();
246
254
  this._reservedAdmissionsByPeer = new Map();
247
255
  }
248
256
 
@@ -253,6 +261,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
253
261
  this._localCapabilityReadyBySession = new WeakMap();
254
262
  this._localCapabilityContextBySession = new WeakMap();
255
263
  this._localCapabilityAdvertisementsByPeer = new Map();
264
+ this._remoteFullRearmBySession = new WeakMap();
256
265
  this._reservedAdmissionsByPeer = new Map();
257
266
  }
258
267
 
@@ -270,6 +279,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
270
279
  this._cutoverPeerSessions = new WeakSet();
271
280
  this._localCapabilityReadyBySession = new WeakMap();
272
281
  this._localCapabilityContextBySession = new WeakMap();
282
+ this._remoteFullRearmBySession = new WeakMap();
273
283
  }
274
284
 
275
285
  clearPeer(peerHash: string, expectedSession?: object): void {
@@ -291,6 +301,7 @@ export class ReplicationInfoV2ReceiveCoordinator {
291
301
  if (expectedSession) {
292
302
  this._localCapabilityReadyBySession.delete(expectedSession);
293
303
  this._localCapabilityContextBySession.delete(expectedSession);
304
+ this._remoteFullRearmBySession.delete(expectedSession);
294
305
  this._cutoverPeerSessions.delete(expectedSession);
295
306
  }
296
307
  }
@@ -625,6 +636,117 @@ export class ReplicationInfoV2ReceiveCoordinator {
625
636
  };
626
637
  }
627
638
 
639
+ /**
640
+ * Send one transient, authenticated rearm hint for the exact current local
641
+ * receive grant. The hint asks a patched remote receiver to rotate its
642
+ * challenge and issue another Full request, rebuilding an outbound sender
643
+ * stream that was lost while both directions otherwise remained current.
644
+ *
645
+ * This deliberately does not mutate the steady advertisement worker. One
646
+ * attempt is coalesced per PeerSession and bound to the caller's signal; the
647
+ * readiness wait's bounded recovery tick decides whether another attempt is
648
+ * needed.
649
+ */
650
+ reAdvertiseLocalCapabilityForRemoteFull(properties: {
651
+ peerHash: string;
652
+ peerSession: object;
653
+ receiveEpoch: object | null;
654
+ signal: AbortSignal;
655
+ }): boolean {
656
+ const context = this._localCapabilityContextBySession.get(
657
+ properties.peerSession,
658
+ );
659
+ const ready = this._localCapabilityReadyBySession.get(
660
+ properties.peerSession,
661
+ );
662
+ if (
663
+ properties.signal.aborted ||
664
+ !context ||
665
+ context.peerHash !== properties.peerHash ||
666
+ context.lifecycleSignal.aborted ||
667
+ this.deps.isClosed() ||
668
+ !this.deps.isPeerStateCurrent(
669
+ properties.peerHash,
670
+ properties.peerSession,
671
+ properties.receiveEpoch,
672
+ ) ||
673
+ !ready ||
674
+ ready.peerHash !== properties.peerHash ||
675
+ ready.receiveEpoch !== properties.receiveEpoch ||
676
+ ready.receiverTransportSession !== this.deps.getReceiverTransportSession()
677
+ ) {
678
+ return false;
679
+ }
680
+
681
+ const now = this.now();
682
+ let rearm = this._remoteFullRearmBySession.get(properties.peerSession);
683
+ if (rearm?.inFlight) {
684
+ return true;
685
+ }
686
+ if (
687
+ rearm !== undefined &&
688
+ now - rearm.lastAttemptAt < this.requestRetryMs
689
+ ) {
690
+ return true;
691
+ }
692
+ if (!rearm) {
693
+ rearm = { lastAttemptAt: now };
694
+ this._remoteFullRearmBySession.set(properties.peerSession, rearm);
695
+ } else {
696
+ rearm.lastAttemptAt = now;
697
+ }
698
+
699
+ const operationSignal = AbortSignal.any([
700
+ context.lifecycleSignal,
701
+ properties.signal,
702
+ ]);
703
+ let operation: Promise<void>;
704
+ const detach = () => {
705
+ if (rearm?.inFlight === operation) {
706
+ rearm.inFlight = undefined;
707
+ }
708
+ };
709
+ operation = Promise.resolve()
710
+ .then(() => {
711
+ if (operationSignal.aborted) {
712
+ throw operationSignal.reason;
713
+ }
714
+ return this.deps.refreshLocalCapability({
715
+ peerHash: properties.peerHash,
716
+ target: context.target,
717
+ peerSession: properties.peerSession,
718
+ receiveEpoch: properties.receiveEpoch,
719
+ signal: operationSignal,
720
+ requestRemoteFullRearm: true,
721
+ });
722
+ })
723
+ .then(() => undefined)
724
+ .catch((error) => {
725
+ if (
726
+ !operationSignal.aborted &&
727
+ !this.deps.isClosed() &&
728
+ this.deps.isPeerStateCurrent(
729
+ properties.peerHash,
730
+ properties.peerSession,
731
+ properties.receiveEpoch,
732
+ )
733
+ ) {
734
+ this.deps.onLocalCapabilityError?.(error);
735
+ }
736
+ })
737
+ .finally(() => {
738
+ operationSignal.removeEventListener("abort", detach);
739
+ detach();
740
+ });
741
+ rearm.inFlight = operation;
742
+ operationSignal.addEventListener("abort", detach, { once: true });
743
+ if (operationSignal.aborted) {
744
+ detach();
745
+ }
746
+ void operation;
747
+ return true;
748
+ }
749
+
628
750
  private promoteLocalCapabilityAdvertisement(
629
751
  state: ReplicationInfoV2LocalCapabilityAdvertisement,
630
752
  ): boolean {
@@ -561,6 +561,22 @@ export class ReplicationInfoV2SendCoordinator<R extends "u32" | "u64"> {
561
561
  );
562
562
  }
563
563
 
564
+ /**
565
+ * Whether the exact current destination generation already has an outbound
566
+ * V2 stream. A missing/stale stream cannot be repaired by confirmation
567
+ * retries alone: the remote receiver must issue another Full request first.
568
+ */
569
+ hasCurrentStateForPeer(target: ApplicationConfirmationTarget): boolean {
570
+ const state = this._sendStates.get(target.peerHash);
571
+ return (
572
+ state !== undefined &&
573
+ state.peerSession === target.peerSession &&
574
+ state.receiverTransportSession === target.receiverTransportSession &&
575
+ this.isCurrent(state) &&
576
+ this.supportsApplicationConfirmation(state)
577
+ );
578
+ }
579
+
564
580
  private trackRetiringWorker(state: ReplicationInfoV2SendState): void {
565
581
  const worker = state.worker;
566
582
  if (!worker) {
package/src/sync/index.ts CHANGED
@@ -93,7 +93,9 @@ export type SyncOptions<R extends "u32" | "u64"> = {
93
93
 
94
94
  /**
95
95
  * Optional profiling callback. It is only invoked when provided, and should
96
- * avoid blocking because it runs inside sync hot paths.
96
+ * avoid blocking because it runs inline during open, provider resolution,
97
+ * and sync paths. Open-phase events describe setup progress only; they are
98
+ * not replication or durable-write readiness barriers.
97
99
  */
98
100
  profile?: SyncProfileFn;
99
101
  };