@peerbit/pubsub 5.2.10 → 5.3.0

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 (67) hide show
  1. package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.d.ts +3 -0
  2. package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.d.ts.map +1 -0
  3. package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.js +90 -0
  4. package/dist/benchmark/fanout-tree-parent-upgrade-default-ready.js.map +1 -0
  5. package/dist/benchmark/fanout-tree-parent-upgrade-eval.d.ts +2 -0
  6. package/dist/benchmark/fanout-tree-parent-upgrade-eval.d.ts.map +1 -0
  7. package/dist/benchmark/fanout-tree-parent-upgrade-eval.js +712 -0
  8. package/dist/benchmark/fanout-tree-parent-upgrade-eval.js.map +1 -0
  9. package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.d.ts +2 -0
  10. package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.d.ts.map +1 -0
  11. package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.js +1591 -0
  12. package/dist/benchmark/fanout-tree-parent-upgrade-multi-eval.js.map +1 -0
  13. package/dist/benchmark/fanout-tree-parent-upgrade-prepush.d.ts +2 -0
  14. package/dist/benchmark/fanout-tree-parent-upgrade-prepush.d.ts.map +1 -0
  15. package/dist/benchmark/fanout-tree-parent-upgrade-prepush.js +297 -0
  16. package/dist/benchmark/fanout-tree-parent-upgrade-prepush.js.map +1 -0
  17. package/dist/benchmark/fanout-tree-parent-upgrade-preset.d.ts +73 -0
  18. package/dist/benchmark/fanout-tree-parent-upgrade-preset.d.ts.map +1 -0
  19. package/dist/benchmark/fanout-tree-parent-upgrade-preset.js +183 -0
  20. package/dist/benchmark/fanout-tree-parent-upgrade-preset.js.map +1 -0
  21. package/dist/benchmark/fanout-tree-sim-lib.d.ts +101 -0
  22. package/dist/benchmark/fanout-tree-sim-lib.d.ts.map +1 -1
  23. package/dist/benchmark/fanout-tree-sim-lib.js +540 -38
  24. package/dist/benchmark/fanout-tree-sim-lib.js.map +1 -1
  25. package/dist/benchmark/fanout-tree-sim.d.ts +0 -9
  26. package/dist/benchmark/fanout-tree-sim.d.ts.map +1 -1
  27. package/dist/benchmark/fanout-tree-sim.js +369 -28
  28. package/dist/benchmark/fanout-tree-sim.js.map +1 -1
  29. package/dist/benchmark/index.js +20 -0
  30. package/dist/benchmark/index.js.map +1 -1
  31. package/dist/benchmark/pubsub-topic-sim-lib.d.ts +4 -0
  32. package/dist/benchmark/pubsub-topic-sim-lib.d.ts.map +1 -1
  33. package/dist/benchmark/pubsub-topic-sim-lib.js +58 -3
  34. package/dist/benchmark/pubsub-topic-sim-lib.js.map +1 -1
  35. package/dist/benchmark/pubsub-topic-sim.js +18 -0
  36. package/dist/benchmark/pubsub-topic-sim.js.map +1 -1
  37. package/dist/src/debounced-set.d.ts +1 -1
  38. package/dist/src/debounced-set.d.ts.map +1 -1
  39. package/dist/src/debounced-set.js +5 -1
  40. package/dist/src/debounced-set.js.map +1 -1
  41. package/dist/src/fanout-tree-codec.d.ts +330 -0
  42. package/dist/src/fanout-tree-codec.d.ts.map +1 -0
  43. package/dist/src/fanout-tree-codec.js +1209 -0
  44. package/dist/src/fanout-tree-codec.js.map +1 -0
  45. package/dist/src/fanout-tree-parent-upgrade.d.ts +117 -0
  46. package/dist/src/fanout-tree-parent-upgrade.d.ts.map +1 -0
  47. package/dist/src/fanout-tree-parent-upgrade.js +135 -0
  48. package/dist/src/fanout-tree-parent-upgrade.js.map +1 -0
  49. package/dist/src/fanout-tree.d.ts +241 -73
  50. package/dist/src/fanout-tree.d.ts.map +1 -1
  51. package/dist/src/fanout-tree.js +2224 -1050
  52. package/dist/src/fanout-tree.js.map +1 -1
  53. package/dist/src/index.d.ts +57 -0
  54. package/dist/src/index.d.ts.map +1 -1
  55. package/dist/src/index.js +293 -41
  56. package/dist/src/index.js.map +1 -1
  57. package/dist/src/topic-root-control-plane.d.ts +13 -0
  58. package/dist/src/topic-root-control-plane.d.ts.map +1 -1
  59. package/dist/src/topic-root-control-plane.js +45 -0
  60. package/dist/src/topic-root-control-plane.js.map +1 -1
  61. package/package.json +6 -6
  62. package/src/debounced-set.ts +5 -0
  63. package/src/fanout-tree-codec.ts +1580 -0
  64. package/src/fanout-tree-parent-upgrade.ts +353 -0
  65. package/src/fanout-tree.ts +4467 -2204
  66. package/src/index.ts +392 -56
  67. package/src/topic-root-control-plane.ts +48 -0
