@voltro/plugin-broadcast 0.86.0 → 0.88.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 +5484 -0
- package/THIRD-PARTY-NOTICES.md +3 -1514
- package/dist/bus.js +1 -2
- package/dist/index.d.ts +28 -82
- package/dist/namespace.js +1 -2
- package/dist/plugin.js +35 -23
- package/dist/providers.js +37 -109
- package/dist/publishQueue.js +1 -2
- package/dist/resolve.js +65 -49
- package/dist/types.js +1 -2
- package/package.json +5 -8
package/dist/bus.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BROADCAST_CHANNEL as e } from "./types.js";
|
|
2
2
|
import { createPublishQueue as t } from "./publishQueue.js";
|
|
3
3
|
import { Effect as n, Metric as r } from "effect";
|
|
4
|
-
|
|
4
|
+
// src/bus.ts
|
|
5
5
|
var i = ({ origin: e, ...t }) => t, a = (e) => Math.min(500 * 2 ** Math.min(Math.max(e, 1) - 1, 5), 1e4), o = {
|
|
6
6
|
info: () => {},
|
|
7
7
|
warn: () => {},
|
|
@@ -177,5 +177,4 @@ var i = ({ origin: e, ...t }) => t, a = (e) => Math.min(500 * 2 ** Math.min(Math
|
|
|
177
177
|
})(), N)
|
|
178
178
|
};
|
|
179
179
|
};
|
|
180
|
-
//#endregion
|
|
181
180
|
export { s as attachBroadcastBus, a as subscribeRetryDelayMs, i as withoutWireOrigin };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ChangeEvent } from '@voltro/database';
|
|
2
|
+
import { ConnectionPubSub } from '@voltro/kv/connection';
|
|
3
|
+
import { ConnectionRef } from '@voltro/kv/connection';
|
|
2
4
|
import { Effect } from 'effect';
|
|
3
5
|
import { Schema } from 'effect';
|
|
4
6
|
import { VoltroPlugin } from '@voltro/protocol';
|
|
@@ -72,6 +74,12 @@ export declare interface BroadcastBusLogger {
|
|
|
72
74
|
* `voltro start` WEB process rather than an api replica. */
|
|
73
75
|
export declare type BroadcastChannelKind = 'changes' | 'events' | 'members' | 'presence' | 'revalidate';
|
|
74
76
|
|
|
77
|
+
export declare interface BroadcastConnectionSelection {
|
|
78
|
+
readonly role: string;
|
|
79
|
+
readonly kind: 'redis' | 'nats';
|
|
80
|
+
readonly reference?: BrokerReference;
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
/**
|
|
76
84
|
* What rides the wire on the `voltro:changes` channel. `origin` is the
|
|
77
85
|
* publishing replica's id; subscribers skip their OWN origin (the writer
|
|
@@ -193,7 +201,6 @@ export declare interface BroadcastHealth {
|
|
|
193
201
|
export declare interface BroadcastPlugin extends VoltroPlugin {
|
|
194
202
|
readonly broadcast: {
|
|
195
203
|
readonly provider: BroadcastProvider;
|
|
196
|
-
readonly url: string | null;
|
|
197
204
|
readonly crossReplica: boolean;
|
|
198
205
|
/**
|
|
199
206
|
* The namespace as DECLARED here, before the app name or env is consulted.
|
|
@@ -208,18 +215,14 @@ export declare const broadcastPlugin: (options?: BroadcastPluginOptions) => Broa
|
|
|
208
215
|
|
|
209
216
|
export declare interface BroadcastPluginOptions {
|
|
210
217
|
/** Provider name (`'redis' | 'nats' | 'memory'`) or a pre-built
|
|
211
|
-
* `BroadcastProvider`. Default:
|
|
212
|
-
* `BROADCAST_PROVIDER` / `REDIS_URL` env, else `'memory'`. */
|
|
218
|
+
* `BroadcastProvider`. Default: reference kind, provider Env or registry endpoint Env, else memory. */
|
|
213
219
|
readonly provider?: BroadcastProviderName | BroadcastProvider;
|
|
214
|
-
/**
|
|
215
|
-
|
|
216
|
-
readonly
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
* `REDIS_URL`). Point broadcast at its own server or the shared one purely
|
|
221
|
-
* by which env var you set. */
|
|
222
|
-
readonly connection?: string;
|
|
220
|
+
/** Default broker reference. Endpoints and credentials belong in app connections. */
|
|
221
|
+
readonly connection?: ConnectionRef<'redis' | 'nats'>;
|
|
222
|
+
readonly events?: ConnectionRef<'redis' | 'nats'>;
|
|
223
|
+
readonly members?: ConnectionRef<'redis' | 'nats'>;
|
|
224
|
+
readonly presence?: ConnectionRef<'redis' | 'nats'>;
|
|
225
|
+
readonly revalidate?: ConnectionRef<'redis' | 'nats'>;
|
|
223
226
|
/**
|
|
224
227
|
* Namespace for EVERY framework channel on this broker — changes, events,
|
|
225
228
|
* membership and presence alike.
|
|
@@ -299,6 +302,8 @@ export declare interface BroadcastTransportEvent {
|
|
|
299
302
|
readonly detail?: string;
|
|
300
303
|
}
|
|
301
304
|
|
|
305
|
+
declare type BrokerReference = ConnectionRef<'redis' | 'nats'>;
|
|
306
|
+
|
|
302
307
|
/**
|
|
303
308
|
* One framework channel inside a namespace.
|
|
304
309
|
*
|
|
@@ -362,63 +367,17 @@ export declare const getBroadcastPlugin: (plugins: ReadonlyArray<VoltroPlugin>)
|
|
|
362
367
|
*/
|
|
363
368
|
export declare const memoryProvider: (bus?: string) => BroadcastProvider;
|
|
364
369
|
|
|
365
|
-
declare interface NatsLike {
|
|
366
|
-
publish: (subject: string, data: Uint8Array) => void;
|
|
367
|
-
subscribe: (subject: string) => NatsSubscription;
|
|
368
|
-
drain: () => Promise<void>;
|
|
369
|
-
/** nats.js' connection events. Read for two reasons: to report a reconnect as
|
|
370
|
-
* the hole it is, and to notice a connection that gave up. */
|
|
371
|
-
status: () => AsyncIterable<{
|
|
372
|
-
type: string;
|
|
373
|
-
data?: unknown;
|
|
374
|
-
}>;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
370
|
export declare const natsProvider: (options: NatsProviderOptions) => BroadcastProvider;
|
|
378
371
|
|
|
379
|
-
export declare interface NatsProviderOptions {
|
|
380
|
-
/** `nats://host:port` (comma-separated for a cluster). Required. */
|
|
381
|
-
readonly url: string;
|
|
382
|
-
/** Bring your own connected NATS connection (e.g. for shared creds). */
|
|
383
|
-
readonly connection?: NatsLike;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
declare interface NatsSubscription extends AsyncIterable<{
|
|
387
|
-
data: Uint8Array;
|
|
388
|
-
}> {
|
|
389
|
-
unsubscribe: () => void;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/** The slice of ioredis the provider touches — keeps the optional dep out
|
|
393
|
-
* of the type surface. */
|
|
394
|
-
declare interface RedisLike {
|
|
395
|
-
publish: (channel: string, message: string) => Promise<number>;
|
|
396
|
-
subscribe: (channel: string) => Promise<unknown>;
|
|
397
|
-
unsubscribe: (channel: string) => Promise<unknown>;
|
|
398
|
-
on(event: 'message', listener: (channel: string, message: string) => void): void;
|
|
399
|
-
/** ioredis' connection lifecycle. The subscriber re-subscribes its channels
|
|
400
|
-
* itself on `ready`; what it does NOT do is tell anyone that the messages
|
|
401
|
-
* sent while it was away are gone. That is what these are read for. */
|
|
402
|
-
on(event: 'ready' | 'reconnecting' | 'end' | 'close', listener: () => void): void;
|
|
403
|
-
/** `error` is REQUIRED, not optional decoration: an `EventEmitter` with no
|
|
404
|
-
* `error` listener turns the event into an uncaught exception. See the
|
|
405
|
-
* registration below. */
|
|
406
|
-
on(event: 'error', listener: (err: unknown) => void): void;
|
|
407
|
-
off(event: 'message', listener: (channel: string, message: string) => void): void;
|
|
408
|
-
off(event: 'ready' | 'reconnecting' | 'end' | 'close', listener: () => void): void;
|
|
409
|
-
duplicate: () => RedisLike;
|
|
410
|
-
quit: () => Promise<unknown>;
|
|
372
|
+
export declare interface NatsProviderOptions extends RedisProviderOptions {
|
|
411
373
|
}
|
|
412
374
|
|
|
375
|
+
/** Both adapters borrow the same consumer contract; neither owns a broker. */
|
|
413
376
|
export declare const redisProvider: (options: RedisProviderOptions) => BroadcastProvider;
|
|
414
377
|
|
|
415
378
|
export declare interface RedisProviderOptions {
|
|
416
|
-
/**
|
|
417
|
-
readonly
|
|
418
|
-
/** Bring your own ioredis client (publisher). A `subscriber` is
|
|
419
|
-
* `client.duplicate()`d internally — a RESP connection in subscribe
|
|
420
|
-
* mode can't issue normal commands. */
|
|
421
|
-
readonly client?: RedisLike;
|
|
379
|
+
/** Acquire a consumer from the app's ConnectionRegistry, never a raw socket. */
|
|
380
|
+
readonly pubsub: Effect.Effect<ConnectionPubSub, unknown>;
|
|
422
381
|
}
|
|
423
382
|
|
|
424
383
|
/* Excluded from this release type: resetMemoryBus */
|
|
@@ -435,31 +394,18 @@ export declare interface ResolveBroadcastNamespaceInput {
|
|
|
435
394
|
}
|
|
436
395
|
|
|
437
396
|
export declare interface ResolveBroadcastOptions {
|
|
438
|
-
/** Explicit provider name or a pre-built `BroadcastProvider`. */
|
|
439
397
|
readonly provider?: BroadcastProviderName | BroadcastProvider;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
readonly
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
* ratelimit), so a deployment points broadcast at the same server or its own
|
|
446
|
-
* purely by env. Default `'broadcast'` (→ `BROADCAST_REDIS_URL`). */
|
|
447
|
-
readonly connection?: string;
|
|
398
|
+
readonly connection?: BrokerReference;
|
|
399
|
+
readonly events?: BrokerReference;
|
|
400
|
+
readonly members?: BrokerReference;
|
|
401
|
+
readonly presence?: BrokerReference;
|
|
402
|
+
readonly revalidate?: BrokerReference;
|
|
448
403
|
}
|
|
449
404
|
|
|
450
|
-
|
|
451
|
-
* Resolve the bus backend. Order: an object provider is used verbatim; a
|
|
452
|
-
* named provider builds from `url`/env; absent → `BROADCAST_PROVIDER` env,
|
|
453
|
-
* else inferred from a `redis://` / `nats://` URL, else `memory`.
|
|
454
|
-
*
|
|
455
|
-
* Returns the provider plus the URL it resolved (for the boot banner) and
|
|
456
|
-
* whether the choice is a real cross-replica bus or the single-process
|
|
457
|
-
* memory fallback.
|
|
458
|
-
*/
|
|
459
|
-
export declare const resolveBroadcastProvider: (options: ResolveBroadcastOptions, env: NodeJS.ProcessEnv) => {
|
|
405
|
+
export declare const resolveBroadcastProvider: (options: ResolveBroadcastOptions, environment: NodeJS.ProcessEnv, pubsub?: (role: string) => Effect.Effect<ConnectionPubSub, unknown>) => {
|
|
460
406
|
provider: BroadcastProvider;
|
|
461
|
-
url: string | null;
|
|
462
407
|
crossReplica: boolean;
|
|
408
|
+
selections: ReadonlyArray<BroadcastConnectionSelection>;
|
|
463
409
|
};
|
|
464
410
|
|
|
465
411
|
/** Retry cadence for a subscribe that could not land: 500ms doubling to a 10s
|
package/dist/namespace.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/namespace.ts
|
|
2
2
|
var e = "voltro", t = (e) => e.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, ""), n = (e) => {
|
|
3
3
|
let n = r(e), i = [e.option, e.env].find((e) => e !== void 0 && t(e).length > 0);
|
|
4
4
|
return {
|
|
@@ -18,5 +18,4 @@ var e = "voltro", t = (e) => e.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-"
|
|
|
18
18
|
}
|
|
19
19
|
return e;
|
|
20
20
|
}, i = (e, t) => `${e}:${t}`, a = (e, n) => `${i(e, "events")}:${t(n)}`;
|
|
21
|
-
//#endregion
|
|
22
21
|
export { e as DEFAULT_BROADCAST_NAMESPACE, i as channelFor, n as describeNamespaceResolution, a as eventChannelFor, r as resolveBroadcastNamespace };
|
package/dist/plugin.js
CHANGED
|
@@ -1,67 +1,80 @@
|
|
|
1
1
|
import { resolveBroadcastProvider as e } from "./resolve.js";
|
|
2
2
|
import { Effect as t } from "effect";
|
|
3
3
|
import { definePlugin as n } from "@voltro/protocol";
|
|
4
|
-
|
|
4
|
+
// src/plugin.ts
|
|
5
5
|
var r = ["network:outbound:*"], i = [
|
|
6
6
|
{
|
|
7
|
-
name: "
|
|
7
|
+
name: "BROADCAST_NATS_URL",
|
|
8
8
|
required: !1,
|
|
9
9
|
secret: !0,
|
|
10
|
-
description: "
|
|
11
|
-
example: "
|
|
10
|
+
description: "Named NATS registry endpoint for broadcast; falls back to NATS_URL.",
|
|
11
|
+
example: "nats://localhost:4222"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
name: "BROADCAST_REDIS_URL",
|
|
15
15
|
required: !1,
|
|
16
16
|
secret: !0,
|
|
17
|
-
description: "Named-connection redis url for the bus (the shared <NAME>_REDIS_URL convention, like CACHE_REDIS_URL / KV_REDIS_URL). Used when the redis provider is selected and no
|
|
17
|
+
description: "Named-connection redis url for the bus (the shared <NAME>_REDIS_URL convention, like CACHE_REDIS_URL / KV_REDIS_URL). Used when the redis provider is selected and no explicit connection reference is set; falls back to REDIS_URL. Set it to put broadcast on its own server.",
|
|
18
18
|
example: "redis://localhost:6379"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
name: "BROADCAST_PROVIDER",
|
|
22
22
|
required: !1,
|
|
23
23
|
secret: !1,
|
|
24
|
-
description: "Explicit provider: 'redis' | 'nats' | 'memory'.
|
|
24
|
+
description: "Explicit provider: 'redis' | 'nats' | 'memory'. The selected registry reference must match.",
|
|
25
25
|
example: "redis"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
name: "REDIS_URL",
|
|
29
29
|
required: !1,
|
|
30
30
|
secret: !0,
|
|
31
|
-
description: "Fallback
|
|
31
|
+
description: "Fallback endpoint for registry-owned Redis broadcast (also infers the redis provider when no provider/URL is given).",
|
|
32
32
|
example: "redis://localhost:6379"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
name: "NATS_URL",
|
|
36
36
|
required: !1,
|
|
37
37
|
secret: !0,
|
|
38
|
-
description: "Fallback broker URL used when the resolved provider is nats and no
|
|
38
|
+
description: "Fallback broker URL used when the resolved provider is nats and no BROADCAST_NATS_URL is set (comma-separated for a cluster).",
|
|
39
39
|
example: "nats://localhost:4222"
|
|
40
40
|
}
|
|
41
41
|
], a = (a = {}) => {
|
|
42
|
-
let o = e({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}, process.env);
|
|
42
|
+
let o, s = /* @__PURE__ */ new Map(), c = e(a, process.env, (e) => t.suspend(() => {
|
|
43
|
+
let n = s.get(e);
|
|
44
|
+
return o === void 0 || n === void 0 ? t.fail(/* @__PURE__ */ Error("Broadcast requires an activated app connection registry")) : o.pubsub(n);
|
|
45
|
+
})), l = Object.fromEntries(c.selections.flatMap((e) => e.reference === void 0 ? [] : [[e.role, e.reference]]));
|
|
47
46
|
return {
|
|
48
47
|
...n({
|
|
49
48
|
name: a.name ? `@voltro/plugin-broadcast#${a.name}` : "@voltro/plugin-broadcast",
|
|
50
49
|
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.",
|
|
51
50
|
permissions: r,
|
|
52
51
|
declaredEnv: i,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
...c.selections.length === 0 ? {} : {
|
|
53
|
+
needs: {
|
|
54
|
+
connections: c.selections.map(({ kind: e, role: t }) => ({
|
|
55
|
+
kind: e,
|
|
56
|
+
role: t
|
|
57
|
+
})),
|
|
58
|
+
pubsub: !0
|
|
59
|
+
},
|
|
60
|
+
connectionBindings: l
|
|
61
|
+
},
|
|
62
|
+
onActivate: (e) => t.gen(function* () {
|
|
63
|
+
o = e.connectionRegistry;
|
|
64
|
+
for (let t of c.selections) {
|
|
65
|
+
if (o === void 0) throw Error("Broadcast requires the app connection registry");
|
|
66
|
+
let n = e.connectionReference?.(t.role, t.kind) ?? t.reference ?? (t.kind === "nats" ? `nats:${t.role}` : process.env[`${t.role.toUpperCase()}_REDIS_URL`] ? `redis:${t.role}` : "");
|
|
67
|
+
s.set(t.role, n);
|
|
68
|
+
let r = yield* o.config(n);
|
|
69
|
+
if (t.kind === "redis" && (!("driver" in r) || r.driver !== "resp")) throw Error("Redis broadcast requires a RESP connection");
|
|
70
|
+
}
|
|
71
|
+
c.crossReplica ? e.logger.info("broadcast active", { provider: c.provider.name }) : e.logger.warn("broadcast: resolved to the in-process memory provider — NO cross-replica fan-out. Select a Redis or NATS connection for a cross-replica bus.");
|
|
58
72
|
}),
|
|
59
|
-
onDeactivate: () =>
|
|
73
|
+
onDeactivate: () => c.provider.close()
|
|
60
74
|
}),
|
|
61
75
|
broadcast: {
|
|
62
|
-
provider:
|
|
63
|
-
|
|
64
|
-
crossReplica: o.crossReplica,
|
|
76
|
+
provider: c.provider,
|
|
77
|
+
crossReplica: c.crossReplica,
|
|
65
78
|
namespace: a.namespace
|
|
66
79
|
}
|
|
67
80
|
};
|
|
@@ -69,5 +82,4 @@ var r = ["network:outbound:*"], i = [
|
|
|
69
82
|
for (let t of e) if (s(t)) return t;
|
|
70
83
|
return null;
|
|
71
84
|
}, s = (e) => typeof e.broadcast == "object" && e.broadcast !== null && typeof e.broadcast.provider == "object";
|
|
72
|
-
//#endregion
|
|
73
85
|
export { a as broadcastPlugin, o as getBroadcastPlugin };
|
package/dist/providers.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BroadcastError as e } from "./types.js";
|
|
2
2
|
import { Effect as t } from "effect";
|
|
3
|
-
|
|
3
|
+
// src/providers.ts
|
|
4
4
|
var n = (t, n, r) => new e({
|
|
5
5
|
provider: t,
|
|
6
6
|
message: n,
|
|
@@ -50,117 +50,45 @@ var n = (t, n, r) => new e({
|
|
|
50
50
|
for (let e of n) e();
|
|
51
51
|
})
|
|
52
52
|
};
|
|
53
|
-
}, l = (e) => {
|
|
54
|
-
let
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} catch (e) {
|
|
62
|
-
throw r("redis", "the 'redis' broadcast provider requires the 'ioredis' optional dependency. Install it to enable it.", e);
|
|
63
|
-
}
|
|
64
|
-
t = new n(e.url);
|
|
65
|
-
}
|
|
66
|
-
let n = t.duplicate();
|
|
67
|
-
return t.on("error", () => {}), n.on("error", () => {}), n.on("reconnecting", () => {
|
|
68
|
-
s && (s = !1, o.emit({
|
|
69
|
-
kind: "disconnected",
|
|
70
|
-
detail: "ioredis is re-dialling"
|
|
71
|
-
}));
|
|
72
|
-
}), n.on("ready", () => {
|
|
73
|
-
s || (s = !0, o.emit({ kind: "reconnected" }));
|
|
74
|
-
}), {
|
|
75
|
-
pub: t,
|
|
76
|
-
sub: n
|
|
77
|
-
};
|
|
78
|
-
})().catch((e) => {
|
|
79
|
-
throw a = null, e;
|
|
80
|
-
}), a);
|
|
81
|
-
return {
|
|
82
|
-
name: "redis",
|
|
83
|
-
onTransportEvent: o.subscribe,
|
|
84
|
-
publish: (e, r) => t.tryPromise({
|
|
85
|
-
try: async () => {
|
|
86
|
-
let { pub: t } = await c();
|
|
87
|
-
await t.publish(e, r);
|
|
88
|
-
},
|
|
89
|
-
catch: (e) => n("redis", `publish failed: ${String(e?.message ?? e)}`, e)
|
|
90
|
-
}),
|
|
91
|
-
subscribe: (e, r) => t.tryPromise({
|
|
92
|
-
try: async () => {
|
|
93
|
-
let { sub: t } = await c(), n = (t, n) => {
|
|
94
|
-
t === e && r(n);
|
|
95
|
-
};
|
|
96
|
-
return t.on("message", n), await t.subscribe(e), () => {
|
|
97
|
-
t.off("message", n), t.unsubscribe(e).catch(() => void 0);
|
|
98
|
-
};
|
|
99
|
-
},
|
|
100
|
-
catch: (e) => n("redis", `subscribe failed: ${String(e?.message ?? e)}`, e)
|
|
101
|
-
}),
|
|
102
|
-
close: () => t.promise(async () => {
|
|
103
|
-
if (a) try {
|
|
104
|
-
let { pub: t, sub: n } = await a;
|
|
105
|
-
await n.quit(), e.client || await t.quit();
|
|
106
|
-
} catch {}
|
|
107
|
-
})
|
|
53
|
+
}, l = (e, a) => {
|
|
54
|
+
let o = i(), s = /* @__PURE__ */ new Set(), c, l, u = !1, d, f = (e) => (s.add(e), e.then(() => s.delete(e), () => s.delete(e)), e), p = (n) => {
|
|
55
|
+
if (u) return Promise.reject(r(e, "Broadcast consumer is closed"));
|
|
56
|
+
if (c !== void 0) return c;
|
|
57
|
+
let i = t.runPromiseWith(n)(a.pubsub).then((e) => (l = e.onTransportEvent(o.emit), e));
|
|
58
|
+
return c = i, i.catch(() => {
|
|
59
|
+
c === i && (c = void 0);
|
|
60
|
+
}), i;
|
|
108
61
|
};
|
|
109
|
-
}, u = new TextEncoder(), d = new TextDecoder(), f = (e) => {
|
|
110
|
-
let a = null, o = i(), s = !0, c = (e) => {
|
|
111
|
-
(async () => {
|
|
112
|
-
try {
|
|
113
|
-
for await (let t of e.status()) t.type === "disconnect" && s ? (s = !1, o.emit({
|
|
114
|
-
kind: "disconnected",
|
|
115
|
-
detail: "nats disconnected"
|
|
116
|
-
})) : t.type === "reconnect" && !s && (s = !0, o.emit({ kind: "reconnected" }));
|
|
117
|
-
} catch {}
|
|
118
|
-
})();
|
|
119
|
-
}, l = () => import("@nats-io/transport-node").catch((e) => {
|
|
120
|
-
throw r("nats", "the 'nats' broadcast provider requires the '@nats-io/transport-node' optional dependency. Install it to enable it.", e);
|
|
121
|
-
}), f = () => a || (a = (async () => {
|
|
122
|
-
if (e.connection) return e.connection;
|
|
123
|
-
let t = await (await l()).connect({
|
|
124
|
-
servers: e.url,
|
|
125
|
-
maxReconnectAttempts: -1,
|
|
126
|
-
waitOnFirstConnect: !0,
|
|
127
|
-
reconnectTimeWait: 2e3
|
|
128
|
-
});
|
|
129
|
-
return c(t), t;
|
|
130
|
-
})().catch((e) => {
|
|
131
|
-
throw a = null, e;
|
|
132
|
-
}), a);
|
|
133
62
|
return {
|
|
134
|
-
name:
|
|
63
|
+
name: e,
|
|
135
64
|
onTransportEvent: o.subscribe,
|
|
136
|
-
publish: (
|
|
137
|
-
try: async () => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
65
|
+
publish: (i, a) => t.flatMap(t.context(), (o) => t.tryPromise({
|
|
66
|
+
try: () => f((async () => {
|
|
67
|
+
let n = await p(o);
|
|
68
|
+
if (u) throw r(e, "Broadcast consumer is closed");
|
|
69
|
+
await t.runPromiseWith(o)(n.publish(i, a));
|
|
70
|
+
})()),
|
|
71
|
+
catch: () => n(e, `${e} publication failed`)
|
|
72
|
+
})),
|
|
73
|
+
subscribe: (i, a) => t.flatMap(t.context(), (o) => t.tryPromise({
|
|
74
|
+
try: () => f((async () => {
|
|
75
|
+
let n = await p(o);
|
|
76
|
+
if (u) throw r(e, "Broadcast consumer is closed");
|
|
77
|
+
let s = !0, c = await t.runPromiseWith(o)(n.subscribe(i, (e) => {
|
|
78
|
+
s && !u && a(e);
|
|
79
|
+
}));
|
|
80
|
+
if (u) throw s = !1, await t.runPromiseWith(o)(c), r(e, "Broadcast consumer is closed");
|
|
81
|
+
return () => {
|
|
82
|
+
s && (s = !1, f(t.runPromiseWith(o)(c)).catch(() => void 0));
|
|
153
83
|
};
|
|
154
|
-
},
|
|
155
|
-
catch: (
|
|
156
|
-
}),
|
|
157
|
-
close: () => t.promise(async () => {
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
e.connection || await t.drain();
|
|
84
|
+
})()),
|
|
85
|
+
catch: () => n(e, `${e} subscription failed`)
|
|
86
|
+
})),
|
|
87
|
+
close: () => t.flatMap(t.context(), (e) => t.promise(() => d ??= (async () => {
|
|
88
|
+
if (u = !0, await Promise.allSettled([...s]), l?.(), c !== void 0) try {
|
|
89
|
+
await t.runPromiseWith(e)((await c).close);
|
|
161
90
|
} catch {}
|
|
162
|
-
})
|
|
91
|
+
})()))
|
|
163
92
|
};
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export { c as memoryProvider, f as natsProvider, l as redisProvider, s as resetMemoryBus };
|
|
93
|
+
}, u = (e) => l("redis", e), d = (e) => l("nats", e);
|
|
94
|
+
export { c as memoryProvider, d as natsProvider, u as redisProvider, s as resetMemoryBus };
|
package/dist/publishQueue.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect as e } from "effect";
|
|
2
|
-
|
|
2
|
+
// src/publishQueue.ts
|
|
3
3
|
var t = (t, n, r, i, a = {}, o = () => {}) => {
|
|
4
4
|
let s = a.maxMessages ?? 1024, c = a.maxBytes ?? 8388608, l = a.timeoutMs ?? 5e3, u = a.shutdownTimeoutMs ?? 1e3;
|
|
5
5
|
for (let [e, t] of Object.entries({
|
|
@@ -60,5 +60,4 @@ var t = (t, n, r, i, a = {}, o = () => {}) => {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
};
|
|
63
|
-
//#endregion
|
|
64
63
|
export { t as createPublishQueue };
|
package/dist/resolve.js
CHANGED
|
@@ -1,53 +1,69 @@
|
|
|
1
1
|
import { memoryProvider as e, natsProvider as t, redisProvider as n } from "./providers.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import { Effect as r } from "effect";
|
|
3
|
+
// src/resolve.ts
|
|
4
|
+
var i = (e) => {
|
|
5
|
+
if (!/^(redis|nats):[A-Za-z][A-Za-z0-9_-]*$/.test(e)) throw Error("Broadcast connections require redis:name or nats:name references");
|
|
6
|
+
return e.startsWith("nats:") ? "nats" : "redis";
|
|
7
|
+
}, a = (a, o, s = () => r.fail(/* @__PURE__ */ Error("Broadcast requires the app connection registry"))) => {
|
|
8
|
+
let c = [], l = (e, i, a) => {
|
|
9
|
+
c.push({
|
|
10
|
+
kind: e,
|
|
11
|
+
role: i,
|
|
12
|
+
...a === void 0 ? {} : { reference: a }
|
|
13
|
+
});
|
|
14
|
+
let o = r.suspend(() => s(i));
|
|
15
|
+
return e === "nats" ? t({ pubsub: o }) : n({ pubsub: o });
|
|
16
|
+
}, u;
|
|
17
|
+
if (typeof a.provider == "object") {
|
|
18
|
+
if (a.connection !== void 0) throw Error("A custom Broadcast provider cannot also select a registry connection");
|
|
19
|
+
u = a.provider;
|
|
20
|
+
} else {
|
|
21
|
+
let t = a.provider ?? o.BROADCAST_PROVIDER;
|
|
22
|
+
if (t !== void 0 && ![
|
|
23
|
+
"redis",
|
|
24
|
+
"nats",
|
|
25
|
+
"memory"
|
|
26
|
+
].includes(t)) throw Error("Broadcast provider must be redis, nats or memory");
|
|
27
|
+
let n = a.connection === void 0 ? void 0 : i(a.connection), r = t ?? n ?? (o.BROADCAST_NATS_URL || o.NATS_URL ? "nats" : o.BROADCAST_REDIS_URL || o.REDIS_URL ? "redis" : "memory");
|
|
28
|
+
if (n !== void 0 && r !== n) throw Error("Broadcast provider must match its connection kind");
|
|
29
|
+
u = r === "memory" ? e() : l(r === "nats" ? "nats" : "redis", "broadcast", a.connection);
|
|
11
30
|
}
|
|
12
|
-
let
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
provider: e(),
|
|
23
|
-
url: null,
|
|
24
|
-
crossReplica: !1
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
return {
|
|
28
|
-
provider: n({ url: t }),
|
|
29
|
-
url: t,
|
|
30
|
-
crossReplica: !0
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
case "nats": {
|
|
34
|
-
let n = o ?? a.NATS_URL;
|
|
35
|
-
return n ? {
|
|
36
|
-
provider: t({ url: n }),
|
|
37
|
-
url: n,
|
|
38
|
-
crossReplica: !0
|
|
39
|
-
} : {
|
|
40
|
-
provider: e(),
|
|
41
|
-
url: null,
|
|
42
|
-
crossReplica: !1
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
default: return {
|
|
46
|
-
provider: e(),
|
|
47
|
-
url: null,
|
|
48
|
-
crossReplica: !1
|
|
49
|
-
};
|
|
31
|
+
let d = /* @__PURE__ */ new Map();
|
|
32
|
+
for (let e of [
|
|
33
|
+
"events",
|
|
34
|
+
"members",
|
|
35
|
+
"presence",
|
|
36
|
+
"revalidate"
|
|
37
|
+
]) {
|
|
38
|
+
let t = a[e];
|
|
39
|
+
t !== void 0 && d.set(e, l(i(t), e, t));
|
|
50
40
|
}
|
|
41
|
+
if (d.size === 0) return {
|
|
42
|
+
provider: u,
|
|
43
|
+
crossReplica: u.name !== "memory",
|
|
44
|
+
selections: c
|
|
45
|
+
};
|
|
46
|
+
let f = [.../* @__PURE__ */ new Set([u, ...d.values()])], p = (e) => {
|
|
47
|
+
let t = e.slice(e.indexOf(":") + 1).split(":")[0];
|
|
48
|
+
return d.get(t) ?? u;
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
provider: {
|
|
52
|
+
name: [...new Set(f.map((e) => e.name))].join("+"),
|
|
53
|
+
publish: (e, t) => p(e).publish(e, t),
|
|
54
|
+
subscribe: (e, t) => p(e).subscribe(e, t),
|
|
55
|
+
onTransportEvent: (e) => {
|
|
56
|
+
let t = /* @__PURE__ */ new WeakSet(), n = f.flatMap((n) => n.onTransportEvent === void 0 ? [] : [n.onTransportEvent((n) => {
|
|
57
|
+
t.has(n) || (t.add(n), e(n));
|
|
58
|
+
})]);
|
|
59
|
+
return () => {
|
|
60
|
+
for (let e of n) e();
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
close: () => r.forEach(f, (e) => e.close(), { discard: !0 }).pipe(r.asVoid)
|
|
64
|
+
},
|
|
65
|
+
crossReplica: f.some((e) => e.name !== "memory"),
|
|
66
|
+
selections: c
|
|
67
|
+
};
|
|
51
68
|
};
|
|
52
|
-
|
|
53
|
-
export { i as resolveBroadcastProvider };
|
|
69
|
+
export { a as resolveBroadcastProvider };
|
package/dist/types.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_BROADCAST_NAMESPACE as e, channelFor as t } from "./namespace.js";
|
|
2
2
|
import { Schema as n } from "effect";
|
|
3
|
-
|
|
3
|
+
// src/types.ts
|
|
4
4
|
var r = class extends n.TaggedError()("BroadcastError", {
|
|
5
5
|
provider: n.String,
|
|
6
6
|
message: n.String,
|
|
7
7
|
transient: n.Boolean,
|
|
8
8
|
cause: n.optional(n.Unknown)
|
|
9
9
|
}) {}, i = t(e, "changes");
|
|
10
|
-
//#endregion
|
|
11
10
|
export { i as BROADCAST_CHANNEL, r as BroadcastError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-broadcast",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.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,12 @@
|
|
|
33
33
|
"node": ">=24.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@voltro/database": "0.
|
|
37
|
-
"@voltro/protocol": "0.
|
|
38
|
-
|
|
39
|
-
"optionalDependencies": {
|
|
40
|
-
"@nats-io/transport-node": "^3.4.0",
|
|
41
|
-
"ioredis": "^5.11.1"
|
|
36
|
+
"@voltro/database": "0.88.0",
|
|
37
|
+
"@voltro/protocol": "0.88.0",
|
|
38
|
+
"@voltro/kv": "0.88.0"
|
|
42
39
|
},
|
|
43
40
|
"peerDependencies": {
|
|
44
|
-
"effect": "4.0
|
|
41
|
+
"effect": "4.1.0"
|
|
45
42
|
},
|
|
46
43
|
"publishConfig": {
|
|
47
44
|
"access": "public"
|