@peerbit/shared-log 13.2.24 → 13.2.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.
Files changed (81) hide show
  1. package/dist/src/checked-prune.d.ts +12 -0
  2. package/dist/src/checked-prune.d.ts.map +1 -1
  3. package/dist/src/checked-prune.js +33 -0
  4. package/dist/src/checked-prune.js.map +1 -1
  5. package/dist/src/coordinate-persistence.d.ts +211 -0
  6. package/dist/src/coordinate-persistence.d.ts.map +1 -0
  7. package/dist/src/coordinate-persistence.js +1133 -0
  8. package/dist/src/coordinate-persistence.js.map +1 -0
  9. package/dist/src/errors.d.ts +1 -0
  10. package/dist/src/errors.d.ts.map +1 -1
  11. package/dist/src/errors.js +19 -0
  12. package/dist/src/errors.js.map +1 -1
  13. package/dist/src/index.d.ts +127 -133
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/index.js +1400 -4269
  16. package/dist/src/index.js.map +1 -1
  17. package/dist/src/instance-lifecycle.d.ts +117 -0
  18. package/dist/src/instance-lifecycle.d.ts.map +1 -0
  19. package/dist/src/instance-lifecycle.js +254 -0
  20. package/dist/src/instance-lifecycle.js.map +1 -0
  21. package/dist/src/join-warmup.d.ts +86 -0
  22. package/dist/src/join-warmup.d.ts.map +1 -0
  23. package/dist/src/join-warmup.js +351 -0
  24. package/dist/src/join-warmup.js.map +1 -0
  25. package/dist/src/native-write-through-block-store.d.ts +123 -0
  26. package/dist/src/native-write-through-block-store.d.ts.map +1 -0
  27. package/dist/src/native-write-through-block-store.js +989 -0
  28. package/dist/src/native-write-through-block-store.js.map +1 -0
  29. package/dist/src/peer-session.d.ts +117 -0
  30. package/dist/src/peer-session.d.ts.map +1 -0
  31. package/dist/src/peer-session.js +259 -0
  32. package/dist/src/peer-session.js.map +1 -0
  33. package/dist/src/replication-announcement.d.ts +116 -0
  34. package/dist/src/replication-announcement.d.ts.map +1 -0
  35. package/dist/src/replication-announcement.js +515 -0
  36. package/dist/src/replication-announcement.js.map +1 -0
  37. package/dist/src/replicator-liveness.d.ts +59 -0
  38. package/dist/src/replicator-liveness.d.ts.map +1 -0
  39. package/dist/src/replicator-liveness.js +304 -0
  40. package/dist/src/replicator-liveness.js.map +1 -0
  41. package/dist/src/sync/dispatch-lifecycle.d.ts +48 -0
  42. package/dist/src/sync/dispatch-lifecycle.d.ts.map +1 -0
  43. package/dist/src/sync/dispatch-lifecycle.js +141 -0
  44. package/dist/src/sync/dispatch-lifecycle.js.map +1 -0
  45. package/dist/src/sync/factory.d.ts +29 -0
  46. package/dist/src/sync/factory.d.ts.map +1 -0
  47. package/dist/src/sync/factory.js +87 -0
  48. package/dist/src/sync/factory.js.map +1 -0
  49. package/dist/src/sync/pending-sync-store.d.ts +105 -0
  50. package/dist/src/sync/pending-sync-store.d.ts.map +1 -0
  51. package/dist/src/sync/pending-sync-store.js +877 -0
  52. package/dist/src/sync/pending-sync-store.js.map +1 -0
  53. package/dist/src/sync/rateless-iblt.d.ts +4 -1
  54. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  55. package/dist/src/sync/rateless-iblt.js +102 -106
  56. package/dist/src/sync/rateless-iblt.js.map +1 -1
  57. package/dist/src/sync/simple.d.ts +10 -41
  58. package/dist/src/sync/simple.d.ts.map +1 -1
  59. package/dist/src/sync/simple.js +259 -952
  60. package/dist/src/sync/simple.js.map +1 -1
  61. package/dist/src/sync/sync-peer-state.d.ts +16 -0
  62. package/dist/src/sync/sync-peer-state.d.ts.map +1 -0
  63. package/dist/src/sync/sync-peer-state.js +82 -0
  64. package/dist/src/sync/sync-peer-state.js.map +1 -0
  65. package/package.json +9 -9
  66. package/src/checked-prune.ts +35 -0
  67. package/src/coordinate-persistence.ts +1796 -0
  68. package/src/errors.ts +21 -0
  69. package/src/index.ts +4014 -7858
  70. package/src/instance-lifecycle.ts +351 -0
  71. package/src/join-warmup.ts +507 -0
  72. package/src/native-write-through-block-store.ts +1170 -0
  73. package/src/peer-session.ts +336 -0
  74. package/src/replication-announcement.ts +744 -0
  75. package/src/replicator-liveness.ts +439 -0
  76. package/src/sync/dispatch-lifecycle.ts +230 -0
  77. package/src/sync/factory.ts +154 -0
  78. package/src/sync/pending-sync-store.ts +1085 -0
  79. package/src/sync/rateless-iblt.ts +122 -132
  80. package/src/sync/simple.ts +334 -1144
  81. package/src/sync/sync-peer-state.ts +107 -0
