@peerbit/shared-log 16.0.27 → 16.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/shared-log",
3
- "version": "16.0.27",
3
+ "version": "16.0.28",
4
4
  "description": "Shared log",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -62,22 +62,22 @@
62
62
  "pidusage": "^4.0.1",
63
63
  "pino": "^9.4.0",
64
64
  "uint8arrays": "^5.1.0",
65
+ "@peerbit/cache": "3.1.1",
65
66
  "@peerbit/blocks": "4.3.0",
66
67
  "@peerbit/blocks-interface": "2.2.0",
67
- "@peerbit/cache": "3.1.1",
68
68
  "@peerbit/crypto": "3.1.6",
69
- "@peerbit/any-store": "2.2.17",
70
69
  "@peerbit/diagnostics": "0.0.1",
71
70
  "@peerbit/indexer-interface": "3.0.13",
71
+ "@peerbit/any-store": "2.2.17",
72
72
  "@peerbit/indexer-sqlite3": "3.0.20",
73
73
  "@peerbit/log": "6.2.32",
74
74
  "@peerbit/logger": "2.0.2",
75
75
  "@peerbit/program": "6.0.59",
76
76
  "@peerbit/pubsub": "5.4.5",
77
- "@peerbit/riblt": "1.2.0",
78
77
  "@peerbit/pubsub-interface": "5.2.2",
79
- "@peerbit/rpc": "6.1.27",
78
+ "@peerbit/riblt": "1.2.0",
80
79
  "@peerbit/time": "3.0.1",
80
+ "@peerbit/rpc": "6.1.27",
81
81
  "@peerbit/stream-interface": "6.0.16"
82
82
  },
83
83
  "optionalDependencies": {
@@ -88,11 +88,11 @@
88
88
  "@types/libsodium-wrappers": "^0.7.14",
89
89
  "@types/pidusage": "^2.0.5",
90
90
  "uuid": "^11.1.1",
91
+ "@peerbit/indexer-simple": "1.2.17",
91
92
  "@peerbit/any-store-rust": "0.1.7",
93
+ "peerbit": "5.4.0",
92
94
  "@peerbit/indexer-rust": "1.0.14",
93
- "@peerbit/indexer-simple": "1.2.17",
94
- "@peerbit/test-utils": "3.1.39",
95
- "peerbit": "5.4.0"
95
+ "@peerbit/test-utils": "3.1.39"
96
96
  },
97
97
  "repository": {
98
98
  "type": "git",
package/src/index.ts CHANGED
@@ -2723,6 +2723,10 @@ export class SharedLog<
2723
2723
  // The epoch tokens are PeerSession objects (src/peer-session.ts); the map
2724
2724
  // itself lives on the registry. Stage-2 of the fence refactor.
2725
2725
  private _peerSessions!: PeerSessionRegistry;
2726
+ // An exact successor session gets at most one unchanged-Full repair. Weak
2727
+ // ownership keeps retired transport generations collectible without an
2728
+ // identity-keyed cleanup path.
2729
+ private _successorFullRepairSessions!: WeakSet<PeerSession>;
2726
2730
  // A superseded removal may be the queue item that actually observed an active
2727
2731
  // replicator. Carry that leave obligation to the transition that ultimately
2728
2732
  // wins, while a winning reconnect clears it without emitting a stale leave.
@@ -4307,6 +4311,7 @@ export class SharedLog<
4307
4311
  // replication-info blocked set (fence B5) alongside the session maps —
4308
4312
  // the legacy inline `new Set()` that sat above moved there.
4309
4313
  this._peerSessions = this.createPeerSessionRegistry();
4314
+ this._successorFullRepairSessions = new WeakSet();
4310
4315
  this._pendingReplicatorLeaveByPeer = new Set();
4311
4316
  this._activeReceiveHandlersByPeer = new Map();
4312
4317
  this._receiveHandlerDrainByPeer = new Map();
@@ -17959,6 +17964,7 @@ export class SharedLog<
17959
17964
  })) > 0;
17960
17965
 
17961
17966
  this._gidPeersHistory = new Map();
17967
+ this._successorFullRepairSessions = new WeakSet();
17962
17968
  this.resetGidPeerHistoryCleanupState();
17963
17969
  const replicationChangeOwnershipLifecycleController =
17964
17970
  this.captureReplicationOwnershipLifecycle();
