@voltro/plugin-broadcast 0.24.0 → 0.26.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 +670 -0
- package/dist/index.d.ts +110 -8
- package/dist/index.js +82 -50
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -23,10 +23,29 @@ export declare interface AttachBroadcastBusOptions {
|
|
|
23
23
|
readonly logger?: BroadcastBusLogger;
|
|
24
24
|
/** Channel override (tests). Default `voltro:changes`. */
|
|
25
25
|
readonly channel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Called when this replica proves it missed changes from a peer.
|
|
28
|
+
*
|
|
29
|
+
* The count is EXACT — the difference between the serial in hand and the last
|
|
30
|
+
* one seen from that origin — not an estimate. There is nothing to replay
|
|
31
|
+
* (pub/sub keeps no log), so the caller's job is to make the loss irrelevant:
|
|
32
|
+
* a live query is idempotent, so re-running every one of them is always safe
|
|
33
|
+
* and always correct. `voltro dev` and `voltro serve` wire this to the
|
|
34
|
+
* dispatcher's refresh.
|
|
35
|
+
*
|
|
36
|
+
* Absent ⇒ the gap is still DETECTED and logged; only the recovery is
|
|
37
|
+
* missing. That is deliberate: a bus used without a dispatcher (a test, an
|
|
38
|
+
* embedder) should still say what it lost.
|
|
39
|
+
*/
|
|
40
|
+
readonly onGap?: (origin: string, missed: number) => void;
|
|
26
41
|
}
|
|
27
42
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
43
|
+
/**
|
|
44
|
+
* The changes channel when no namespace is known — a last resort, not the
|
|
45
|
+
* normal path. A real boot resolves a namespace from the app's name and passes
|
|
46
|
+
* the channel in, so two apps on one broker do not share it.
|
|
47
|
+
*/
|
|
48
|
+
export declare const BROADCAST_CHANNEL: string;
|
|
30
49
|
|
|
31
50
|
export declare interface BroadcastBusHandle {
|
|
32
51
|
/** Detach the onChange listener, the bus subscription, and close the
|
|
@@ -40,6 +59,9 @@ export declare interface BroadcastBusLogger {
|
|
|
40
59
|
debug: (message: string, fields?: Record<string, unknown>) => void;
|
|
41
60
|
}
|
|
42
61
|
|
|
62
|
+
/** The channels the framework runs over one broker. */
|
|
63
|
+
export declare type BroadcastChannelKind = 'changes' | 'events' | 'members' | 'presence';
|
|
64
|
+
|
|
43
65
|
/**
|
|
44
66
|
* What rides the wire on the `voltro:changes` channel. `origin` is the
|
|
45
67
|
* publishing replica's id; subscribers skip their OWN origin (the writer
|
|
@@ -50,6 +72,21 @@ export declare interface BroadcastBusLogger {
|
|
|
50
72
|
export declare interface BroadcastEnvelope {
|
|
51
73
|
readonly origin: string;
|
|
52
74
|
readonly event: ChangeEvent;
|
|
75
|
+
/**
|
|
76
|
+
* This origin's monotonic serial, from 1.
|
|
77
|
+
*
|
|
78
|
+
* The one thing a receiver cannot infer, and without it a dropped message is
|
|
79
|
+
* undetectable: pub/sub has no retention, so a replica whose broker
|
|
80
|
+
* connection blips simply never learns that a change happened. Its clients
|
|
81
|
+
* keep their sockets — the client-side reconnect never fires — and their live
|
|
82
|
+
* queries stay stale until something else touches the same table, which for a
|
|
83
|
+
* quiet table can be never.
|
|
84
|
+
*
|
|
85
|
+
* Optional on the type because a message from an older replica during a
|
|
86
|
+
* rolling deploy has none; the receiver treats that as "cannot tell" rather
|
|
87
|
+
* than as a gap.
|
|
88
|
+
*/
|
|
89
|
+
readonly n?: number;
|
|
53
90
|
}
|
|
54
91
|
|
|
55
92
|
/**
|
|
@@ -87,6 +124,12 @@ export declare interface BroadcastPlugin extends VoltroPlugin {
|
|
|
87
124
|
readonly provider: BroadcastProvider;
|
|
88
125
|
readonly url: string | null;
|
|
89
126
|
readonly crossReplica: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* The namespace as DECLARED here, before the app name or env is consulted.
|
|
129
|
+
* `undefined` means "derive it" — the framework resolves the effective value
|
|
130
|
+
* at boot, because only it knows the app's name.
|
|
131
|
+
*/
|
|
132
|
+
readonly namespace: string | undefined;
|
|
90
133
|
};
|
|
91
134
|
}
|
|
92
135
|
|
|
@@ -106,12 +149,18 @@ export declare interface BroadcastPluginOptions {
|
|
|
106
149
|
* `REDIS_URL`). Point broadcast at its own server or the shared one purely
|
|
107
150
|
* by which env var you set. */
|
|
108
151
|
readonly connection?: string;
|
|
109
|
-
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Namespace for EVERY framework channel on this broker — changes, events,
|
|
154
|
+
* membership and presence alike.
|
|
155
|
+
*
|
|
156
|
+
* Defaults to your app's name, so two different apps sharing one Redis or
|
|
157
|
+
* NATS separate without anyone remembering to do anything. Set it explicitly
|
|
158
|
+
* for the case that default cannot see: **several deployments of the SAME app
|
|
159
|
+
* on one broker** (staging and production, say) have the same name and the
|
|
160
|
+
* same code, so nothing derivable tells them apart. There, this — or
|
|
161
|
+
* `VOLTRO_BROADCAST_NAMESPACE` — is the only thing that works.
|
|
162
|
+
*/
|
|
163
|
+
readonly namespace?: string;
|
|
115
164
|
/** Disambiguates multiple instances of this plugin in one app. */
|
|
116
165
|
readonly name?: string;
|
|
117
166
|
}
|
|
@@ -150,6 +199,33 @@ export declare interface BroadcastStore {
|
|
|
150
199
|
injectExternalChange: (event: ChangeEvent) => void;
|
|
151
200
|
}
|
|
152
201
|
|
|
202
|
+
/**
|
|
203
|
+
* One framework channel inside a namespace.
|
|
204
|
+
*
|
|
205
|
+
* Every channel goes through here so a fifth one cannot be added as a flat
|
|
206
|
+
* constant that quietly skips the namespace — which is how the first four came
|
|
207
|
+
* to be flat in the first place.
|
|
208
|
+
*/
|
|
209
|
+
export declare const channelFor: (namespace: string, kind: BroadcastChannelKind) => string;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The namespace used when nothing else is known.
|
|
213
|
+
*
|
|
214
|
+
* Only reachable when there is no app name to derive from — a bare library use.
|
|
215
|
+
* A real boot always has one.
|
|
216
|
+
*/
|
|
217
|
+
export declare const DEFAULT_BROADCAST_NAMESPACE = "voltro";
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The channel for ONE declared event.
|
|
221
|
+
*
|
|
222
|
+
* Per event rather than per app, so a replica can decline the traffic it has no
|
|
223
|
+
* subscribers for. The event name comes from a declaration and is unique by boot
|
|
224
|
+
* audit, but it is sanitised anyway: a name containing a NATS wildcard would
|
|
225
|
+
* turn one event's channel into a pattern matching others.
|
|
226
|
+
*/
|
|
227
|
+
export declare const eventChannelFor: (namespace: string, event: string) => string;
|
|
228
|
+
|
|
153
229
|
/**
|
|
154
230
|
* Pull the resolved broadcast carrier off a plugin list (the framework's
|
|
155
231
|
* serve pipeline calls this after building the store). Returns the FIRST
|
|
@@ -210,6 +286,32 @@ export declare interface RedisProviderOptions {
|
|
|
210
286
|
|
|
211
287
|
/* Excluded from this release type: resetMemoryBus */
|
|
212
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Resolve the namespace every framework channel hangs off.
|
|
291
|
+
*
|
|
292
|
+
* Deriving from the app name by DEFAULT rather than requiring configuration, and
|
|
293
|
+
* that ordering is the whole point: a namespace you must remember to set is one
|
|
294
|
+
* two apps forget to set, and the failure is silent in the worst direction —
|
|
295
|
+
* one deployment's events arriving at another's clients. The default has to be
|
|
296
|
+
* safe; configuration is for where the default cannot see.
|
|
297
|
+
*
|
|
298
|
+
* And it genuinely cannot see one case, which is worth stating plainly rather
|
|
299
|
+
* than papering over: STAGING AND PRODUCTION OF THE SAME APP have the same name,
|
|
300
|
+
* the same code and the same fingerprint. Nothing derivable separates them. If
|
|
301
|
+
* one broker serves several deployments of one app, the env var is not optional
|
|
302
|
+
* — it is the only thing that can work, and the docs say so.
|
|
303
|
+
*/
|
|
304
|
+
export declare const resolveBroadcastNamespace: (input: ResolveBroadcastNamespaceInput) => string;
|
|
305
|
+
|
|
306
|
+
export declare interface ResolveBroadcastNamespaceInput {
|
|
307
|
+
/** `broadcast({ namespace })` — explicit, and wins over everything. */
|
|
308
|
+
readonly option?: string | undefined;
|
|
309
|
+
/** `VOLTRO_BROADCAST_NAMESPACE` — for the case the code cannot see. */
|
|
310
|
+
readonly env?: string | undefined;
|
|
311
|
+
/** The app's name, the default. Different apps therefore separate on their own. */
|
|
312
|
+
readonly appName?: string | undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
213
315
|
export declare interface ResolveBroadcastOptions {
|
|
214
316
|
/** Explicit provider name or a pre-built `BroadcastProvider`. */
|
|
215
317
|
readonly provider?: BroadcastProviderName | BroadcastProvider;
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
import { Effect as e, Schema as t } from "effect";
|
|
2
2
|
import { definePlugin as n } from "@voltro/protocol";
|
|
3
|
-
//#region src/
|
|
4
|
-
var r =
|
|
3
|
+
//#region src/namespace.ts
|
|
4
|
+
var r = "voltro", i = (e) => e.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, ""), a = (e) => {
|
|
5
|
+
for (let t of [
|
|
6
|
+
e.option,
|
|
7
|
+
e.env,
|
|
8
|
+
e.appName
|
|
9
|
+
]) {
|
|
10
|
+
if (t === void 0) continue;
|
|
11
|
+
let e = i(t);
|
|
12
|
+
if (e.length > 0) return e;
|
|
13
|
+
}
|
|
14
|
+
return r;
|
|
15
|
+
}, o = (e, t) => `${e}:${t}`, s = (e, t) => `${o(e, "events")}:${i(t)}`, c = class extends t.TaggedError()("BroadcastError", {
|
|
5
16
|
provider: t.String,
|
|
6
17
|
message: t.String,
|
|
7
18
|
transient: t.Boolean,
|
|
8
19
|
cause: t.optional(t.Unknown)
|
|
9
|
-
}) {},
|
|
20
|
+
}) {}, l = o(r, "changes"), u = (e, t, n) => new c({
|
|
10
21
|
provider: e,
|
|
11
22
|
message: t,
|
|
12
23
|
transient: !0,
|
|
13
24
|
...n === void 0 ? {} : { cause: n }
|
|
14
|
-
}),
|
|
25
|
+
}), d = (e, t, n) => new c({
|
|
15
26
|
provider: e,
|
|
16
27
|
message: t,
|
|
17
28
|
transient: !1,
|
|
18
29
|
...n === void 0 ? {} : { cause: n }
|
|
19
|
-
}),
|
|
20
|
-
let t =
|
|
21
|
-
return t || (t = { handlers: /* @__PURE__ */ new Map() },
|
|
22
|
-
},
|
|
23
|
-
|
|
24
|
-
},
|
|
25
|
-
let n =
|
|
30
|
+
}), f = /* @__PURE__ */ new Map(), p = (e) => {
|
|
31
|
+
let t = f.get(e);
|
|
32
|
+
return t || (t = { handlers: /* @__PURE__ */ new Map() }, f.set(e, t)), t;
|
|
33
|
+
}, m = (e) => {
|
|
34
|
+
f.get(e)?.handlers.clear();
|
|
35
|
+
}, h = (t = `default-${Math.random().toString(36).slice(2)}`) => {
|
|
36
|
+
let n = p(t);
|
|
26
37
|
return {
|
|
27
38
|
name: "memory",
|
|
28
39
|
publish: (t, r) => e.sync(() => {
|
|
@@ -39,7 +50,7 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
39
50
|
}),
|
|
40
51
|
close: () => e.void
|
|
41
52
|
};
|
|
42
|
-
},
|
|
53
|
+
}, g = (t) => {
|
|
43
54
|
let n = null, r = () => n || (n = (async () => {
|
|
44
55
|
let e;
|
|
45
56
|
if (t.client) e = t.client;
|
|
@@ -48,7 +59,7 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
48
59
|
try {
|
|
49
60
|
n = (await import("ioredis")).default;
|
|
50
61
|
} catch (e) {
|
|
51
|
-
throw
|
|
62
|
+
throw d("redis", "the 'redis' broadcast provider requires the 'ioredis' optional dependency. Install it to enable it.", e);
|
|
52
63
|
}
|
|
53
64
|
e = new n(t.url);
|
|
54
65
|
}
|
|
@@ -65,7 +76,7 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
65
76
|
let { pub: e } = await r();
|
|
66
77
|
await e.publish(t, n);
|
|
67
78
|
},
|
|
68
|
-
catch: (e) =>
|
|
79
|
+
catch: (e) => u("redis", `publish failed: ${String(e?.message ?? e)}`, e)
|
|
69
80
|
}),
|
|
70
81
|
subscribe: (t, n) => e.tryPromise({
|
|
71
82
|
try: async () => {
|
|
@@ -76,7 +87,7 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
76
87
|
e.off("message", i), e.unsubscribe(t);
|
|
77
88
|
};
|
|
78
89
|
},
|
|
79
|
-
catch: (e) =>
|
|
90
|
+
catch: (e) => u("redis", `subscribe failed: ${String(e?.message ?? e)}`, e)
|
|
80
91
|
}),
|
|
81
92
|
close: () => e.promise(async () => {
|
|
82
93
|
if (n) try {
|
|
@@ -85,28 +96,28 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
85
96
|
} catch {}
|
|
86
97
|
})
|
|
87
98
|
};
|
|
88
|
-
},
|
|
99
|
+
}, _ = new TextEncoder(), v = new TextDecoder(), y = (t) => {
|
|
89
100
|
let n = null, r = () => import("@nats-io/transport-node").catch((e) => {
|
|
90
|
-
throw
|
|
101
|
+
throw d("nats", "the 'nats' broadcast provider requires the '@nats-io/transport-node' optional dependency. Install it to enable it.", e);
|
|
91
102
|
}), i = () => n || (n = (async () => t.connection ? t.connection : (await r()).connect({ servers: t.url }))(), n);
|
|
92
103
|
return {
|
|
93
104
|
name: "nats",
|
|
94
105
|
publish: (t, n) => e.tryPromise({
|
|
95
106
|
try: async () => {
|
|
96
|
-
(await i()).publish(t,
|
|
107
|
+
(await i()).publish(t, _.encode(n));
|
|
97
108
|
},
|
|
98
|
-
catch: (e) =>
|
|
109
|
+
catch: (e) => u("nats", `publish failed: ${String(e?.message ?? e)}`, e)
|
|
99
110
|
}),
|
|
100
111
|
subscribe: (t, n) => e.tryPromise({
|
|
101
112
|
try: async () => {
|
|
102
113
|
let e = (await i()).subscribe(t);
|
|
103
114
|
return (async () => {
|
|
104
|
-
for await (let t of e) n(
|
|
115
|
+
for await (let t of e) n(v.decode(t.data));
|
|
105
116
|
})(), () => {
|
|
106
117
|
e.unsubscribe();
|
|
107
118
|
};
|
|
108
119
|
},
|
|
109
|
-
catch: (e) =>
|
|
120
|
+
catch: (e) => u("nats", `subscribe failed: ${String(e?.message ?? e)}`, e)
|
|
110
121
|
}),
|
|
111
122
|
close: () => e.promise(async () => {
|
|
112
123
|
if (n) try {
|
|
@@ -115,7 +126,7 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
115
126
|
} catch {}
|
|
116
127
|
})
|
|
117
128
|
};
|
|
118
|
-
},
|
|
129
|
+
}, b = (e, t) => t[`${e.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_REDIS_URL`], x = (e, t) => {
|
|
119
130
|
if (e.provider && typeof e.provider == "object") {
|
|
120
131
|
let t = e.provider;
|
|
121
132
|
return {
|
|
@@ -124,16 +135,16 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
124
135
|
crossReplica: t.name !== "memory"
|
|
125
136
|
};
|
|
126
137
|
}
|
|
127
|
-
let n = e.url ?? t.BROADCAST_URL ?? null, r =
|
|
138
|
+
let n = e.url ?? t.BROADCAST_URL ?? null, r = b(e.connection ?? "broadcast", t), i = e.connection !== void 0;
|
|
128
139
|
switch ((e.provider ?? t.BROADCAST_PROVIDER)?.toLowerCase() ?? (n?.startsWith("nats://") ? "nats" : n?.startsWith("redis://") || n?.startsWith("rediss://") || r || i && t.REDIS_URL ? "redis" : void 0)) {
|
|
129
140
|
case "redis": {
|
|
130
141
|
let e = n ?? r ?? t.REDIS_URL;
|
|
131
142
|
return e ? {
|
|
132
|
-
provider:
|
|
143
|
+
provider: g({ url: e }),
|
|
133
144
|
url: e,
|
|
134
145
|
crossReplica: !0
|
|
135
146
|
} : {
|
|
136
|
-
provider:
|
|
147
|
+
provider: h(),
|
|
137
148
|
url: null,
|
|
138
149
|
crossReplica: !1
|
|
139
150
|
};
|
|
@@ -141,22 +152,22 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
141
152
|
case "nats": {
|
|
142
153
|
let e = n ?? t.NATS_URL;
|
|
143
154
|
return e ? {
|
|
144
|
-
provider:
|
|
155
|
+
provider: y({ url: e }),
|
|
145
156
|
url: e,
|
|
146
157
|
crossReplica: !0
|
|
147
158
|
} : {
|
|
148
|
-
provider:
|
|
159
|
+
provider: h(),
|
|
149
160
|
url: null,
|
|
150
161
|
crossReplica: !1
|
|
151
162
|
};
|
|
152
163
|
}
|
|
153
164
|
default: return {
|
|
154
|
-
provider:
|
|
165
|
+
provider: h(),
|
|
155
166
|
url: null,
|
|
156
167
|
crossReplica: !1
|
|
157
168
|
};
|
|
158
169
|
}
|
|
159
|
-
},
|
|
170
|
+
}, S = ["network:outbound:*"], C = [
|
|
160
171
|
{
|
|
161
172
|
name: "BROADCAST_URL",
|
|
162
173
|
required: !1,
|
|
@@ -192,8 +203,8 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
192
203
|
description: "Fallback broker URL used when the resolved provider is nats and no BROADCAST_URL is set (comma-separated for a cluster).",
|
|
193
204
|
example: "nats://localhost:4222"
|
|
194
205
|
}
|
|
195
|
-
],
|
|
196
|
-
let r =
|
|
206
|
+
], w = (t = {}) => {
|
|
207
|
+
let r = x({
|
|
197
208
|
...t.provider === void 0 ? {} : { provider: t.provider },
|
|
198
209
|
...t.url === void 0 ? {} : { url: t.url },
|
|
199
210
|
...t.connection === void 0 ? {} : { connection: t.connection }
|
|
@@ -202,8 +213,8 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
202
213
|
...n({
|
|
203
214
|
name: t.name ? `@voltro/plugin-broadcast#${t.name}` : "@voltro/plugin-broadcast",
|
|
204
215
|
description: "Cross-replica reactivity — fans out app-mutation ChangeEvents to every replica over a pub/sub bus (Redis / NATS). ADDITIVE to inline emit: local reactivity survives a broker outage. Closes the single-instance gap for every non-postgres dialect.",
|
|
205
|
-
permissions:
|
|
206
|
-
declaredEnv:
|
|
216
|
+
permissions: S,
|
|
217
|
+
declaredEnv: C,
|
|
207
218
|
onActivate: (t) => e.sync(() => {
|
|
208
219
|
r.crossReplica ? t.logger.info("broadcast active", {
|
|
209
220
|
provider: r.provider.name,
|
|
@@ -215,40 +226,61 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
215
226
|
broadcast: {
|
|
216
227
|
provider: r.provider,
|
|
217
228
|
url: r.url,
|
|
218
|
-
crossReplica: r.crossReplica
|
|
229
|
+
crossReplica: r.crossReplica,
|
|
230
|
+
namespace: t.namespace
|
|
219
231
|
}
|
|
220
232
|
};
|
|
221
|
-
},
|
|
222
|
-
for (let t of e) if (
|
|
233
|
+
}, T = (e) => {
|
|
234
|
+
for (let t of e) if (E(t)) return t;
|
|
223
235
|
return null;
|
|
224
|
-
},
|
|
236
|
+
}, E = (e) => typeof e.broadcast == "object" && e.broadcast !== null && typeof e.broadcast.provider == "object", D = {
|
|
225
237
|
info: () => {},
|
|
226
238
|
warn: () => {},
|
|
227
239
|
debug: () => {}
|
|
228
|
-
},
|
|
229
|
-
let { store: n, provider: r, replicaId: i } = t, a = t.logger ??
|
|
230
|
-
let
|
|
240
|
+
}, O = async (t) => {
|
|
241
|
+
let { store: n, provider: r, replicaId: i } = t, a = t.logger ?? D, o = t.channel ?? l, s = !1, c = !1, u = 0, d = /* @__PURE__ */ new Map(), f = await e.runPromise(r.subscribe(o, (e) => {
|
|
242
|
+
let r;
|
|
231
243
|
try {
|
|
232
|
-
|
|
244
|
+
r = JSON.parse(e);
|
|
233
245
|
} catch {
|
|
234
246
|
a.warn("broadcast: bad payload (not JSON)", { channel: o });
|
|
235
247
|
return;
|
|
236
248
|
}
|
|
237
|
-
if (
|
|
249
|
+
if (r.origin !== i && !(!r.event || typeof r.event != "object")) {
|
|
250
|
+
if (typeof r.n == "number" && Number.isFinite(r.n)) {
|
|
251
|
+
let e = d.get(r.origin);
|
|
252
|
+
if (e !== void 0 && r.n > e + 1) {
|
|
253
|
+
let n = r.n - e - 1;
|
|
254
|
+
a.warn("broadcast: missed changes from a peer — refreshing live queries, which is safe because a query is idempotent", {
|
|
255
|
+
origin: r.origin,
|
|
256
|
+
missed: n,
|
|
257
|
+
from: e,
|
|
258
|
+
to: r.n
|
|
259
|
+
});
|
|
260
|
+
try {
|
|
261
|
+
t.onGap?.(r.origin, n);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
a.warn("broadcast: gap recovery threw", { err: e });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
(e === void 0 || r.n > e) && d.set(r.origin, r.n);
|
|
267
|
+
}
|
|
238
268
|
s = !0;
|
|
239
269
|
try {
|
|
240
|
-
n.injectExternalChange(
|
|
270
|
+
n.injectExternalChange(r.event);
|
|
241
271
|
} catch (e) {
|
|
242
272
|
a.warn("broadcast: inject failed", { err: e });
|
|
243
273
|
} finally {
|
|
244
274
|
s = !1;
|
|
245
275
|
}
|
|
246
276
|
}
|
|
247
|
-
})),
|
|
277
|
+
})), p = n.onChange((t) => {
|
|
248
278
|
if (s) return;
|
|
279
|
+
u += 1;
|
|
249
280
|
let n = {
|
|
250
281
|
origin: i,
|
|
251
|
-
event: t
|
|
282
|
+
event: t,
|
|
283
|
+
n: u
|
|
252
284
|
};
|
|
253
285
|
e.runPromise(r.publish(o, JSON.stringify(n)).pipe(e.tap(() => e.sync(() => {
|
|
254
286
|
c = !1;
|
|
@@ -265,16 +297,16 @@ var r = class extends t.TaggedError()("BroadcastError", {
|
|
|
265
297
|
channel: o,
|
|
266
298
|
replicaId: i
|
|
267
299
|
});
|
|
268
|
-
let
|
|
300
|
+
let m = !1;
|
|
269
301
|
return { close: async () => {
|
|
270
|
-
if (!
|
|
271
|
-
|
|
302
|
+
if (!m) {
|
|
303
|
+
m = !0, p();
|
|
272
304
|
try {
|
|
273
|
-
|
|
305
|
+
f();
|
|
274
306
|
} catch {}
|
|
275
307
|
await e.runPromise(r.close());
|
|
276
308
|
}
|
|
277
309
|
} };
|
|
278
310
|
};
|
|
279
311
|
//#endregion
|
|
280
|
-
export {
|
|
312
|
+
export { l as BROADCAST_CHANNEL, c as BroadcastError, r as DEFAULT_BROADCAST_NAMESPACE, O as attachBroadcastBus, w as broadcastPlugin, o as channelFor, s as eventChannelFor, T as getBroadcastPlugin, h as memoryProvider, y as natsProvider, g as redisProvider, m as resetMemoryBus, a as resolveBroadcastNamespace, x as resolveBroadcastProvider };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-broadcast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.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",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"node": ">=24.0.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@voltro/database": "0.
|
|
36
|
-
"@voltro/protocol": "0.
|
|
35
|
+
"@voltro/database": "0.26.0",
|
|
36
|
+
"@voltro/protocol": "0.26.0"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
39
|
"@nats-io/transport-node": "^3.4.0",
|