@@ -0,0 +1,877 @@
1
+ // The keyed record store for pending simple-sync claims (stage-4 sync
2
+ // unification). One record per retained key replaces the former lockstep
3
+ // map family (claimants, claimant indexes, round-robin cursors and key
4
+ // expiry nodes); the expiry heap holds the records themselves, unioned with
5
+ // admission-reservation expiry nodes. The public queue maps survive as the
6
+ // store's physical indexes — the SAME Map instances tests and integrations
7
+ // have always seeded and observed — and directly seeded keys hydrate into
8
+ // records on first access, exactly like the former defensive branches.
9
+ //
10
+ // Lifetimes: a record exists while its key is retained (one add path:
11
+ // addPendingSyncClaim; unified removal: clearSyncProcessKey /
12
+ // removePendingSyncClaim / TTL expiry / close). Admission reservations keep
13
+ // their quota charged until their originating lookup settles — invalidation
14
+ // (expiry/disconnect/close) deliberately does not return the quota early.
15
+ // Late coordinate-to-hash cache fills are discovered incrementally. Keep this
16
+ // independent of retained queue size so an empty or repeated request cannot
17
+ // force an O(global pending keys) reverse-alias rebuild.
18
+ const MAX_PENDING_SIMPLE_SYNC_ALIAS_REFRESH_PER_MESSAGE = 128;
19
+ export const QUEUED_SYNC_ALIAS_REFRESH_PENDING = Symbol("queued-sync-alias-refresh-pending");
20
+ // This is an absolute first-seen lifetime. Repeated claims and additional peers
21
+ // deliberately do not slide the deadline.
22
+ export const PENDING_SIMPLE_SYNC_KEY_TTL_MS = 60_000;
23
+ export class PendingSyncStore {
24
+ deps;
25
+ // map of hash to public keys that we can ask for entries. The public
26
+ // legacy view instances: the same Maps the synchronizer has always
27
+ // exposed, kept as the store's physical indexes.
28
+ syncInFlightQueue;
29
+ syncInFlightQueueInverted;
30
+ syncInFlightQueueExpiresAt;
31
+ records;
32
+ pendingSyncExpiryHeap;
33
+ syncInFlightQueueExpiryTimer;
34
+ pendingSyncAdmissionExpiryNodes;
35
+ syncInFlightQueuedCoordinates;
36
+ syncInFlightQueuedHashByCoordinate;
37
+ syncInFlightQueuedCoordinatesByHash;
38
+ syncInFlightQueuedCoordinateRefreshIterator;
39
+ pendingSyncClaimCount;
40
+ pendingSyncAdmissionCount;
41
+ pendingSyncActiveAdmissionReservations;
42
+ pendingSyncAdmissionCountByPeer;
43
+ pendingSyncAdmissionIdentitiesByPeer;
44
+ pendingSyncAdmissionReservations;
45
+ pendingSyncAdmissionReservationsByPeer;
46
+ pendingSyncAdmissionReservationsByIdentity;
47
+ // map of hash to public keys that we have asked for entries
48
+ syncInFlight;
49
+ syncInFlightTargetsByKey;
50
+ constructor(deps) {
51
+ this.deps = deps;
52
+ this.syncInFlightQueue = new Map();
53
+ this.syncInFlightQueueInverted = new Map();
54
+ this.syncInFlightQueueExpiresAt = new Map();
55
+ this.records = new Map();
56
+ this.pendingSyncExpiryHeap = [];
57
+ this.pendingSyncAdmissionExpiryNodes = new Map();
58
+ this.syncInFlightQueuedCoordinates = new Set();
59
+ this.syncInFlightQueuedHashByCoordinate = new Map();
60
+ this.syncInFlightQueuedCoordinatesByHash = new Map();
61
+ this.pendingSyncClaimCount = 0;
62
+ this.pendingSyncAdmissionCount = 0;
63
+ this.pendingSyncActiveAdmissionReservations = 0;
64
+ this.pendingSyncAdmissionCountByPeer = new Map();
65
+ this.pendingSyncAdmissionIdentitiesByPeer = new Map();
66
+ this.pendingSyncAdmissionReservations = new Set();
67
+ this.pendingSyncAdmissionReservationsByPeer = new Map();
68
+ this.pendingSyncAdmissionReservationsByIdentity = new Map();
69
+ this.syncInFlight = new Map();
70
+ this.syncInFlightTargetsByKey = new Map();
71
+ }
72
+ // Defensive compatibility for callers/tests that seed the public queue
73
+ // maps directly. Internally every retained key gets its record at
74
+ // creation; a seeded key hydrates here (counting its claimants) and never
75
+ // enters the expiry heap, exactly like the former hydration branches.
76
+ hydrateRecordFromQueue(key, peers) {
77
+ const claimants = new Set(peers.map((peer) => peer.hashcode()));
78
+ const claimantIndexes = new Map();
79
+ for (let index = 0; index < peers.length; index += 1) {
80
+ claimantIndexes.set(peers[index].hashcode(), index);
81
+ }
82
+ const record = {
83
+ kind: "key",
84
+ key,
85
+ peers,
86
+ claimants,
87
+ claimantIndexes,
88
+ rrCursor: 0,
89
+ expiresAt: this.syncInFlightQueueExpiresAt.get(key) ?? Number.POSITIVE_INFINITY,
90
+ heapIndex: -1,
91
+ };
92
+ this.records.set(key, record);
93
+ this.pendingSyncClaimCount += claimants.size;
94
+ return record;
95
+ }
96
+ getPendingSyncKeyIdentity(key) {
97
+ if (typeof key === "string") {
98
+ return key;
99
+ }
100
+ const hash = this.deps.coordinateToHash.get(key);
101
+ return hash ?? key;
102
+ }
103
+ removeQueuedSyncCoordinateAlias(key) {
104
+ this.syncInFlightQueuedCoordinates.delete(key);
105
+ if (this.syncInFlightQueuedCoordinates.size === 0) {
106
+ this.syncInFlightQueuedCoordinateRefreshIterator = undefined;
107
+ }
108
+ const previousHash = this.syncInFlightQueuedHashByCoordinate.get(key);
109
+ this.syncInFlightQueuedHashByCoordinate.delete(key);
110
+ if (previousHash != null) {
111
+ const coordinates = this.syncInFlightQueuedCoordinatesByHash.get(previousHash);
112
+ coordinates?.delete(key);
113
+ if (coordinates?.size === 0) {
114
+ this.syncInFlightQueuedCoordinatesByHash.delete(previousHash);
115
+ }
116
+ }
117
+ }
118
+ refreshQueuedSyncCoordinateAlias(key) {
119
+ if (!this.syncInFlightQueue.has(key)) {
120
+ this.removeQueuedSyncCoordinateAlias(key);
121
+ return;
122
+ }
123
+ this.syncInFlightQueuedCoordinates.add(key);
124
+ const hash = this.deps.coordinateToHash.get(key) ?? undefined;
125
+ const previousHash = this.syncInFlightQueuedHashByCoordinate.get(key);
126
+ if (previousHash !== hash) {
127
+ if (previousHash != null) {
128
+ const coordinates = this.syncInFlightQueuedCoordinatesByHash.get(previousHash);
129
+ coordinates?.delete(key);
130
+ if (coordinates?.size === 0) {
131
+ this.syncInFlightQueuedCoordinatesByHash.delete(previousHash);
132
+ }
133
+ }
134
+ if (hash == null) {
135
+ this.syncInFlightQueuedHashByCoordinate.delete(key);
136
+ }
137
+ else {
138
+ this.syncInFlightQueuedHashByCoordinate.set(key, hash);
139
+ }
140
+ }
141
+ if (hash != null) {
142
+ let coordinates = this.syncInFlightQueuedCoordinatesByHash.get(hash);
143
+ if (!coordinates) {
144
+ coordinates = new Set();
145
+ this.syncInFlightQueuedCoordinatesByHash.set(hash, coordinates);
146
+ }
147
+ coordinates.add(key);
148
+ this.reconcileQueuedSyncCoordinateAlias(key, hash);
149
+ }
150
+ }
151
+ reconcileQueuedSyncCoordinateAlias(coordinate, hash) {
152
+ const now = Date.now();
153
+ const coordinateExpiresAt = this.syncInFlightQueueExpiresAt.get(coordinate);
154
+ if (coordinateExpiresAt != null && coordinateExpiresAt <= now) {
155
+ this.clearSyncProcessKey(coordinate);
156
+ return;
157
+ }
158
+ const hashExpiresAt = this.syncInFlightQueueExpiresAt.get(hash);
159
+ if (hashExpiresAt != null && hashExpiresAt <= now) {
160
+ this.clearSyncProcessKey(hash);
161
+ return;
162
+ }
163
+ const hashClaimants = this.syncInFlightQueue.get(hash);
164
+ if (!hashClaimants || !this.syncInFlightQueue.has(coordinate)) {
165
+ return;
166
+ }
167
+ const expiresAt = Math.min(this.syncInFlightQueueExpiresAt.get(coordinate) ?? Infinity, this.syncInFlightQueueExpiresAt.get(hash) ?? Infinity);
168
+ for (const claimant of [...hashClaimants]) {
169
+ this.addPendingSyncClaim(coordinate, claimant, expiresAt);
170
+ }
171
+ if (Number.isFinite(expiresAt)) {
172
+ this.movePendingSyncKeyExpiryEarlier(coordinate, expiresAt);
173
+ }
174
+ for (const target of [...(this.syncInFlightTargetsByKey.get(hash) ?? [])]) {
175
+ const state = this.syncInFlight.get(target)?.get(hash);
176
+ if (state) {
177
+ this.setSyncInFlightTargetKey(target, coordinate, state.timestamp);
178
+ }
179
+ }
180
+ this.clearSyncProcessKey(hash);
181
+ }
182
+ refreshQueuedSyncCoordinateAliases() {
183
+ if (this.syncInFlightQueuedCoordinates.size === 0 &&
184
+ this.records.size < this.syncInFlightQueue.size) {
185
+ // Defensive compatibility for callers/tests that seed the public queue
186
+ // directly. Keep hydration bounded; internal writes register coordinates
187
+ // when the key is first admitted.
188
+ let inspected = 0;
189
+ for (const key of this.syncInFlightQueue.keys()) {
190
+ if (typeof key === "bigint") {
191
+ this.syncInFlightQueuedCoordinates.add(key);
192
+ }
193
+ inspected += 1;
194
+ if (inspected >= MAX_PENDING_SIMPLE_SYNC_ALIAS_REFRESH_PER_MESSAGE) {
195
+ break;
196
+ }
197
+ }
198
+ }
199
+ if (this.syncInFlightQueuedCoordinates.size === 0) {
200
+ this.syncInFlightQueuedCoordinateRefreshIterator = undefined;
201
+ return;
202
+ }
203
+ this.syncInFlightQueuedCoordinateRefreshIterator ??=
204
+ this.syncInFlightQueuedCoordinates.values();
205
+ for (let refreshed = 0; refreshed < MAX_PENDING_SIMPLE_SYNC_ALIAS_REFRESH_PER_MESSAGE; refreshed += 1) {
206
+ const next = this.syncInFlightQueuedCoordinateRefreshIterator.next();
207
+ if (next.done) {
208
+ this.syncInFlightQueuedCoordinateRefreshIterator = undefined;
209
+ break;
210
+ }
211
+ this.refreshQueuedSyncCoordinateAlias(next.value);
212
+ }
213
+ }
214
+ getQueuedSyncKeyForAdmission(key) {
215
+ const getValidQueuedKey = (candidate) => {
216
+ if (!this.syncInFlightQueue.has(candidate)) {
217
+ return undefined;
218
+ }
219
+ const expiresAt = this.syncInFlightQueueExpiresAt.get(candidate);
220
+ if (expiresAt != null && expiresAt <= Date.now()) {
221
+ this.clearSyncProcessKey(candidate);
222
+ return undefined;
223
+ }
224
+ return candidate;
225
+ };
226
+ if (getValidQueuedKey(key) != null) {
227
+ if (typeof key === "bigint") {
228
+ this.refreshQueuedSyncCoordinateAlias(key);
229
+ return getValidQueuedKey(key);
230
+ }
231
+ return key;
232
+ }
233
+ if (typeof key === "string") {
234
+ const aliases = this.syncInFlightQueuedCoordinatesByHash.get(key);
235
+ if (aliases) {
236
+ let inspected = 0;
237
+ for (const alias of aliases) {
238
+ if (inspected >= MAX_PENDING_SIMPLE_SYNC_ALIAS_REFRESH_PER_MESSAGE) {
239
+ return QUEUED_SYNC_ALIAS_REFRESH_PENDING;
240
+ }
241
+ inspected += 1;
242
+ this.refreshQueuedSyncCoordinateAlias(alias);
243
+ if (this.syncInFlightQueuedCoordinatesByHash.get(key)?.has(alias) ===
244
+ true) {
245
+ const validAlias = getValidQueuedKey(alias);
246
+ if (validAlias != null) {
247
+ return validAlias;
248
+ }
249
+ }
250
+ }
251
+ if ((this.syncInFlightQueuedCoordinatesByHash.get(key)?.size ?? 0) > 0) {
252
+ return QUEUED_SYNC_ALIAS_REFRESH_PENDING;
253
+ }
254
+ }
255
+ return undefined;
256
+ }
257
+ const hash = this.deps.coordinateToHash.get(key);
258
+ return hash != null ? getValidQueuedKey(hash) : undefined;
259
+ }
260
+ addPendingSyncClaim(key, from, expiresAt) {
261
+ const fromHash = from.hashcode();
262
+ let peers = this.syncInFlightQueue.get(key);
263
+ let record = this.records.get(key);
264
+ if (!peers) {
265
+ peers = [];
266
+ this.syncInFlightQueue.set(key, peers);
267
+ const deadline = expiresAt ?? Date.now() + PENDING_SIMPLE_SYNC_KEY_TTL_MS;
268
+ this.syncInFlightQueueExpiresAt.set(key, deadline);
269
+ record = {
270
+ kind: "key",
271
+ key,
272
+ peers,
273
+ claimants: new Set(),
274
+ claimantIndexes: new Map(),
275
+ rrCursor: 0,
276
+ expiresAt: deadline,
277
+ heapIndex: -1,
278
+ };
279
+ this.records.set(key, record);
280
+ this.pushPendingSyncExpiry(record);
281
+ if (typeof key === "bigint") {
282
+ this.refreshQueuedSyncCoordinateAlias(key);
283
+ }
284
+ }
285
+ else if (!record) {
286
+ record = this.hydrateRecordFromQueue(key, peers);
287
+ }
288
+ if (record.claimants.has(fromHash)) {
289
+ return false;
290
+ }
291
+ record.claimantIndexes.set(fromHash, peers.length);
292
+ peers.push(from);
293
+ record.claimants.add(fromHash);
294
+ let inverted = this.syncInFlightQueueInverted.get(fromHash);
295
+ if (!inverted) {
296
+ inverted = new Set();
297
+ this.syncInFlightQueueInverted.set(fromHash, inverted);
298
+ }
299
+ inverted.add(key);
300
+ this.pendingSyncClaimCount += 1;
301
+ this.schedulePendingSyncKeyExpiry();
302
+ return true;
303
+ }
304
+ hasPendingSyncClaim(key, peer) {
305
+ const record = this.records.get(key);
306
+ if (record) {
307
+ return record.claimants.has(peer);
308
+ }
309
+ const peers = this.syncInFlightQueue.get(key);
310
+ if (!peers) {
311
+ return false;
312
+ }
313
+ return this.hydrateRecordFromQueue(key, peers).claimants.has(peer);
314
+ }
315
+ filterDispatchablePendingSyncClaims(keys, peer, epoch) {
316
+ if (!this.deps.isDispatchEpochCurrent(peer, epoch)) {
317
+ return [];
318
+ }
319
+ const now = Date.now();
320
+ const dispatchable = [];
321
+ for (const key of keys) {
322
+ const expiresAt = this.syncInFlightQueueExpiresAt.get(key);
323
+ if (expiresAt != null && expiresAt <= now) {
324
+ this.clearSyncProcessKey(key);
325
+ continue;
326
+ }
327
+ if (this.syncInFlightQueue.has(key) &&
328
+ this.hasPendingSyncClaim(key, peer)) {
329
+ dispatchable.push(key);
330
+ }
331
+ }
332
+ return dispatchable;
333
+ }
334
+ getRoundRobinCursor(key) {
335
+ return this.records.get(key)?.rrCursor ?? 0;
336
+ }
337
+ setRoundRobinCursor(key, cursor) {
338
+ const peers = this.syncInFlightQueue.get(key);
339
+ if (!peers) {
340
+ return;
341
+ }
342
+ const record = this.records.get(key) ?? this.hydrateRecordFromQueue(key, peers);
343
+ record.rrCursor = cursor;
344
+ }
345
+ reservePendingSyncAdmission(peer, identities) {
346
+ const count = identities.length;
347
+ if (count <= 0) {
348
+ return undefined;
349
+ }
350
+ const reservation = {
351
+ peer,
352
+ remaining: count,
353
+ active: true,
354
+ released: false,
355
+ expiresAt: Date.now() + PENDING_SIMPLE_SYNC_KEY_TTL_MS,
356
+ identities: new Set(identities),
357
+ retainedSettled: 0,
358
+ };
359
+ this.pendingSyncAdmissionReservations.add(reservation);
360
+ let peerReservations = this.pendingSyncAdmissionReservationsByPeer.get(peer);
361
+ if (!peerReservations) {
362
+ peerReservations = new Set();
363
+ this.pendingSyncAdmissionReservationsByPeer.set(peer, peerReservations);
364
+ }
365
+ peerReservations.add(reservation);
366
+ this.pendingSyncActiveAdmissionReservations += 1;
367
+ this.pendingSyncAdmissionCount += count;
368
+ this.pendingSyncAdmissionCountByPeer.set(peer, (this.pendingSyncAdmissionCountByPeer.get(peer) ?? 0) + count);
369
+ let reservedIdentities = this.pendingSyncAdmissionIdentitiesByPeer.get(peer);
370
+ if (!reservedIdentities) {
371
+ reservedIdentities = new Set();
372
+ this.pendingSyncAdmissionIdentitiesByPeer.set(peer, reservedIdentities);
373
+ }
374
+ for (const identity of identities) {
375
+ reservedIdentities.add(identity);
376
+ let reservationsForIdentity = this.pendingSyncAdmissionReservationsByIdentity.get(identity);
377
+ if (!reservationsForIdentity) {
378
+ reservationsForIdentity = new Set();
379
+ this.pendingSyncAdmissionReservationsByIdentity.set(identity, reservationsForIdentity);
380
+ }
381
+ reservationsForIdentity.add(reservation);
382
+ }
383
+ const expiryNode = {
384
+ kind: "admission",
385
+ reservation,
386
+ expiresAt: reservation.expiresAt,
387
+ heapIndex: -1,
388
+ };
389
+ this.pendingSyncAdmissionExpiryNodes.set(reservation, expiryNode);
390
+ this.pushPendingSyncExpiry(expiryNode);
391
+ this.schedulePendingSyncKeyExpiry();
392
+ return reservation;
393
+ }
394
+ removePendingSyncAdmissionIdentity(reservation, identity, options) {
395
+ if (!reservation.identities.delete(identity)) {
396
+ return false;
397
+ }
398
+ if (options?.retainQuota === true) {
399
+ reservation.retainedSettled += 1;
400
+ }
401
+ else {
402
+ reservation.remaining -= 1;
403
+ this.pendingSyncAdmissionCount -= 1;
404
+ const peerCount = (this.pendingSyncAdmissionCountByPeer.get(reservation.peer) ?? 0) - 1;
405
+ if (peerCount === 0) {
406
+ this.pendingSyncAdmissionCountByPeer.delete(reservation.peer);
407
+ }
408
+ else {
409
+ this.pendingSyncAdmissionCountByPeer.set(reservation.peer, peerCount);
410
+ }
411
+ }
412
+ const reservedIdentities = this.pendingSyncAdmissionIdentitiesByPeer.get(reservation.peer);
413
+ reservedIdentities?.delete(identity);
414
+ if (reservedIdentities?.size === 0) {
415
+ this.pendingSyncAdmissionIdentitiesByPeer.delete(reservation.peer);
416
+ }
417
+ const reservationsForIdentity = this.pendingSyncAdmissionReservationsByIdentity.get(identity);
418
+ reservationsForIdentity?.delete(reservation);
419
+ if (reservationsForIdentity?.size === 0) {
420
+ this.pendingSyncAdmissionReservationsByIdentity.delete(identity);
421
+ }
422
+ return true;
423
+ }
424
+ clearPendingSyncAdmissionIdentity(identity) {
425
+ const reservations = this.pendingSyncAdmissionReservationsByIdentity.get(identity);
426
+ if (!reservations) {
427
+ return;
428
+ }
429
+ for (const reservation of [...reservations]) {
430
+ this.removePendingSyncAdmissionIdentity(reservation, identity, {
431
+ retainQuota: true,
432
+ });
433
+ }
434
+ }
435
+ consumePendingSyncAdmission(reservation, identity) {
436
+ if (!reservation.active || reservation.expiresAt <= Date.now()) {
437
+ if (reservation.expiresAt <= Date.now()) {
438
+ this.invalidatePendingSyncAdmission(reservation);
439
+ }
440
+ return "invalid";
441
+ }
442
+ if (!reservation.identities.has(identity)) {
443
+ return "settled";
444
+ }
445
+ this.removePendingSyncAdmissionIdentity(reservation, identity);
446
+ if (reservation.remaining === 0) {
447
+ this.removePendingSyncAdmissionExpiry(reservation);
448
+ reservation.active = false;
449
+ reservation.released = true;
450
+ this.pendingSyncActiveAdmissionReservations -= 1;
451
+ this.pendingSyncAdmissionReservations.delete(reservation);
452
+ const peerReservations = this.pendingSyncAdmissionReservationsByPeer.get(reservation.peer);
453
+ peerReservations?.delete(reservation);
454
+ if (peerReservations?.size === 0) {
455
+ this.pendingSyncAdmissionReservationsByPeer.delete(reservation.peer);
456
+ }
457
+ this.clearPendingSyncExpiryTimerIfIdle();
458
+ }
459
+ return "consumed";
460
+ }
461
+ transferPendingSyncAdmissionIdentity(peer, identity) {
462
+ const reservations = this.pendingSyncAdmissionReservationsByIdentity.get(identity);
463
+ if (!reservations) {
464
+ return undefined;
465
+ }
466
+ for (const reservation of reservations) {
467
+ if (reservation.peer !== peer ||
468
+ reservation.released ||
469
+ reservation.expiresAt <= Date.now() ||
470
+ !this.removePendingSyncAdmissionIdentity(reservation, identity, {
471
+ retainQuota: true,
472
+ })) {
473
+ continue;
474
+ }
475
+ // The original resolver may be non-abortable and still retains its input
476
+ // arrays. Keep that reservation charged until its queueSync finally
477
+ // settles; the queued claim is counted separately and can disappear
478
+ // without returning the resolver's quota early.
479
+ return reservation.expiresAt;
480
+ }
481
+ return undefined;
482
+ }
483
+ invalidatePendingSyncAdmission(reservation) {
484
+ if (!reservation || reservation.released || !reservation.active) {
485
+ return;
486
+ }
487
+ // Expiry/disconnect invalidates late lookup results, but it must not return
488
+ // the quota slot while the underlying storage/index work is still alive.
489
+ // Those lookups are not generally abortable; only queueSync's finally block
490
+ // may release their active-work accounting.
491
+ this.removePendingSyncAdmissionExpiry(reservation);
492
+ reservation.active = false;
493
+ this.pendingSyncActiveAdmissionReservations -= 1;
494
+ this.clearPendingSyncExpiryTimerIfIdle();
495
+ }
496
+ releasePendingSyncAdmission(reservation) {
497
+ if (!reservation || reservation.released) {
498
+ return;
499
+ }
500
+ this.removePendingSyncAdmissionExpiry(reservation);
501
+ for (const identity of [...reservation.identities]) {
502
+ this.removePendingSyncAdmissionIdentity(reservation, identity);
503
+ }
504
+ if (reservation.retainedSettled > 0) {
505
+ const retainedSettled = reservation.retainedSettled;
506
+ reservation.retainedSettled = 0;
507
+ reservation.remaining -= retainedSettled;
508
+ this.pendingSyncAdmissionCount -= retainedSettled;
509
+ const peerCount = (this.pendingSyncAdmissionCountByPeer.get(reservation.peer) ?? 0) -
510
+ retainedSettled;
511
+ if (peerCount === 0) {
512
+ this.pendingSyncAdmissionCountByPeer.delete(reservation.peer);
513
+ }
514
+ else {
515
+ this.pendingSyncAdmissionCountByPeer.set(reservation.peer, peerCount);
516
+ }
517
+ }
518
+ if (reservation.active) {
519
+ this.pendingSyncActiveAdmissionReservations -= 1;
520
+ }
521
+ reservation.active = false;
522
+ reservation.released = true;
523
+ this.pendingSyncAdmissionReservations.delete(reservation);
524
+ const peerReservations = this.pendingSyncAdmissionReservationsByPeer.get(reservation.peer);
525
+ peerReservations?.delete(reservation);
526
+ if (peerReservations?.size === 0) {
527
+ this.pendingSyncAdmissionReservationsByPeer.delete(reservation.peer);
528
+ }
529
+ this.clearPendingSyncExpiryTimerIfIdle();
530
+ }
531
+ clearPendingSyncAdmissions(peer) {
532
+ const reservations = peer == null
533
+ ? this.pendingSyncAdmissionReservations
534
+ : this.pendingSyncAdmissionReservationsByPeer.get(peer);
535
+ if (!reservations) {
536
+ return;
537
+ }
538
+ for (const reservation of [...reservations]) {
539
+ this.invalidatePendingSyncAdmission(reservation);
540
+ }
541
+ }
542
+ swapPendingSyncExpiry(left, right) {
543
+ const leftNode = this.pendingSyncExpiryHeap[left];
544
+ const rightNode = this.pendingSyncExpiryHeap[right];
545
+ this.pendingSyncExpiryHeap[left] = rightNode;
546
+ this.pendingSyncExpiryHeap[right] = leftNode;
547
+ rightNode.heapIndex = left;
548
+ leftNode.heapIndex = right;
549
+ }
550
+ pushPendingSyncExpiry(node) {
551
+ node.heapIndex = this.pendingSyncExpiryHeap.length;
552
+ this.pendingSyncExpiryHeap.push(node);
553
+ let index = node.heapIndex;
554
+ while (index > 0) {
555
+ const parent = Math.floor((index - 1) / 2);
556
+ if (this.pendingSyncExpiryHeap[parent].expiresAt <=
557
+ this.pendingSyncExpiryHeap[index].expiresAt) {
558
+ break;
559
+ }
560
+ this.swapPendingSyncExpiry(parent, index);
561
+ index = parent;
562
+ }
563
+ }
564
+ removePendingSyncExpiry(node) {
565
+ const index = node.heapIndex;
566
+ if (index < 0 ||
567
+ index >= this.pendingSyncExpiryHeap.length ||
568
+ this.pendingSyncExpiryHeap[index] !== node) {
569
+ return;
570
+ }
571
+ const last = this.pendingSyncExpiryHeap.pop();
572
+ node.heapIndex = -1;
573
+ if (index >= this.pendingSyncExpiryHeap.length) {
574
+ return;
575
+ }
576
+ this.pendingSyncExpiryHeap[index] = last;
577
+ last.heapIndex = index;
578
+ let current = index;
579
+ while (current > 0) {
580
+ const parent = Math.floor((current - 1) / 2);
581
+ if (this.pendingSyncExpiryHeap[parent].expiresAt <=
582
+ this.pendingSyncExpiryHeap[current].expiresAt) {
583
+ break;
584
+ }
585
+ this.swapPendingSyncExpiry(parent, current);
586
+ current = parent;
587
+ }
588
+ for (;;) {
589
+ const left = current * 2 + 1;
590
+ const right = left + 1;
591
+ let smallest = current;
592
+ if (left < this.pendingSyncExpiryHeap.length &&
593
+ this.pendingSyncExpiryHeap[left].expiresAt <
594
+ this.pendingSyncExpiryHeap[smallest].expiresAt) {
595
+ smallest = left;
596
+ }
597
+ if (right < this.pendingSyncExpiryHeap.length &&
598
+ this.pendingSyncExpiryHeap[right].expiresAt <
599
+ this.pendingSyncExpiryHeap[smallest].expiresAt) {
600
+ smallest = right;
601
+ }
602
+ if (smallest === current) {
603
+ break;
604
+ }
605
+ this.swapPendingSyncExpiry(current, smallest);
606
+ current = smallest;
607
+ }
608
+ }
609
+ movePendingSyncKeyExpiryEarlier(key, expiresAt) {
610
+ const current = this.syncInFlightQueueExpiresAt.get(key);
611
+ if (current == null || current <= expiresAt) {
612
+ return;
613
+ }
614
+ this.syncInFlightQueueExpiresAt.set(key, expiresAt);
615
+ const record = this.records.get(key);
616
+ if (!record) {
617
+ return;
618
+ }
619
+ this.removePendingSyncExpiry(record);
620
+ record.expiresAt = expiresAt;
621
+ this.pushPendingSyncExpiry(record);
622
+ this.schedulePendingSyncKeyExpiry();
623
+ }
624
+ removePendingSyncAdmissionExpiry(reservation) {
625
+ const node = this.pendingSyncAdmissionExpiryNodes.get(reservation);
626
+ if (!node) {
627
+ return;
628
+ }
629
+ this.pendingSyncAdmissionExpiryNodes.delete(reservation);
630
+ this.removePendingSyncExpiry(node);
631
+ }
632
+ expirePendingSyncKeys(now = Date.now()) {
633
+ for (;;) {
634
+ const node = this.pendingSyncExpiryHeap[0];
635
+ if (!node || node.expiresAt > now) {
636
+ break;
637
+ }
638
+ this.removePendingSyncExpiry(node);
639
+ if (node.kind === "key") {
640
+ if (this.records.get(node.key) !== node) {
641
+ continue;
642
+ }
643
+ this.clearSyncProcessKey(node.key);
644
+ }
645
+ else {
646
+ if (this.pendingSyncAdmissionExpiryNodes.get(node.reservation) !== node) {
647
+ continue;
648
+ }
649
+ this.pendingSyncAdmissionExpiryNodes.delete(node.reservation);
650
+ this.invalidatePendingSyncAdmission(node.reservation);
651
+ }
652
+ }
653
+ }
654
+ clearPendingSyncExpiryTimerIfIdle() {
655
+ if (this.pendingSyncExpiryHeap.length === 0 &&
656
+ this.syncInFlightQueueExpiryTimer != null) {
657
+ clearTimeout(this.syncInFlightQueueExpiryTimer);
658
+ this.syncInFlightQueueExpiryTimer = undefined;
659
+ }
660
+ }
661
+ schedulePendingSyncKeyExpiry() {
662
+ if (this.syncInFlightQueueExpiryTimer != null ||
663
+ this.pendingSyncExpiryHeap.length === 0) {
664
+ return;
665
+ }
666
+ const expiresAt = this.pendingSyncExpiryHeap[0].expiresAt;
667
+ this.syncInFlightQueueExpiryTimer = setTimeout(() => {
668
+ this.syncInFlightQueueExpiryTimer = undefined;
669
+ this.expirePendingSyncKeys();
670
+ this.schedulePendingSyncKeyExpiry();
671
+ }, Math.max(0, expiresAt - Date.now()));
672
+ this.syncInFlightQueueExpiryTimer.unref?.();
673
+ }
674
+ hasSyncProcessState() {
675
+ return (this.syncInFlightQueue.size > 0 ||
676
+ this.syncInFlightQueueInverted.size > 0 ||
677
+ this.pendingSyncAdmissionCount > 0 ||
678
+ this.syncInFlight.size > 0);
679
+ }
680
+ clearSyncProcessKey(key) {
681
+ const inflight = this.syncInFlightQueue.get(key);
682
+ const record = this.records.get(key);
683
+ if (inflight) {
684
+ for (const peer of inflight) {
685
+ const map = this.syncInFlightQueueInverted.get(peer.hashcode());
686
+ if (map) {
687
+ map.delete(key);
688
+ if (map.size === 0) {
689
+ this.syncInFlightQueueInverted.delete(peer.hashcode());
690
+ }
691
+ }
692
+ }
693
+ this.pendingSyncClaimCount = Math.max(0, this.pendingSyncClaimCount -
694
+ (record?.claimants.size ?? inflight.length));
695
+ this.syncInFlightQueue.delete(key);
696
+ }
697
+ if (record) {
698
+ this.records.delete(key);
699
+ this.removePendingSyncExpiry(record);
700
+ }
701
+ if (typeof key === "bigint") {
702
+ this.removeQueuedSyncCoordinateAlias(key);
703
+ }
704
+ this.syncInFlightQueueExpiresAt.delete(key);
705
+ this.clearPendingSyncExpiryTimerIfIdle();
706
+ this.clearSyncInFlightKey(key);
707
+ }
708
+ removePendingSyncClaim(key, peer) {
709
+ const inflight = this.syncInFlightQueue.get(key);
710
+ if (!inflight) {
711
+ return;
712
+ }
713
+ const record = this.records.get(key) ?? this.hydrateRecordFromQueue(key, inflight);
714
+ const index = record.claimantIndexes.get(peer);
715
+ if (index == null) {
716
+ return;
717
+ }
718
+ const lastIndex = inflight.length - 1;
719
+ if (index !== lastIndex) {
720
+ const lastClaimant = inflight[lastIndex];
721
+ const lastClaimantHash = lastClaimant.hashcode();
722
+ inflight[index] = lastClaimant;
723
+ record.claimantIndexes.set(lastClaimantHash, index);
724
+ }
725
+ inflight.pop();
726
+ record.claimantIndexes.delete(peer);
727
+ record.claimants.delete(peer);
728
+ this.pendingSyncClaimCount = Math.max(0, this.pendingSyncClaimCount - 1);
729
+ const inverted = this.syncInFlightQueueInverted.get(peer);
730
+ inverted?.delete(key);
731
+ if (inverted?.size === 0) {
732
+ this.syncInFlightQueueInverted.delete(peer);
733
+ }
734
+ if (inflight.length > 0) {
735
+ const cursor = record.rrCursor;
736
+ record.rrCursor =
737
+ cursor === lastIndex
738
+ ? index % inflight.length
739
+ : cursor % inflight.length;
740
+ return;
741
+ }
742
+ this.syncInFlightQueue.delete(key);
743
+ this.records.delete(key);
744
+ if (typeof key === "bigint") {
745
+ this.removeQueuedSyncCoordinateAlias(key);
746
+ }
747
+ this.removePendingSyncExpiry(record);
748
+ this.syncInFlightQueueExpiresAt.delete(key);
749
+ this.clearSyncInFlightKey(key);
750
+ this.clearPendingSyncExpiryTimerIfIdle();
751
+ }
752
+ removeClaimsForPeer(publicKeyHash) {
753
+ const map = this.syncInFlightQueueInverted.get(publicKeyHash);
754
+ if (map) {
755
+ for (const hash of [...map]) {
756
+ this.removePendingSyncClaim(hash, publicKeyHash);
757
+ }
758
+ this.syncInFlightQueueInverted.delete(publicKeyHash);
759
+ }
760
+ }
761
+ removeSyncInFlightTargetKey(peer, key) {
762
+ const map = this.syncInFlight.get(peer);
763
+ if (!map?.delete(key)) {
764
+ return;
765
+ }
766
+ if (map.size === 0) {
767
+ this.syncInFlight.delete(peer);
768
+ }
769
+ const targets = this.syncInFlightTargetsByKey.get(key);
770
+ targets?.delete(peer);
771
+ if (targets?.size === 0) {
772
+ this.syncInFlightTargetsByKey.delete(key);
773
+ }
774
+ }
775
+ setSyncInFlightTargetKey(peer, key, timestamp) {
776
+ let map = this.syncInFlight.get(peer);
777
+ if (!map) {
778
+ map = new Map();
779
+ this.syncInFlight.set(peer, map);
780
+ }
781
+ const existing = map.get(key);
782
+ if (!existing || existing.timestamp < timestamp) {
783
+ map.set(key, { timestamp });
784
+ }
785
+ let targets = this.syncInFlightTargetsByKey.get(key);
786
+ if (!targets) {
787
+ targets = new Set();
788
+ this.syncInFlightTargetsByKey.set(key, targets);
789
+ }
790
+ targets.add(peer);
791
+ }
792
+ clearSyncInFlightTarget(peer) {
793
+ const map = this.syncInFlight.get(peer);
794
+ if (!map) {
795
+ return;
796
+ }
797
+ for (const key of map.keys()) {
798
+ const targets = this.syncInFlightTargetsByKey.get(key);
799
+ targets?.delete(peer);
800
+ if (targets?.size === 0) {
801
+ this.syncInFlightTargetsByKey.delete(key);
802
+ }
803
+ }
804
+ this.syncInFlight.delete(peer);
805
+ }
806
+ clearSyncInFlightKey(key) {
807
+ const targets = this.syncInFlightTargetsByKey.get(key);
808
+ if (!targets) {
809
+ // Defensive compatibility for tests or integrations that seed the public
810
+ // syncInFlight map directly. Internal writes always populate the index.
811
+ for (const [peer, map] of this.syncInFlight) {
812
+ if (map.has(key)) {
813
+ this.removeSyncInFlightTargetKey(peer, key);
814
+ }
815
+ }
816
+ return;
817
+ }
818
+ for (const peer of [...targets]) {
819
+ this.removeSyncInFlightTargetKey(peer, key);
820
+ }
821
+ }
822
+ forEachKnownAlias(hash, callback) {
823
+ callback(hash);
824
+ for (const coordinate of [
825
+ ...(this.syncInFlightQueuedCoordinatesByHash.get(hash) ?? []),
826
+ ]) {
827
+ callback(coordinate);
828
+ }
829
+ }
830
+ clearSyncInFlightForPeerHashes(publicKeyHash, hashes) {
831
+ const map = this.syncInFlight.get(publicKeyHash);
832
+ if (!map || hashes.length === 0) {
833
+ return;
834
+ }
835
+ this.refreshQueuedSyncCoordinateAliases();
836
+ for (const hash of hashes) {
837
+ this.forEachKnownAlias(hash, (key) => this.removeSyncInFlightTargetKey(publicKeyHash, key));
838
+ }
839
+ }
840
+ clearSyncProcess(hash) {
841
+ this.refreshQueuedSyncCoordinateAliases();
842
+ this.forEachKnownAlias(hash, (key) => this.clearSyncProcessKey(key));
843
+ }
844
+ clearSyncProcesses(hashes) {
845
+ if (hashes.length === 0) {
846
+ return;
847
+ }
848
+ this.refreshQueuedSyncCoordinateAliases();
849
+ const keys = new Set();
850
+ for (const hash of hashes) {
851
+ this.forEachKnownAlias(hash, (key) => keys.add(key));
852
+ }
853
+ for (const key of keys) {
854
+ this.clearSyncProcessKey(key);
855
+ }
856
+ }
857
+ clearForClose() {
858
+ this.syncInFlightQueue.clear();
859
+ this.syncInFlightQueueInverted.clear();
860
+ this.syncInFlightQueueExpiresAt.clear();
861
+ this.pendingSyncExpiryHeap.length = 0;
862
+ this.records.clear();
863
+ this.pendingSyncAdmissionExpiryNodes.clear();
864
+ this.syncInFlightQueuedCoordinates.clear();
865
+ this.syncInFlightQueuedHashByCoordinate.clear();
866
+ this.syncInFlightQueuedCoordinatesByHash.clear();
867
+ this.syncInFlightQueuedCoordinateRefreshIterator = undefined;
868
+ this.pendingSyncClaimCount = 0;
869
+ if (this.syncInFlightQueueExpiryTimer != null) {
870
+ clearTimeout(this.syncInFlightQueueExpiryTimer);
871
+ this.syncInFlightQueueExpiryTimer = undefined;
872
+ }
873
+ this.syncInFlight.clear();
874
+ this.syncInFlightTargetsByKey.clear();
875
+ }
876
+ }
877
+ //# sourceMappingURL=pending-sync-store.js.map