@@ -0,0 +1,353 @@
1
+ export type ParentUpgradeOptions = {
2
+ parentUpgradeIntervalMs?: number;
3
+ parentUpgradeLeafOnly?: boolean;
4
+ parentUpgradeMinLevelGain?: number;
5
+ parentUpgradeRootMinLevelGain?: number;
6
+ parentUpgradeRootMinSubtreeGain?: number;
7
+ parentUpgradeNonRootMinLevelGain?: number;
8
+ parentUpgradeMinFreeSlots?: number;
9
+ parentUpgradeRootMinFreeSlots?: number;
10
+ parentUpgradeMaxChildLoadRatio?: number;
11
+ parentUpgradeRootMaxChildLoadRatio?: number;
12
+ parentUpgradeCooldownMs?: number;
13
+ parentUpgradeFailedBackoffMinMs?: number;
14
+ parentUpgradeFailedBackoffMaxMs?: number;
15
+ parentUpgradeQuietMs?: number;
16
+ parentUpgradeRepairQuietMs?: number;
17
+ parentUpgradeMaxPerPeer?: number;
18
+ parentUpgradeRepairGuard?: boolean;
19
+ parentUpgradeDataGuard?: boolean;
20
+ parentUpgradeMode?: ParentUpgradeMode;
21
+ parentUpgradeVerifyStaleRootCapacity?: boolean;
22
+ parentUpgradeStaleRootProbeProbability?: number;
23
+ parentProbeTimeoutMs?: number;
24
+ parentProbeMaxPerRound?: number;
25
+ parentProbeMaxLagMessages?: number;
26
+ parentProbeRejectCooldownMs?: number;
27
+ parentProbeRejectCooldownMaxMs?: number;
28
+ parentShadowObserveMs?: number;
29
+ parentShadowMinObservations?: number;
30
+ parentShadowDualPathMs?: number;
31
+ parentShadowDualPathMinMessages?: number;
32
+ };
33
+
34
+ export type ParentUpgradeSkipReason =
35
+ | "leaf"
36
+ | "repair"
37
+ | "data"
38
+ | "cooldown"
39
+ | "quiet"
40
+ | "budget";
41
+
42
+ export type ParentUpgradeMode = "direct" | "probe" | "shadow";
43
+
44
+ export type ParentUpgradePolicy = {
45
+ intervalMs: number;
46
+ leafOnly: boolean;
47
+ minLevelGain: number;
48
+ rootMinLevelGain: number;
49
+ rootMinSubtreeGain: number;
50
+ nonRootMinLevelGain: number;
51
+ minFreeSlots: number;
52
+ rootMinFreeSlots: number;
53
+ maxChildLoadRatio: number;
54
+ rootMaxChildLoadRatio: number;
55
+ staleRootProbeProbability: number;
56
+ cooldownMs: number;
57
+ quietMs: number;
58
+ repairQuietMs: number;
59
+ maxPerPeer: number;
60
+ repairGuard: boolean;
61
+ dataGuard: boolean;
62
+ mode: ParentUpgradeMode;
63
+ verifyStaleRootCapacity: boolean;
64
+ failedBackoff: {
65
+ minMs: number;
66
+ maxMs: number;
67
+ };
68
+ probe: {
69
+ timeoutMs: number;
70
+ maxPerRound: number;
71
+ maxLagMessages: number;
72
+ rejectCooldownMs: number;
73
+ rejectCooldownMaxMs: number;
74
+ };
75
+ shadow: {
76
+ observeMs: number;
77
+ minObservations: number;
78
+ dualPathMs: number;
79
+ dualPathMinMessages: number;
80
+ };
81
+ };
82
+
83
+ export type ParentUpgradeGateState = {
84
+ children: { size: number };
85
+ missingSeqs: { size: number };
86
+ lastRepairSentAt: number;
87
+ endSeqExclusive: number;
88
+ parentUpgradeRetryAfterSeq: number;
89
+ maxSeqSeen: number;
90
+ parentUpgradeCount: number;
91
+ parentUpgradeBackoffUntil: number;
92
+ parentUpgradeLastAt: number;
93
+ lastParentDataAt: number;
94
+ lastParentUpgradeActivityAt?: number;
95
+ };
96
+
97
+ export type ParentUpgradeGateOptions = {
98
+ leafOnly: boolean;
99
+ repairGuard: boolean;
100
+ dataGuard: boolean;
101
+ endedAndComplete: boolean;
102
+ maxPerPeer: number;
103
+ cooldownMs: number;
104
+ quietMs: number;
105
+ repairQuietMs: number;
106
+ now: number;
107
+ };
108
+
109
+ export type ParentUpgradeSkipMetrics = {
110
+ reparentUpgradeSkipLeaf: number;
111
+ reparentUpgradeSkipRepair: number;
112
+ reparentUpgradeSkipData: number;
113
+ reparentUpgradeSkipCooldown: number;
114
+ reparentUpgradeSkipQuiet: number;
115
+ reparentUpgradeSkipBudget: number;
116
+ };
117
+
118
+ export const normalizeParentUpgradePolicy = (
119
+ options: ParentUpgradeOptions,
120
+ ): ParentUpgradePolicy => {
121
+ const intervalMs = Math.max(
122
+ 0,
123
+ Math.floor(options.parentUpgradeIntervalMs ?? 0),
124
+ );
125
+ const minLevelGain = Math.max(
126
+ 1,
127
+ Math.floor(options.parentUpgradeMinLevelGain ?? 1),
128
+ );
129
+ const rootMinLevelGain = Math.max(
130
+ minLevelGain,
131
+ Math.floor(options.parentUpgradeRootMinLevelGain ?? 3),
132
+ );
133
+ const rootMinSubtreeGain = Math.max(
134
+ minLevelGain,
135
+ Math.floor(options.parentUpgradeRootMinSubtreeGain ?? rootMinLevelGain),
136
+ );
137
+ const minFreeSlots = Math.max(
138
+ 0,
139
+ Math.floor(options.parentUpgradeMinFreeSlots ?? 8),
140
+ );
141
+ const maxChildLoadRatioRaw = Number(
142
+ options.parentUpgradeMaxChildLoadRatio ?? 0.5,
143
+ );
144
+ const maxChildLoadRatio = Number.isFinite(maxChildLoadRatioRaw)
145
+ ? Math.max(0, maxChildLoadRatioRaw)
146
+ : 0.5;
147
+ const rootMaxChildLoadRatioRaw = Number(
148
+ options.parentUpgradeRootMaxChildLoadRatio ??
149
+ Math.min(maxChildLoadRatio, 0.4),
150
+ );
151
+ const rootMaxChildLoadRatio = Number.isFinite(rootMaxChildLoadRatioRaw)
152
+ ? Math.max(0, rootMaxChildLoadRatioRaw)
153
+ : Math.min(maxChildLoadRatio, 0.4);
154
+ const staleRootProbeProbabilityRaw = Number(
155
+ options.parentUpgradeStaleRootProbeProbability ?? 0.015625,
156
+ );
157
+ const staleRootProbeProbability = Number.isFinite(
158
+ staleRootProbeProbabilityRaw,
159
+ )
160
+ ? Math.max(0, Math.min(1, staleRootProbeProbabilityRaw))
161
+ : 0.015625;
162
+ const cooldownMs = Math.max(
163
+ 0,
164
+ Math.floor(options.parentUpgradeCooldownMs ?? 5_000),
165
+ );
166
+ const failedBackoffMinMs = Math.max(
167
+ 0,
168
+ Math.floor(options.parentUpgradeFailedBackoffMinMs ?? cooldownMs),
169
+ );
170
+ const probeRejectCooldownMs = Math.max(
171
+ 0,
172
+ Math.floor(options.parentProbeRejectCooldownMs ?? 10_000),
173
+ );
174
+ const quietMs = Math.max(
175
+ 0,
176
+ Math.floor(options.parentUpgradeQuietMs ?? 5_000),
177
+ );
178
+ const mode: ParentUpgradeMode =
179
+ options.parentUpgradeMode === "probe" ||
180
+ options.parentUpgradeMode === "shadow"
181
+ ? options.parentUpgradeMode
182
+ : options.parentUpgradeMode === "direct"
183
+ ? "direct"
184
+ : "shadow";
185
+
186
+ return {
187
+ intervalMs,
188
+ leafOnly: options.parentUpgradeLeafOnly !== false,
189
+ minLevelGain,
190
+ rootMinLevelGain,
191
+ rootMinSubtreeGain,
192
+ nonRootMinLevelGain: Math.max(
193
+ minLevelGain,
194
+ Math.floor(options.parentUpgradeNonRootMinLevelGain ?? 2),
195
+ ),
196
+ minFreeSlots,
197
+ rootMinFreeSlots: Math.max(
198
+ 0,
199
+ Math.floor(options.parentUpgradeRootMinFreeSlots ?? minFreeSlots),
200
+ ),
201
+ maxChildLoadRatio,
202
+ rootMaxChildLoadRatio,
203
+ staleRootProbeProbability,
204
+ cooldownMs,
205
+ quietMs,
206
+ repairQuietMs: Math.max(
207
+ 0,
208
+ Math.floor(options.parentUpgradeRepairQuietMs ?? quietMs),
209
+ ),
210
+ maxPerPeer: Math.max(
211
+ 0,
212
+ Math.floor(options.parentUpgradeMaxPerPeer ?? 2),
213
+ ),
214
+ repairGuard: options.parentUpgradeRepairGuard !== false,
215
+ dataGuard: options.parentUpgradeDataGuard !== false,
216
+ mode,
217
+ verifyStaleRootCapacity:
218
+ options.parentUpgradeVerifyStaleRootCapacity ?? (mode === "shadow"),
219
+ failedBackoff: {
220
+ minMs: failedBackoffMinMs,
221
+ maxMs: Math.max(
222
+ failedBackoffMinMs,
223
+ Math.floor(options.parentUpgradeFailedBackoffMaxMs ?? 60_000),
224
+ ),
225
+ },
226
+ probe: {
227
+ timeoutMs: Math.max(
228
+ 1,
229
+ Math.floor(options.parentProbeTimeoutMs ?? 500),
230
+ ),
231
+ maxPerRound: Math.max(
232
+ 1,
233
+ Math.floor(options.parentProbeMaxPerRound ?? 2),
234
+ ),
235
+ maxLagMessages: Math.max(
236
+ 0,
237
+ Math.floor(options.parentProbeMaxLagMessages ?? 0),
238
+ ),
239
+ rejectCooldownMs: probeRejectCooldownMs,
240
+ rejectCooldownMaxMs: Math.max(
241
+ probeRejectCooldownMs,
242
+ Math.floor(options.parentProbeRejectCooldownMaxMs ?? 60_000),
243
+ ),
244
+ },
245
+ shadow: {
246
+ observeMs: Math.max(
247
+ 0,
248
+ Math.floor(options.parentShadowObserveMs ?? 2_000),
249
+ ),
250
+ minObservations: Math.max(
251
+ 1,
252
+ Math.floor(options.parentShadowMinObservations ?? 2),
253
+ ),
254
+ dualPathMs: Math.max(
255
+ 0,
256
+ Math.floor(
257
+ options.parentShadowDualPathMs ?? (mode === "shadow" ? 5_000 : 0),
258
+ ),
259
+ ),
260
+ dualPathMinMessages: Math.max(
261
+ 1,
262
+ Math.floor(
263
+ options.parentShadowDualPathMinMessages ??
264
+ (mode === "shadow" ? 32 : 1),
265
+ ),
266
+ ),
267
+ },
268
+ };
269
+ };
270
+
271
+ export const evaluateParentUpgradeGate = (
272
+ state: ParentUpgradeGateState,
273
+ options: ParentUpgradeGateOptions,
274
+ ): { run: true } | { run: false; reason: ParentUpgradeSkipReason } => {
275
+ if (options.leafOnly && state.children.size > 0) {
276
+ return { run: false, reason: "leaf" };
277
+ }
278
+ if (options.repairGuard && state.missingSeqs.size > 0) {
279
+ return { run: false, reason: "repair" };
280
+ }
281
+ if (
282
+ options.repairGuard &&
283
+ options.repairQuietMs > 0 &&
284
+ state.lastRepairSentAt > 0 &&
285
+ options.now - state.lastRepairSentAt < options.repairQuietMs
286
+ ) {
287
+ return { run: false, reason: "repair" };
288
+ }
289
+ if (
290
+ options.dataGuard &&
291
+ !(state.endSeqExclusive > 0 && options.endedAndComplete)
292
+ ) {
293
+ return { run: false, reason: "data" };
294
+ }
295
+ if (options.dataGuard && state.parentUpgradeRetryAfterSeq >= 0) {
296
+ if (state.maxSeqSeen <= state.parentUpgradeRetryAfterSeq) {
297
+ return { run: false, reason: "data" };
298
+ }
299
+ state.parentUpgradeRetryAfterSeq = -1;
300
+ }
301
+ if (
302
+ options.maxPerPeer > 0 &&
303
+ state.parentUpgradeCount >= options.maxPerPeer
304
+ ) {
305
+ return { run: false, reason: "budget" };
306
+ }
307
+ if (state.parentUpgradeBackoffUntil > options.now) {
308
+ return { run: false, reason: "cooldown" };
309
+ }
310
+ if (
311
+ options.cooldownMs > 0 &&
312
+ state.parentUpgradeLastAt > 0 &&
313
+ options.now - state.parentUpgradeLastAt < options.cooldownMs
314
+ ) {
315
+ return { run: false, reason: "cooldown" };
316
+ }
317
+ const lastParentUpgradeActivityAt =
318
+ state.lastParentUpgradeActivityAt ?? state.lastParentDataAt;
319
+ if (
320
+ options.quietMs > 0 &&
321
+ lastParentUpgradeActivityAt > 0 &&
322
+ options.now - lastParentUpgradeActivityAt < options.quietMs
323
+ ) {
324
+ return { run: false, reason: "quiet" };
325
+ }
326
+ return { run: true };
327
+ };
328
+
329
+ export const recordParentUpgradeSkip = (
330
+ metrics: ParentUpgradeSkipMetrics,
331
+ reason: ParentUpgradeSkipReason,
332
+ ) => {
333
+ switch (reason) {
334
+ case "leaf":
335
+ metrics.reparentUpgradeSkipLeaf += 1;
336
+ break;
337
+ case "repair":
338
+ metrics.reparentUpgradeSkipRepair += 1;
339
+ break;
340
+ case "data":
341
+ metrics.reparentUpgradeSkipData += 1;
342
+ break;
343
+ case "cooldown":
344
+ metrics.reparentUpgradeSkipCooldown += 1;
345
+ break;
346
+ case "quiet":
347
+ metrics.reparentUpgradeSkipQuiet += 1;
348
+ break;
349
+ case "budget":
350
+ metrics.reparentUpgradeSkipBudget += 1;
351
+ break;
352
+ }
353
+ };