@voltro/plugin-broadcast 0.83.1 → 0.85.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.
- package/CHANGELOG.md +1191 -0
- package/THIRD-PARTY-NOTICES.md +1 -57
- package/dist/index.d.ts +20 -8
- package/dist/index.js +181 -76
- package/package.json +4 -4
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -5,66 +5,10 @@ property of its respective copyright holders and is used under the terms of
|
|
|
5
5
|
its license. This file is provided for attribution; it grants no rights in
|
|
6
6
|
@voltro/plugin-broadcast itself, which is proprietary (see LICENSE).
|
|
7
7
|
|
|
8
|
-
Generated from the resolved runtime dependency closure (
|
|
8
|
+
Generated from the resolved runtime dependency closure (19 packages).
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
## @effect/experimental@0.61.1
|
|
13
|
-
|
|
14
|
-
License: MIT
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
MIT License
|
|
18
|
-
|
|
19
|
-
Copyright (c) 2023-present The Contributors
|
|
20
|
-
|
|
21
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
22
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
23
|
-
in the Software without restriction, including without limitation the rights
|
|
24
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
25
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
26
|
-
furnished to do so, subject to the following conditions:
|
|
27
|
-
|
|
28
|
-
The above copyright notice and this permission notice shall be included in all
|
|
29
|
-
copies or substantial portions of the Software.
|
|
30
|
-
|
|
31
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
37
|
-
SOFTWARE.
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## @effect/sql@0.52.1
|
|
41
|
-
|
|
42
|
-
License: MIT
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
MIT License
|
|
46
|
-
|
|
47
|
-
Copyright (c) 2023-present The Contributors
|
|
48
|
-
|
|
49
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
50
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
51
|
-
in the Software without restriction, including without limitation the rights
|
|
52
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
53
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
54
|
-
furnished to do so, subject to the following conditions:
|
|
55
|
-
|
|
56
|
-
The above copyright notice and this permission notice shall be included in all
|
|
57
|
-
copies or substantial portions of the Software.
|
|
58
|
-
|
|
59
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
60
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
61
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
62
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
63
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
64
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
65
|
-
SOFTWARE.
|
|
66
|
-
```
|
|
67
|
-
|
|
68
12
|
## @ioredis/commands@1.10.0
|
|
69
13
|
|
|
70
14
|
License: MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ChangeEvent } from '@voltro/database';
|
|
|
2
2
|
import { Effect } from 'effect';
|
|
3
3
|
import { Schema } from 'effect';
|
|
4
4
|
import { VoltroPlugin } from '@voltro/protocol';
|
|
5
|
+
import { YieldableError } from 'effect/Cause';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Wire a store to a broadcast provider. Publishes local changes outward
|
|
@@ -40,6 +41,9 @@ export declare interface AttachBroadcastBusOptions {
|
|
|
40
41
|
* embedder) should still say what it lost.
|
|
41
42
|
*/
|
|
42
43
|
readonly onGap?: (gap: BroadcastGap) => void;
|
|
44
|
+
/** Bounded outbound buffering and broker/shutdown deadlines. Defaults:
|
|
45
|
+
* 1024 messages, 8 MiB, 5s publish, 1s shutdown. Overflow signals peer resync. */
|
|
46
|
+
readonly publishQueue?: BroadcastPublishQueueOptions;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
/**
|
|
@@ -76,6 +80,9 @@ export declare type BroadcastChannelKind = 'changes' | 'events' | 'members' | 'p
|
|
|
76
80
|
* into every OTHER replica's store emitter.
|
|
77
81
|
*/
|
|
78
82
|
export declare interface BroadcastEnvelope {
|
|
83
|
+
/** Control delivery: refresh live queries rather than inject `event`.
|
|
84
|
+
* Sent after outbound loss, including when no later mutation occurs. */
|
|
85
|
+
readonly resync?: true;
|
|
79
86
|
readonly origin: string;
|
|
80
87
|
readonly event: ChangeEvent;
|
|
81
88
|
/**
|
|
@@ -132,14 +139,12 @@ export declare interface BroadcastEnvelope {
|
|
|
132
139
|
export declare class BroadcastError extends BroadcastError_base {
|
|
133
140
|
}
|
|
134
141
|
|
|
135
|
-
declare const BroadcastError_base: Schema.
|
|
136
|
-
readonly
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
cause: Schema.optional<typeof Schema.Unknown>;
|
|
142
|
-
}>;
|
|
142
|
+
declare const BroadcastError_base: Schema.Class<BroadcastError, Schema.TaggedStruct<"BroadcastError", {
|
|
143
|
+
readonly provider: Schema.String;
|
|
144
|
+
readonly message: Schema.String;
|
|
145
|
+
readonly transient: Schema.Boolean;
|
|
146
|
+
readonly cause: Schema.optional<Schema.Unknown>;
|
|
147
|
+
}>, YieldableError>;
|
|
143
148
|
|
|
144
149
|
/**
|
|
145
150
|
* A proven — or presumed — hole in what this replica received.
|
|
@@ -274,6 +279,13 @@ export declare interface BroadcastProvider {
|
|
|
274
279
|
/** Provider id understood by `resolveBroadcastProvider`. */
|
|
275
280
|
export declare type BroadcastProviderName = 'redis' | 'nats' | 'memory';
|
|
276
281
|
|
|
282
|
+
export declare interface BroadcastPublishQueueOptions {
|
|
283
|
+
readonly maxMessages?: number;
|
|
284
|
+
readonly maxBytes?: number;
|
|
285
|
+
readonly timeoutMs?: number;
|
|
286
|
+
readonly shutdownTimeoutMs?: number;
|
|
287
|
+
}
|
|
288
|
+
|
|
277
289
|
/** The minimal store surface the bus drives — a subset of `DataStore` so
|
|
278
290
|
* the bus stays decoupled from the concrete store classes. */
|
|
279
291
|
export declare interface BroadcastStore {
|
package/dist/index.js
CHANGED
|
@@ -50,24 +50,28 @@ var i = "voltro", a = (e) => e.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-"
|
|
|
50
50
|
let t = h.get(e);
|
|
51
51
|
return t || (t = { handlers: /* @__PURE__ */ new Map() }, h.set(e, t)), t;
|
|
52
52
|
}, _ = (e) => {
|
|
53
|
-
h.
|
|
53
|
+
h.delete(e);
|
|
54
54
|
}, v = (t = `default-${Math.random().toString(36).slice(2)}`) => {
|
|
55
|
-
let n =
|
|
55
|
+
let n = /* @__PURE__ */ new Set();
|
|
56
56
|
return {
|
|
57
57
|
name: "memory",
|
|
58
|
-
publish: (
|
|
59
|
-
let e =
|
|
58
|
+
publish: (n, r) => e.sync(() => {
|
|
59
|
+
let e = h.get(t)?.handlers.get(n);
|
|
60
60
|
if (e) for (let t of e) try {
|
|
61
61
|
t(r);
|
|
62
62
|
} catch {}
|
|
63
63
|
}),
|
|
64
|
-
subscribe: (
|
|
65
|
-
let e =
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
subscribe: (r, i) => e.sync(() => {
|
|
65
|
+
let e = g(t), a = e.handlers.get(r) ?? /* @__PURE__ */ new Set(), o = (e) => i(e);
|
|
66
|
+
a.add(o), e.handlers.set(r, a);
|
|
67
|
+
let s = () => {
|
|
68
|
+
n.delete(s), a.delete(o), a.size === 0 && e.handlers.get(r) === a && e.handlers.delete(r), e.handlers.size === 0 && h.get(t) === e && h.delete(t);
|
|
68
69
|
};
|
|
70
|
+
return n.add(s), s;
|
|
69
71
|
}),
|
|
70
|
-
close: () => e.
|
|
72
|
+
close: () => e.sync(() => {
|
|
73
|
+
for (let e of n) e();
|
|
74
|
+
})
|
|
71
75
|
};
|
|
72
76
|
}, y = (t) => {
|
|
73
77
|
let n = null, r = m(), i = !0, a = () => n || (n = (async () => {
|
|
@@ -294,24 +298,87 @@ var i = "voltro", a = (e) => e.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-"
|
|
|
294
298
|
}, O = (e) => {
|
|
295
299
|
for (let t of e) if (k(t)) return t;
|
|
296
300
|
return null;
|
|
297
|
-
}, k = (e) => typeof e.broadcast == "object" && e.broadcast !== null && typeof e.broadcast.provider == "object", A = (
|
|
301
|
+
}, k = (e) => typeof e.broadcast == "object" && e.broadcast !== null && typeof e.broadcast.provider == "object", A = (t, n, r, i, a = {}, o = () => {}) => {
|
|
302
|
+
let s = a.maxMessages ?? 1024, c = a.maxBytes ?? 8388608, l = a.timeoutMs ?? 5e3, u = a.shutdownTimeoutMs ?? 1e3;
|
|
303
|
+
for (let [e, t] of Object.entries({
|
|
304
|
+
maxMessages: s,
|
|
305
|
+
maxBytes: c,
|
|
306
|
+
timeoutMs: l,
|
|
307
|
+
shutdownTimeoutMs: u
|
|
308
|
+
})) if (!Number.isSafeInteger(t) || t < 1) throw Error(`broadcast ${e} must be a positive safe integer`);
|
|
309
|
+
let d = [], f = 0, p = 0, m = !1, h = 0, g = !1, _ = !1, v, y, b = !1, x, S = /* @__PURE__ */ new Set(), C = () => {
|
|
310
|
+
for (let e of S) e();
|
|
311
|
+
S.clear();
|
|
312
|
+
}, w = (e) => {
|
|
313
|
+
m || i(e), m = !0;
|
|
314
|
+
}, T = () => {
|
|
315
|
+
if (g || b || v !== void 0) return;
|
|
316
|
+
let i = d[f];
|
|
317
|
+
if (!i && !m) {
|
|
318
|
+
C();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
let a = h, s = i?.payload ?? r(a);
|
|
322
|
+
i && (d[f] = void 0, f++, f * 2 >= d.length && (d = d.slice(f), f = 0)), x = new AbortController(), b = !0;
|
|
323
|
+
let c = setTimeout(() => {
|
|
324
|
+
w("publish timed out; peers need a resync");
|
|
325
|
+
}, l);
|
|
326
|
+
y = e.runPromise(t.publish(n, s), { signal: x.signal }).then(() => {
|
|
327
|
+
g || o(), !i && h === a && (m = !1);
|
|
328
|
+
}, () => {
|
|
329
|
+
g || (w("publish failed — cross-replica fan-out degraded, local reactivity unaffected; peers need a resync"), v = setTimeout(() => {
|
|
330
|
+
v = void 0, T();
|
|
331
|
+
}, 500));
|
|
332
|
+
}).finally(() => {
|
|
333
|
+
clearTimeout(c), i && (p -= i.bytes), y = void 0, b = !1, x = void 0, g ? C() : T();
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
return {
|
|
337
|
+
enqueue: (e, t) => {
|
|
338
|
+
if (_ || g) return;
|
|
339
|
+
if (h = t, m || d.length - f + +!!b >= s) {
|
|
340
|
+
w("publish queue full; peers need a resync"), T();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
let n = e(), r = Buffer.byteLength(n, "utf8");
|
|
344
|
+
if (p + r > c) {
|
|
345
|
+
w("publish byte budget exceeded; peers need a resync"), T();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
p += r, d.push({
|
|
349
|
+
payload: n,
|
|
350
|
+
bytes: r
|
|
351
|
+
}), T();
|
|
352
|
+
},
|
|
353
|
+
close: async () => {
|
|
354
|
+
_ = !0, v !== void 0 && (clearTimeout(v), v = void 0), T();
|
|
355
|
+
let e;
|
|
356
|
+
(y || d.length > f || m) && await new Promise((t) => {
|
|
357
|
+
let n = () => {
|
|
358
|
+
S.delete(n), t();
|
|
359
|
+
};
|
|
360
|
+
S.add(n), e = setTimeout(n, u);
|
|
361
|
+
}), e !== void 0 && clearTimeout(e), g = !0, (y || d.length > f || m) && i("shutdown deadline reached; unsent changes require peer resync"), d = [], f = 0, v !== void 0 && clearTimeout(v), x?.abort(), y && await Promise.race([y, new Promise((e) => setImmediate(e))]), p = 0, C();
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}, j = ({ origin: e, ...t }) => t, M = (e) => Math.min(500 * 2 ** Math.min(Math.max(e, 1) - 1, 5), 1e4), N = {
|
|
298
365
|
info: () => {},
|
|
299
366
|
warn: () => {},
|
|
300
367
|
debug: () => {}
|
|
301
|
-
},
|
|
302
|
-
let { store: r, provider: i, replicaId: a } = n, o = n.logger ??
|
|
368
|
+
}, P = async (n) => {
|
|
369
|
+
let { store: r, provider: i, replicaId: a } = n, o = n.logger ?? N, s = n.channel ?? d, c = !1, l, u = 0, f = /* @__PURE__ */ new Map(), p = `${Date.now().toString(36)}.${Math.random().toString(36).slice(2, 10)}`, m = t.gauge("voltro_broadcast_connected").pipe(t.withAttributes({ provider: i.name })), h = t.counter("voltro_broadcast_gaps_total").pipe(t.withAttributes({ provider: i.name })), g = t.counter("voltro_broadcast_missed_total").pipe(t.withAttributes({ provider: i.name })), _ = t.counter("voltro_broadcast_reconnects_total").pipe(t.withAttributes({ provider: i.name })), v = {
|
|
303
370
|
connected: !1,
|
|
304
371
|
lastReceivedAt: null,
|
|
305
372
|
lastPublishedAt: null,
|
|
306
373
|
reconnects: 0,
|
|
307
374
|
gaps: 0,
|
|
308
375
|
missed: 0
|
|
309
|
-
},
|
|
310
|
-
|
|
376
|
+
}, y = (n) => {
|
|
377
|
+
v.connected = n, e.runSync(t.update(m, +!!n));
|
|
311
378
|
};
|
|
312
|
-
|
|
313
|
-
let
|
|
314
|
-
|
|
379
|
+
y(!1);
|
|
380
|
+
let b = (r) => {
|
|
381
|
+
v.gaps += 1, e.runSync(t.update(h, 1)), r.missed !== void 0 && r.missed > 0 && (v.missed += r.missed, e.runSync(t.update(g, r.missed))), o.warn(`broadcast: ${r.reason} — refreshing live queries, which is safe because a query is idempotent`, {
|
|
315
382
|
...r.origin === void 0 ? {} : { origin: r.origin },
|
|
316
383
|
...r.missed === void 0 ? {} : { missed: r.missed }
|
|
317
384
|
});
|
|
@@ -320,8 +387,9 @@ var i = "voltro", a = (e) => e.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-"
|
|
|
320
387
|
} catch (e) {
|
|
321
388
|
o.warn("broadcast: gap recovery threw", { err: e });
|
|
322
389
|
}
|
|
323
|
-
},
|
|
324
|
-
|
|
390
|
+
}, x = (e) => {
|
|
391
|
+
if (w) return;
|
|
392
|
+
v.lastReceivedAt = Date.now();
|
|
325
393
|
let t;
|
|
326
394
|
try {
|
|
327
395
|
t = JSON.parse(e);
|
|
@@ -330,35 +398,66 @@ var i = "voltro", a = (e) => e.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-"
|
|
|
330
398
|
return;
|
|
331
399
|
}
|
|
332
400
|
if (t.origin === a || !t.event || typeof t.event != "object") return;
|
|
401
|
+
let n = !1;
|
|
333
402
|
if (typeof t.n == "number" && Number.isFinite(t.n)) {
|
|
334
|
-
let e = t.epoch ?? "",
|
|
335
|
-
|
|
403
|
+
let e = t.epoch ?? "", r = f.get(t.origin);
|
|
404
|
+
if (r === void 0) f.set(t.origin, {
|
|
336
405
|
epoch: e,
|
|
337
406
|
n: t.n
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
origin: t.origin,
|
|
341
|
-
missed: t.n - n.n - 1
|
|
342
|
-
}), t.n > n.n && p.set(t.origin, {
|
|
343
|
-
epoch: e,
|
|
344
|
-
n: t.n
|
|
345
|
-
})) : p.set(t.origin, {
|
|
407
|
+
});
|
|
408
|
+
else if (r.epoch !== e) f.set(t.origin, {
|
|
346
409
|
epoch: e,
|
|
347
410
|
n: t.n
|
|
348
411
|
});
|
|
412
|
+
else {
|
|
413
|
+
let i = t.n - r.n - (t.resync === !0 ? 0 : 1);
|
|
414
|
+
i > 0 && (n = !0, b({
|
|
415
|
+
reason: `missed ${i} change(s) from ${t.origin}`,
|
|
416
|
+
origin: t.origin,
|
|
417
|
+
missed: i
|
|
418
|
+
})), t.n > r.n && f.set(t.origin, {
|
|
419
|
+
epoch: e,
|
|
420
|
+
n: t.n
|
|
421
|
+
});
|
|
422
|
+
}
|
|
349
423
|
}
|
|
350
|
-
|
|
351
|
-
|
|
424
|
+
if (t.resync === !0) {
|
|
425
|
+
n || b({
|
|
426
|
+
reason: `the ${t.origin} publisher lost changes and requested a resync`,
|
|
427
|
+
origin: t.origin
|
|
428
|
+
});
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
let i = j(t.event);
|
|
432
|
+
c = !0, l = i;
|
|
352
433
|
try {
|
|
353
|
-
r.injectExternalChange(
|
|
434
|
+
r.injectExternalChange(i);
|
|
354
435
|
} catch (e) {
|
|
355
436
|
o.warn("broadcast: inject failed", { err: e });
|
|
356
437
|
} finally {
|
|
357
438
|
c = !1, l = void 0;
|
|
358
439
|
}
|
|
359
|
-
},
|
|
440
|
+
}, S = A(i, s, (e) => JSON.stringify({
|
|
441
|
+
origin: a,
|
|
442
|
+
epoch: p,
|
|
443
|
+
n: e,
|
|
444
|
+
resync: !0,
|
|
445
|
+
event: {
|
|
446
|
+
table: "",
|
|
447
|
+
op: "update",
|
|
448
|
+
old: null,
|
|
449
|
+
new: null
|
|
450
|
+
}
|
|
451
|
+
}), (e) => {
|
|
452
|
+
o.warn(`broadcast: ${e}`, {
|
|
453
|
+
provider: i.name,
|
|
454
|
+
channel: s
|
|
455
|
+
});
|
|
456
|
+
}, n.publishQueue, () => {
|
|
457
|
+
v.lastPublishedAt = Date.now();
|
|
458
|
+
}), C, w = !1, T = 0, E = async () => {
|
|
360
459
|
try {
|
|
361
|
-
return C = await e.runPromise(i.subscribe(s,
|
|
460
|
+
return C = await e.runPromise(i.subscribe(s, x)), w ? (C(), C = void 0, !1) : (y(!0), !0);
|
|
362
461
|
} catch (e) {
|
|
363
462
|
return T += 1, T === 1 && o.warn("broadcast: could not subscribe — this replica will not receive other replicas' changes yet. Retrying in the background; local reactivity is unaffected.", {
|
|
364
463
|
provider: i.name,
|
|
@@ -370,72 +469,78 @@ var i = "voltro", a = (e) => e.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-"
|
|
|
370
469
|
await E() || (async () => {
|
|
371
470
|
let e = Date.now();
|
|
372
471
|
for (; !w && C === void 0;) {
|
|
373
|
-
if (await new Promise((e) => setTimeout(e,
|
|
472
|
+
if (await new Promise((e) => setTimeout(e, M(T))), w) return;
|
|
374
473
|
if (await E()) {
|
|
375
474
|
o.info("broadcast: subscribed", {
|
|
376
475
|
provider: i.name,
|
|
377
476
|
channel: s,
|
|
378
477
|
attempts: T
|
|
379
|
-
}),
|
|
478
|
+
}), b({ reason: `this replica was not subscribed to '${s}' for ${Math.round((Date.now() - e) / 1e3)}s` });
|
|
380
479
|
return;
|
|
381
480
|
}
|
|
382
481
|
}
|
|
383
482
|
})();
|
|
384
483
|
let D = i.onTransportEvent?.((n) => {
|
|
385
|
-
if (
|
|
386
|
-
|
|
484
|
+
if (!w) {
|
|
485
|
+
if (n.kind === "disconnected") {
|
|
486
|
+
y(!1), o.warn("broadcast: transport disconnected — cross-replica fan-out is down until it re-dials", {
|
|
487
|
+
provider: i.name,
|
|
488
|
+
...n.detail === void 0 ? {} : { detail: n.detail }
|
|
489
|
+
});
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
y(!0), v.reconnects += 1, e.runSync(t.update(_, 1)), o.warn("broadcast transport reconnected", {
|
|
387
493
|
provider: i.name,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
494
|
+
channel: s,
|
|
495
|
+
replicaId: a,
|
|
496
|
+
reconnects: v.reconnects,
|
|
497
|
+
lastReceivedAt: v.lastReceivedAt
|
|
498
|
+
}), b({ reason: `the ${i.name} transport reconnected after an outage` });
|
|
391
499
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
reconnects: y.reconnects,
|
|
397
|
-
lastReceivedAt: y.lastReceivedAt
|
|
398
|
-
}), x({ reason: `the ${i.name} transport reconnected after an outage` });
|
|
399
|
-
}), O = r.onChange((t) => {
|
|
400
|
-
if (t.origin === "injected" || c && t === l) return;
|
|
401
|
-
f += 1;
|
|
402
|
-
let n = {
|
|
500
|
+
}), O = r.onChange((e) => {
|
|
501
|
+
if (e.origin === "injected" || c && e === l) return;
|
|
502
|
+
u += 1;
|
|
503
|
+
let t = {
|
|
403
504
|
origin: a,
|
|
404
|
-
event:
|
|
405
|
-
n:
|
|
406
|
-
epoch:
|
|
505
|
+
event: e,
|
|
506
|
+
n: u,
|
|
507
|
+
epoch: p
|
|
407
508
|
};
|
|
408
|
-
|
|
409
|
-
u = !1;
|
|
410
|
-
})), e.catchAll((t) => e.sync(() => {
|
|
411
|
-
u || (u = !0, o.warn("broadcast: publish failed — cross-replica fan-out degraded, local reactivity unaffected", {
|
|
412
|
-
provider: i.name,
|
|
413
|
-
transient: t.transient,
|
|
414
|
-
message: t.message
|
|
415
|
-
}));
|
|
416
|
-
}))));
|
|
509
|
+
S.enqueue(() => JSON.stringify(t), u);
|
|
417
510
|
});
|
|
418
|
-
|
|
511
|
+
o.info("broadcast bus attached", {
|
|
419
512
|
provider: i.name,
|
|
420
513
|
channel: s,
|
|
421
514
|
replicaId: a
|
|
422
|
-
})
|
|
515
|
+
});
|
|
516
|
+
let k;
|
|
517
|
+
return {
|
|
423
518
|
health: () => ({
|
|
424
519
|
provider: i.name,
|
|
425
520
|
channel: s,
|
|
426
521
|
replicaId: a,
|
|
427
|
-
...
|
|
522
|
+
...v
|
|
428
523
|
}),
|
|
429
|
-
close: async () => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
524
|
+
close: () => (k ??= (async () => {
|
|
525
|
+
w = !0, O(), D?.(), y(!1);
|
|
526
|
+
try {
|
|
527
|
+
C?.();
|
|
528
|
+
} catch {}
|
|
529
|
+
await S.close();
|
|
530
|
+
let t = new AbortController(), r, a = e.runPromise(i.close(), { signal: t.signal }).catch((e) => {
|
|
531
|
+
o.warn("broadcast: provider close failed", { error: e });
|
|
532
|
+
});
|
|
533
|
+
try {
|
|
534
|
+
await Promise.race([a, new Promise((e) => {
|
|
535
|
+
r = setTimeout(() => {
|
|
536
|
+
o.warn("broadcast: provider shutdown timed out"), t.abort(), e();
|
|
537
|
+
}, n.publishQueue?.shutdownTimeoutMs ?? 1e3);
|
|
538
|
+
})]);
|
|
539
|
+
} finally {
|
|
540
|
+
r !== void 0 && clearTimeout(r);
|
|
436
541
|
}
|
|
437
|
-
}
|
|
542
|
+
})(), k)
|
|
438
543
|
};
|
|
439
544
|
};
|
|
440
545
|
//#endregion
|
|
441
|
-
export { d as BROADCAST_CHANNEL, u as BroadcastError, i as DEFAULT_BROADCAST_NAMESPACE,
|
|
546
|
+
export { d as BROADCAST_CHANNEL, u as BroadcastError, i as DEFAULT_BROADCAST_NAMESPACE, P as attachBroadcastBus, D as broadcastPlugin, c as channelFor, o as describeNamespaceResolution, l as eventChannelFor, O as getBroadcastPlugin, v as memoryProvider, S as natsProvider, y as redisProvider, _ as resetMemoryBus, s as resolveBroadcastNamespace, w as resolveBroadcastProvider, M as subscribeRetryDelayMs };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-broadcast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"description": "Cross-replica reactivity — a pub/sub message bus (Redis / NATS / memory) that fans out app-mutation ChangeEvents to every replica behind a load balancer. ADDITIVE to inline emit: local reactivity survives a broker outage; cross-replica degrades gracefully. Closes the single-instance gap for every non-postgres dialect (postgres keeps native LISTEN/NOTIFY).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
"node": ">=24.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@voltro/database": "0.
|
|
37
|
-
"@voltro/protocol": "0.
|
|
36
|
+
"@voltro/database": "0.85.0",
|
|
37
|
+
"@voltro/protocol": "0.85.0"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
40
|
"@nats-io/transport-node": "^3.4.0",
|
|
41
41
|
"ioredis": "^5.11.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"effect": "
|
|
44
|
+
"effect": "4.0.1"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|