@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.
- package/dist/src/exchange-heads.d.ts +8 -0
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +8 -0
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +292 -26
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +23 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +91 -0
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +6 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +13 -0
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/dist/src/sync/index.d.ts +3 -1
- package/dist/src/sync/index.d.ts.map +1 -1
- package/package.json +16 -16
- package/src/exchange-heads.ts +9 -0
- package/src/index.ts +386 -35
- package/src/replication-info-v2-receive.ts +122 -0
- package/src/replication-info-v2-send.ts +16 -0
- package/src/sync/index.ts +3 -1
|
@@ -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
|
|
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
|
};
|