@@ -20758,6 +20764,7 @@ export class SharedLog<
20758
20764
  this._receiveHandlerDrainByPeer?.clear();
20759
20765
  this._openingSyncCapabilitiesByPeer?.clear();
20760
20766
  this._gidPeersHistory?.clear();
20767
+ this._successorFullRepairSessions = new WeakSet();
20761
20768
  this._peerSyncCapabilities?.clear();
20762
20769
  this._peerSyncCapabilitySessions?.clear();
20763
20770
  this._peerSyncCapabilityTimestamps?.clear();
@@ -24026,6 +24033,10 @@ export class SharedLog<
24026
24033
  ) {
24027
24034
  return;
24028
24035
  }
24036
+ const isFirstSuccessorFull =
24037
+ msg instanceof FullReplicationInfoV2Message &&
24038
+ receiveSession.hasPredecessor &&
24039
+ !this._successorFullRepairSessions.has(receiveSession);
24029
24040
  const admission = this._v2Receive.reserve(msg, {
24030
24041
  from,
24031
24042
  peerSession: receiveSession,
@@ -24059,6 +24070,8 @@ export class SharedLog<
24059
24070
  const exactGate = () =>
24060
24071
  hostGate() && this._v2Receive.isAdmissionCurrent(admission);
24061
24072
  let durableCommitted = false;
24073
+ let committedSuccessorFull = false;
24074
+ let repairUnchangedSuccessorFull = false;
24062
24075
 
24063
24076
  try {
24064
24077
  if (
@@ -24102,6 +24115,12 @@ export class SharedLog<
24102
24115
  ) {
24103
24116
  return;
24104
24117
  }
24118
+ committedSuccessorFull =
24119
+ isFirstSuccessorFull &&
24120
+ msg instanceof FullReplicationInfoV2Message &&
24121
+ msg.segments.length > 0;
24122
+ repairUnchangedSuccessorFull =
24123
+ committedSuccessorFull && result.length === 0;
24105
24124
  } else {
24106
24125
  if (!exactGate()) {
24107
24126
  return;
@@ -24175,6 +24194,30 @@ export class SharedLog<
24175
24194
  if (!hostGate()) {
24176
24195
  return;
24177
24196
  }
24197
+ if (committedSuccessorFull) {
24198
+ if (!exactGate()) {
24199
+ return;
24200
+ }
24201
+ this._successorFullRepairSessions.add(receiveSession);
24202
+ if (repairUnchangedSuccessorFull) {
24203
+ // The unchanged range rows belong to the public-key principal, but
24204
+ // delivery evidence may belong to the superseded process. Reuse the
24205
+ // receipt-driven authoritative repair path and bypass only those stale
24206
+ // hints; do not synthesize public membership or range-change events.
24207
+ this._repairFrontierBypassKnownPeersByMode
24208
+ .get("join-authoritative")
24209
+ ?.add(fromHash);
24210
+ const peers = new Set([fromHash]);
24211
+ this.scheduleRepairSweep(
24212
+ { mode: "join-authoritative", peers },
24213
+ lane.ownershipLifecycleController,
24214
+ );
24215
+ this.scheduleJoinAuthoritativeRepair(
24216
+ peers,
24217
+ lane.ownershipLifecycleController,
24218
+ );
24219
+ }
24220
+ }
24178
24221
  this._liveness.markReplicatorActivity(fromHash);
24179
24222
  // A committed V2 announcement is applied progress: the peer answers,
24180
24223
  // so recovery re-solicitation may restart from the base interval.
@@ -25141,6 +25184,11 @@ export class SharedLog<
25141
25184
  timeout?: number;
25142
25185
  },
25143
25186
  ) {
25187
+ if (this.closed) {
25188
+ throw new ClosedError();
25189
+ }
25190
+ const closeSignal = this._closeController.signal;
25191
+ if (closeSignal.aborted) throw new ClosedError();
25144
25192
  if (options?.signal?.aborted) {
25145
25193
  throw new AbortError();
25146
25194
  }
@@ -25149,6 +25197,9 @@ export class SharedLog<
25149
25197
  const resolvedRoleAge = options?.eager
25150
25198
  ? undefined
25151
25199
  : (options?.roleAge ?? (await this.getDefaultMinRoleAge()));
25200
+ if (this.closed || closeSignal.aborted) {
25201
+ throw new ClosedError();
25202
+ }
25152
25203
  if (options?.signal?.aborted) {
25153
25204
  throw new AbortError();
25154
25205
  }
@@ -25164,6 +25215,7 @@ export class SharedLog<
25164
25215
  this.events.removeEventListener("replicator:mature", runCheck);
25165
25216
  this.events.removeEventListener("replication:change", runCheck);
25166
25217
  options?.signal?.removeEventListener("abort", onAbort);
25218
+ closeSignal.removeEventListener("abort", onClose);
25167
25219
  if (timer != null) {
25168
25220
  clearTimeout(timer);
25169
25221
  timer = undefined;
@@ -25203,9 +25255,11 @@ export class SharedLog<
25203
25255
  };
25204
25256
 
25205
25257
  const onAbort = () => reject(new AbortError());
25258
+ const onClose = () => reject(new ClosedError());
25206
25259
  if (options?.signal) {
25207
- options.signal.addEventListener("abort", onAbort);
25260
+ options.signal.addEventListener("abort", onAbort, { once: true });
25208
25261
  }
25262
+ closeSignal.addEventListener("abort", onClose, { once: true });
25209
25263
 
25210
25264
  timer = setTimeout(() => {
25211
25265
  reject(
@@ -25248,31 +25302,32 @@ export class SharedLog<
25248
25302
  return;
25249
25303
  }
25250
25304
 
25251
- if (requestAttempts >= maxRequestAttempts) {
25252
- return;
25253
- }
25254
-
25255
- requestAttempts++;
25305
+ if (requestAttempts < maxRequestAttempts) {
25306
+ requestAttempts++;
25256
25307
 
25257
- const peerHash = key.hashcode();
25258
- const peerSession = this._peerSessions.current(peerHash);
25259
- if (peerSession?.phase === "open") {
25260
- this._v2Receive.ensureRequestProgress({
25261
- peerHash,
25262
- peerSession,
25263
- receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
25264
- });
25265
- } else if (peerSession === null || peerSession.phase === "departing") {
25266
- // A peer can be known to routing before its SharedLog topic
25267
- // subscription has been observed. Legacy requests used to bootstrap
25268
- // that case directly; V2 needs an authoritative Subscribe snapshot
25269
- // before it can create a fenced PeerSession and request a Full.
25270
- requestSubscriberSnapshot();
25308
+ const peerHash = key.hashcode();
25309
+ const peerSession = this._peerSessions.current(peerHash);
25310
+ if (peerSession?.phase === "open") {
25311
+ this._v2Receive.ensureRequestProgress({
25312
+ peerHash,
25313
+ peerSession,
25314
+ receiveEpoch: this._peerSessions.receiveEpoch(peerHash),
25315
+ });
25316
+ } else if (peerSession === null || peerSession.phase === "departing") {
25317
+ // A peer can be known to routing before its SharedLog topic
25318
+ // subscription has been observed. Legacy requests used to bootstrap
25319
+ // that case directly; V2 needs an authoritative Subscribe snapshot
25320
+ // before it can create a fenced PeerSession and request a Full.
25321
+ requestSubscriberSnapshot();
25322
+ }
25271
25323
  }
25272
25324
 
25273
- if (requestAttempts < maxRequestAttempts) {
25274
- requestTimer = setTimeout(requestReplicationInfo, requestIntervalMs);
25275
- }
25325
+ // Replication change events are wake hints, not replayable state. Keep
25326
+ // checking the authoritative index after the bounded network recovery
25327
+ // attempts are exhausted so a transiently empty transition read cannot
25328
+ // strand an otherwise-ready waiter until its outer timeout.
25329
+ runCheck();
25330
+ requestTimer = setTimeout(requestReplicationInfo, requestIntervalMs);
25276
25331
  };
25277
25332
 
25278
25333
  const check = async () => {
@@ -25324,8 +25379,9 @@ export class SharedLog<
25324
25379
  // replay a maturity/change event that fires while that read is in flight.
25325
25380
  this.events.addEventListener("replicator:mature", runCheck);
25326
25381
  this.events.addEventListener("replication:change", runCheck);
25327
- requestReplicationInfo();
25328
- runCheck();
25382
+ if (closeSignal.aborted) onClose();
25383
+ else if (options?.signal?.aborted) onAbort();
25384
+ else requestReplicationInfo();
25329
25385
 
25330
25386
  return deferred.promise.finally(clear);
25331
25387
  }