@macula-io/ts 0.13.4 → 0.14.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/README.md +18 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pool.d.ts +123 -0
- package/dist/pool.js +538 -0
- package/dist/pool.js.map +1 -0
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/@macula-io+ts.node +0 -0
- package/prebuilds/darwin-x64/@macula-io+ts.node +0 -0
- package/prebuilds/win32-x64/@macula-io+ts.node +0 -0
package/README.md
CHANGED
|
@@ -235,6 +235,16 @@ fixed) is in [CHANGELOG.md](CHANGELOG.md).
|
|
|
235
235
|
is why `keepAdvertisedDirect()` is a standalone function rather than a
|
|
236
236
|
`Session` method.
|
|
237
237
|
|
|
238
|
+
- **Pool** — `Pool.connect(seeds, controlIdentity, opts)` holds live
|
|
239
|
+
connections to every configured seed concurrently (not
|
|
240
|
+
dial-one-then-fallback-on-failure), each independently monitored and
|
|
241
|
+
respawned with backoff on disconnect; `publish()`/`call()` fan out
|
|
242
|
+
over live links, `subscribe()` re-establishes automatically on
|
|
243
|
+
reconnect. Ports `macula/src/client/macula_client.erl`'s pool design;
|
|
244
|
+
see `pool.ts`'s own module doc for why it's a set of role-scoped
|
|
245
|
+
`Session`s per seed rather than one, given the single-reader
|
|
246
|
+
constraint below.
|
|
247
|
+
|
|
238
248
|
Every item above is live-verified against the real production fleet
|
|
239
249
|
(`station-de-frankfurt.macula.io`), including negative/error paths and,
|
|
240
250
|
where applicable, the actual packaged npm tarball rather than only the
|
|
@@ -257,21 +267,23 @@ arbitrary payload" function (see above for why), a `station_endpoint`
|
|
|
257
267
|
record builder (macula-go has none either — stations publish those
|
|
258
268
|
themselves, not clients), and `Pinned`/`Insecure` trust modes (`WebPKI`
|
|
259
269
|
only so far). Multiple concurrent `subscribe()` topics on one `Session`
|
|
260
|
-
|
|
261
|
-
`Session` at a time; open a second `Session`
|
|
262
|
-
|
|
263
|
-
|
|
270
|
+
still isn't supported at the `Session` level itself — one `subscribe()`
|
|
271
|
+
(like one `serve()`) per `Session` at a time; open a second `Session`
|
|
272
|
+
for a second topic (`Pool`, above, does exactly this internally to give
|
|
273
|
+
each tracked topic its own session). Each of these is a separate, later
|
|
274
|
+
slice of work built on top of a working `Session`.
|
|
264
275
|
|
|
265
276
|
## Testing
|
|
266
277
|
|
|
267
278
|
```bash
|
|
268
279
|
npx vitest run # default suite, no network
|
|
269
|
-
npm run test:live # MACULA_TS_LIVE=1 vitest run src/session.live.test.ts src/rpc.live.test.ts src/dht.live.test.ts src/pubsub.live.test.ts src/content.live.test.ts src/ucan.live.test.ts src/directdial.live.test.ts
|
|
280
|
+
npm run test:live # MACULA_TS_LIVE=1 vitest run src/session.live.test.ts src/rpc.live.test.ts src/dht.live.test.ts src/pubsub.live.test.ts src/content.live.test.ts src/ucan.live.test.ts src/directdial.live.test.ts src/pool.live.test.ts
|
|
270
281
|
```
|
|
271
282
|
|
|
272
283
|
`src/session.live.test.ts`, `src/rpc.live.test.ts`, `src/dht.live.test.ts`,
|
|
273
284
|
`src/pubsub.live.test.ts`, `src/content.live.test.ts`,
|
|
274
|
-
`src/ucan.live.test.ts`,
|
|
285
|
+
`src/ucan.live.test.ts`, `src/directdial.live.test.ts`, and
|
|
286
|
+
`src/pool.live.test.ts` hit the real
|
|
275
287
|
production fleet and are **not** part of default `npm test`/CI — opt in
|
|
276
288
|
explicitly, gated behind `MACULA_TS_LIVE`. Same convention as macula-go's
|
|
277
289
|
`live` build tag, macula-rust's `#[ignore]`, and macula-dotnet's
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { Identity } from "./identity.js";
|
|
2
2
|
export { Session, type CallOptions } from "./session.js";
|
|
3
|
+
export { Pool, NoHealthyStationError, type Seed, type PoolOptions, type PoolStatus } from "./pool.js";
|
|
3
4
|
export { MaculaCallError, type JsonValue, type Bolt4ErrorInfo } from "./rpc.js";
|
|
4
5
|
export { DhtRecordType, DHT_DEFAULT_TTL_MS, type DhtRecord } from "./dht.js";
|
|
5
6
|
export type { PublishOptions, PubsubEvent } from "./pubsub.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { Identity } from "./identity.js";
|
|
2
2
|
export { Session } from "./session.js";
|
|
3
|
+
export { Pool, NoHealthyStationError } from "./pool.js";
|
|
3
4
|
export { MaculaCallError } from "./rpc.js";
|
|
4
5
|
export { DhtRecordType, DHT_DEFAULT_TTL_MS } from "./dht.js";
|
|
5
6
|
export { ContentNotFoundError } from "./content.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAoB,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,eAAe,EAAuC,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAkB,MAAM,UAAU,CAAC;AAE7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,IAAI,EAAiE,MAAM,WAAW,CAAC;AAChG,OAAO,EACL,oBAAoB,EACpB,kCAAkC,GAInC,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAoB,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,qBAAqB,EAAgD,MAAM,WAAW,CAAC;AACtG,OAAO,EAAE,eAAe,EAAuC,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAkB,MAAM,UAAU,CAAC;AAE7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,IAAI,EAAiE,MAAM,WAAW,CAAC;AAChG,OAAO,EACL,oBAAoB,EACpB,kCAAkC,GAInC,MAAM,iBAAiB,CAAC"}
|
package/dist/pool.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Identity } from "./identity.js";
|
|
2
|
+
import type { PubsubEvent } from "./pubsub.js";
|
|
3
|
+
import { type JsonValue } from "./rpc.js";
|
|
4
|
+
/** One configured connection target. */
|
|
5
|
+
export interface Seed {
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
}
|
|
9
|
+
export interface PoolOptions {
|
|
10
|
+
/** How many currently-live control links a publish() fans out to
|
|
11
|
+
* before resolving -- partial success counts as success, matching
|
|
12
|
+
* macula_client:publish/5's own documented semantics. Default 1.
|
|
13
|
+
* NOT YET FULLY AT PARITY above 1: the Erlang reference stamps ONE
|
|
14
|
+
* seq across every replica specifically so a receiver's own
|
|
15
|
+
* (publisher, seq, topic) dedup collapses the redundant copies into
|
|
16
|
+
* one event. This SDK's Session.publish() has no caller-supplied-seq
|
|
17
|
+
* parameter to do the same (each call mints its own fresh seq), so
|
|
18
|
+
* setting this above 1 would deliver N distinct, non-deduplicable
|
|
19
|
+
* copies to every subscriber -- clamped to 1 until that's fixed. */
|
|
20
|
+
replicationFactor?: number;
|
|
21
|
+
/** How long a recorded (realm, publisher, seq, topic) key is treated
|
|
22
|
+
* as a duplicate before it ages out. Default 60_000, matching the
|
|
23
|
+
* Erlang reference's own `dedup_window_ms` default. */
|
|
24
|
+
dedupWindowMs?: number;
|
|
25
|
+
/** How often the dedup table is swept for expired entries. Default
|
|
26
|
+
* 30_000, matching the Erlang reference's own `dedup_sweep_ms`. */
|
|
27
|
+
dedupSweepMs?: number;
|
|
28
|
+
/** How often a live control link is health-checked (see
|
|
29
|
+
* #armHealthCheck's own doc for why publish() alone can't be trusted
|
|
30
|
+
* to ever notice a dead connection). Default 10_000. */
|
|
31
|
+
healthCheckIntervalMs?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface PoolStatus {
|
|
34
|
+
/** Seeds whose control link is currently connected. */
|
|
35
|
+
healthyLinks: number;
|
|
36
|
+
/** Seeds whose control link is not currently connected (connecting
|
|
37
|
+
* or backing off). Every configured seed is exactly one or the
|
|
38
|
+
* other -- a link backs off and retries forever, it is never given
|
|
39
|
+
* up on and dropped from the pool, same as the Erlang reference. */
|
|
40
|
+
failedLinks: number;
|
|
41
|
+
}
|
|
42
|
+
/** Thrown by publish()/call() when the pool has zero live control links
|
|
43
|
+
* to try -- a distinct, identifiable condition from any one link's own
|
|
44
|
+
* transient error, matching macula_client:publish/5's own `{error,
|
|
45
|
+
* {transient, no_healthy_station}}`. */
|
|
46
|
+
export declare class NoHealthyStationError extends Error {
|
|
47
|
+
constructor();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A resilient multi-station client: live connections to every configured
|
|
51
|
+
* seed held concurrently, each independently monitored and respawned
|
|
52
|
+
* with backoff on disconnect, every tracked subscription re-established
|
|
53
|
+
* automatically when its own link reconnects. See this module's own
|
|
54
|
+
* header doc for the full design, why a "link" is a small role-scoped
|
|
55
|
+
* session set rather than one Session, and its one deliberate deviation
|
|
56
|
+
* from the Erlang reference (topic-scoped dedup).
|
|
57
|
+
*/
|
|
58
|
+
export declare class Pool {
|
|
59
|
+
#private;
|
|
60
|
+
private constructor();
|
|
61
|
+
/** Dials the control role against every seed concurrently under
|
|
62
|
+
* `controlIdentity` (the caller's own identity -- publish()/call()
|
|
63
|
+
* are attributed to it on the wire) and resolves once every seed's
|
|
64
|
+
* FIRST connect attempt has settled (success or a logged failure
|
|
65
|
+
* headed into backoff) -- matches tapRoom()'s own "await the first
|
|
66
|
+
* attempt, not the eventual outcome" reasoning (macula-mcp's
|
|
67
|
+
* lobby_observer.ts): a caller that publishes/calls immediately
|
|
68
|
+
* after connect() must not race links still mid-handshake. */
|
|
69
|
+
static connect(seeds: Seed[], controlIdentity: Identity, opts?: PoolOptions): Promise<Pool>;
|
|
70
|
+
/** Publishes to `replicationFactor` currently-live control links
|
|
71
|
+
* (default 1); partial success counts as success. A link whose
|
|
72
|
+
* publish attempt fails is marked for respawn immediately -- this is
|
|
73
|
+
* this v1's only liveness signal for the control role, since it
|
|
74
|
+
* cannot also carry a liveness-only subscribe (see this module's own
|
|
75
|
+
* header doc). Throws NoHealthyStationError if zero links are live. */
|
|
76
|
+
publish(realm: string | undefined, topic: string, payload: JsonValue, opts?: {
|
|
77
|
+
ttlMs?: number;
|
|
78
|
+
}): Promise<void>;
|
|
79
|
+
/** Calls `procedure` against the pool's live control links in order
|
|
80
|
+
* until one succeeds or all have been tried. Throws
|
|
81
|
+
* NoHealthyStationError if zero links are live.
|
|
82
|
+
*
|
|
83
|
+
* A `MaculaCallError` (a real BOLT#4 response -- e.g.
|
|
84
|
+
* unknown_next_peer, unauthorized) does NOT mark its link for
|
|
85
|
+
* respawn: the connection plainly worked, it answered. Found in
|
|
86
|
+
* review 2026-09-05: treating every thrown error as evidence of a
|
|
87
|
+
* dead connection meant a single genuinely-answered error (a
|
|
88
|
+
* procedure nobody serves, a bad realm, a gated call this identity
|
|
89
|
+
* isn't authorized for) tore down and reconnected EVERY live control
|
|
90
|
+
* link in turn as call() moved through them re-trying the same
|
|
91
|
+
* doomed call -- and for a non-idempotent provider handler, actually
|
|
92
|
+
* re-invoked it on each one. Only an error that never got a
|
|
93
|
+
* wire-level answer at all (the same distinction call()'s own doc on
|
|
94
|
+
* Session draws) is treated as a dead link. Each link's own `session`
|
|
95
|
+
* reference is re-checked before scheduling a reconnect, in case a
|
|
96
|
+
* concurrent operation already superseded it between this loop
|
|
97
|
+
* starting and this iteration running. */
|
|
98
|
+
call(realm: string | undefined, procedure: string, payload: JsonValue, opts?: {
|
|
99
|
+
deadlineMs?: number;
|
|
100
|
+
}): Promise<JsonValue>;
|
|
101
|
+
/** Subscribes `handler` to `(realm, topic)`: opens one subscribe-only
|
|
102
|
+
* session against every configured seed, replayed automatically on
|
|
103
|
+
* every future respawn. Mints a dedicated identity for this topic by
|
|
104
|
+
* default (disposed on unsubscribe); pass `identity` to supply the
|
|
105
|
+
* pool's own instead (e.g. for a stable, caller-controlled identity
|
|
106
|
+
* across restarts, matching macula-mcp's own observeRoomIdentityPath
|
|
107
|
+
* pattern) -- the pool never disposes an identity it didn't mint.
|
|
108
|
+
* Returns an unsubscribe function. */
|
|
109
|
+
subscribe(realm: string | undefined, topic: string, handler: (evt: PubsubEvent) => void, identity?: Identity): Promise<() => Promise<void>>;
|
|
110
|
+
/** Live/backing-off CONTROL link counts (publish/call reachability).
|
|
111
|
+
* Every configured seed is exactly one or the other. Per-topic
|
|
112
|
+
* subscription link health is not reflected here -- inspect a
|
|
113
|
+
* specific subscription's own behavior (events arriving or not)
|
|
114
|
+
* instead; exposing N independent per-topic health vectors here
|
|
115
|
+
* would not simplify what a caller actually needs to know. */
|
|
116
|
+
status(): PoolStatus;
|
|
117
|
+
/** Closes every control and subscription link and disposes every
|
|
118
|
+
* identity this pool owns (the caller-supplied control identity
|
|
119
|
+
* included). Awaits each link's in-flight connect/reconnect first,
|
|
120
|
+
* so a still-connecting link never has its identity yanked out from
|
|
121
|
+
* under it. */
|
|
122
|
+
close(): Promise<void>;
|
|
123
|
+
}
|
package/dist/pool.js
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
// Resilient multi-station client -- ports macula/src/client/macula_client.erl's
|
|
2
|
+
// pool design (N live links, monitor + respawn-with-backoff, subscription
|
|
3
|
+
// replay on reconnect) into this SDK. `Session` (session.ts) and
|
|
4
|
+
// `connectWithFallback`/`withSession` (this SDK's consumers, e.g.
|
|
5
|
+
// macula-mcp's macula_ts_client.ts) are both dial-one-then-use primitives --
|
|
6
|
+
// correct for a one-shot operation, wrong for anything that needs to stay
|
|
7
|
+
// reachable across a station's own outage. A Pool holds live connections to
|
|
8
|
+
// every configured seed concurrently, not one-then-fallback-on-first-failure;
|
|
9
|
+
// a seed that never connects and a link whose connection later dies are the
|
|
10
|
+
// same condition (backoff, retry forever) rather than two different code
|
|
11
|
+
// paths.
|
|
12
|
+
//
|
|
13
|
+
// == Why this isn't "one Session per seed" ==
|
|
14
|
+
//
|
|
15
|
+
// Erlang's macula_client can put N tracked (Realm, Topic) subscriptions
|
|
16
|
+
// (plus Call, plus Advertise) on ONE gen_server-owned link, because a BEAM
|
|
17
|
+
// mailbox demuxes every message type for that one process regardless of how
|
|
18
|
+
// many concerns it's juggling. This SDK's Session has no equivalent: its
|
|
19
|
+
// underlying control stream supports exactly one concurrent reader --
|
|
20
|
+
// `subscribe()` throws outright if called twice on the same Session
|
|
21
|
+
// ("a second concurrent subscribe() on the same Session races; open a
|
|
22
|
+
// second Session instead"), and `call()`/`serve()` are mutually exclusive
|
|
23
|
+
// with an active subscribe on the same Session for the identical reason
|
|
24
|
+
// (macula-go's FrameStream does a raw sequential read into a shared buffer,
|
|
25
|
+
// no per-caller demux). Confirmed live 2026-09-04 while porting the Go side
|
|
26
|
+
// of this same design: this is a wire-level constraint, not a TS-specific
|
|
27
|
+
// gap -- macula-mcp already works around it today by giving lobby_observer.ts
|
|
28
|
+
// a dedicated identity+Session PER ROOM TOPIC rather than multiplexing one
|
|
29
|
+
// connection.
|
|
30
|
+
//
|
|
31
|
+
// So a "link" to one station here is a small SET of role-scoped sessions,
|
|
32
|
+
// not one session:
|
|
33
|
+
// - one "control" session (this pool's own caller-supplied identity),
|
|
34
|
+
// used for publish() and call() and NEVER subscribed on -- publish() is
|
|
35
|
+
// write-only (explicitly unguarded by the exclusivity rule) and call()'s
|
|
36
|
+
// own internal queue already serialises concurrent calls safely, so one
|
|
37
|
+
// control session per station is enough regardless of call volume.
|
|
38
|
+
// - one additional session PER CURRENTLY-TRACKED TOPIC, under an identity
|
|
39
|
+
// derived from that topic (shared across every station's copy of that
|
|
40
|
+
// topic's session -- reused across stations is safe, since a station's
|
|
41
|
+
// own per-identity dedupe only kicks a SECOND connection to THAT SAME
|
|
42
|
+
// station, never across different stations). Each does exactly one
|
|
43
|
+
// subscribe() and nothing else.
|
|
44
|
+
// Every one of these sessions is independently monitored and respawned with
|
|
45
|
+
// backoff; a topic session dying just re-subscribes its own one topic on
|
|
46
|
+
// respawn (no "replay N subscriptions onto a survivor" step needed, since
|
|
47
|
+
// each session only ever carried one). A control link can't also carry a
|
|
48
|
+
// liveness-only subscribe without reintroducing the exclusivity problem it
|
|
49
|
+
// exists to avoid, and a failed call() genuinely does surface a dead
|
|
50
|
+
// connection -- but publish() does not: found live 2026-09-04 that a
|
|
51
|
+
// publish() attempt on a session the station had already kicked can still
|
|
52
|
+
// locally "succeed" (the frame is handed off before the QUIC stack notices
|
|
53
|
+
// its own connection is gone), so a publish-only workload could go
|
|
54
|
+
// arbitrarily long without ever discovering a dead control link. Every
|
|
55
|
+
// live control link is therefore also health-checked on a timer
|
|
56
|
+
// (#armHealthCheck's own doc has the mechanism) -- a periodic call() to a
|
|
57
|
+
// procedure name guaranteed never to be advertised, whose failure is
|
|
58
|
+
// inspected to tell "a real BOLT#4 unknown_next_peer came back" (alive)
|
|
59
|
+
// apart from "this never reached anyone at all" (dead, reconnect).
|
|
60
|
+
//
|
|
61
|
+
// == Deliberate deviation from the Erlang reference ==
|
|
62
|
+
//
|
|
63
|
+
// macula_client's own inbound-event dedup keys on (Realm, Publisher, Seq)
|
|
64
|
+
// alone, with no Topic. That is the exact shape of a real bug found and
|
|
65
|
+
// fixed on the station side 2026-09-04 (macula-station's event_dedup): an
|
|
66
|
+
// identity's own auto-published facts and its application-level publishes
|
|
67
|
+
// can share one seq-counter space, and two different topics publishing the
|
|
68
|
+
// same seq collide under a topic-blind key. This pool's own dedup includes
|
|
69
|
+
// Topic from the start.
|
|
70
|
+
import { Identity } from "./identity.js";
|
|
71
|
+
import { MaculaCallError } from "./rpc.js";
|
|
72
|
+
import { Session } from "./session.js";
|
|
73
|
+
/** Thrown by publish()/call() when the pool has zero live control links
|
|
74
|
+
* to try -- a distinct, identifiable condition from any one link's own
|
|
75
|
+
* transient error, matching macula_client:publish/5's own `{error,
|
|
76
|
+
* {transient, no_healthy_station}}`. */
|
|
77
|
+
export class NoHealthyStationError extends Error {
|
|
78
|
+
constructor() {
|
|
79
|
+
super("macula-ts: pool has no currently-live links");
|
|
80
|
+
this.name = "NoHealthyStationError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const RECONNECT_BASE_MS = 1_000;
|
|
84
|
+
const RECONNECT_MAX_MS = 30_000;
|
|
85
|
+
/** How often a LIVE control link is health-checked (see #armHealthCheck's
|
|
86
|
+
* own doc for why publish() alone can't be trusted to ever notice a dead
|
|
87
|
+
* connection). Topic-role links need no equivalent -- their own
|
|
88
|
+
* subscribe() already gives them a real, immediate onClosed signal. */
|
|
89
|
+
const HEALTH_CHECK_INTERVAL_MS = 10_000;
|
|
90
|
+
function subKey(realm, topic) {
|
|
91
|
+
return `${realm ?? ""}\0${topic}`;
|
|
92
|
+
}
|
|
93
|
+
function dedupKey(realm, publisher, seq, topic) {
|
|
94
|
+
return `${realm ?? ""}\0${Buffer.from(publisher).toString("hex")}\0${seq}\0${topic}`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A resilient multi-station client: live connections to every configured
|
|
98
|
+
* seed held concurrently, each independently monitored and respawned
|
|
99
|
+
* with backoff on disconnect, every tracked subscription re-established
|
|
100
|
+
* automatically when its own link reconnects. See this module's own
|
|
101
|
+
* header doc for the full design, why a "link" is a small role-scoped
|
|
102
|
+
* session set rather than one Session, and its one deliberate deviation
|
|
103
|
+
* from the Erlang reference (topic-scoped dedup).
|
|
104
|
+
*/
|
|
105
|
+
export class Pool {
|
|
106
|
+
#controlIdentity;
|
|
107
|
+
#controlLinks;
|
|
108
|
+
#seeds;
|
|
109
|
+
#subscriptions = new Map();
|
|
110
|
+
#dedup = new Map();
|
|
111
|
+
#sweepTimer;
|
|
112
|
+
#replicationFactor;
|
|
113
|
+
#dedupWindowMs;
|
|
114
|
+
#healthCheckIntervalMs;
|
|
115
|
+
#closed = false;
|
|
116
|
+
constructor(controlIdentity, seeds, opts) {
|
|
117
|
+
this.#controlIdentity = controlIdentity;
|
|
118
|
+
this.#seeds = seeds;
|
|
119
|
+
const requestedReplication = opts.replicationFactor ?? 1;
|
|
120
|
+
if (requestedReplication !== 1) {
|
|
121
|
+
console.error(`macula-ts pool: replicationFactor ${requestedReplication} is not yet supported (each replica would publish ` +
|
|
122
|
+
"a distinct seq, so a receiver's dedup can't collapse them into one event) -- clamping to 1.");
|
|
123
|
+
}
|
|
124
|
+
this.#replicationFactor = 1;
|
|
125
|
+
this.#dedupWindowMs = opts.dedupWindowMs ?? 60_000;
|
|
126
|
+
this.#healthCheckIntervalMs = opts.healthCheckIntervalMs ?? HEALTH_CHECK_INTERVAL_MS;
|
|
127
|
+
this.#controlLinks = seeds.map((seed) => this.#newRoleLink(seed, controlIdentity, undefined));
|
|
128
|
+
const sweepMs = opts.dedupSweepMs ?? 30_000;
|
|
129
|
+
this.#sweepTimer = setInterval(() => this.#sweepDedup(), sweepMs);
|
|
130
|
+
this.#sweepTimer.unref?.();
|
|
131
|
+
}
|
|
132
|
+
#newRoleLink(seed, identity, onConnected) {
|
|
133
|
+
return {
|
|
134
|
+
seed,
|
|
135
|
+
identity,
|
|
136
|
+
session: undefined,
|
|
137
|
+
status: "connecting",
|
|
138
|
+
reconnectAttempt: 0,
|
|
139
|
+
retryTimer: undefined,
|
|
140
|
+
healthCheckTimer: undefined,
|
|
141
|
+
closing: false,
|
|
142
|
+
inFlight: undefined,
|
|
143
|
+
onConnected,
|
|
144
|
+
closedDuringConnect: false,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** Dials the control role against every seed concurrently under
|
|
148
|
+
* `controlIdentity` (the caller's own identity -- publish()/call()
|
|
149
|
+
* are attributed to it on the wire) and resolves once every seed's
|
|
150
|
+
* FIRST connect attempt has settled (success or a logged failure
|
|
151
|
+
* headed into backoff) -- matches tapRoom()'s own "await the first
|
|
152
|
+
* attempt, not the eventual outcome" reasoning (macula-mcp's
|
|
153
|
+
* lobby_observer.ts): a caller that publishes/calls immediately
|
|
154
|
+
* after connect() must not race links still mid-handshake. */
|
|
155
|
+
static async connect(seeds, controlIdentity, opts = {}) {
|
|
156
|
+
if (seeds.length === 0)
|
|
157
|
+
throw new Error("macula-ts: Pool.connect() needs at least one seed");
|
|
158
|
+
const seen = new Set();
|
|
159
|
+
for (const seed of seeds) {
|
|
160
|
+
const seedKey = `${seed.host}:${seed.port}`;
|
|
161
|
+
if (seen.has(seedKey))
|
|
162
|
+
throw new Error(`macula-ts: Pool.connect() given duplicate seed ${seedKey} -- each seed must be a distinct station`);
|
|
163
|
+
seen.add(seedKey);
|
|
164
|
+
}
|
|
165
|
+
const pool = new Pool(controlIdentity, seeds, opts);
|
|
166
|
+
await Promise.all(pool.#controlLinks.map((link) => pool.#attach(link)));
|
|
167
|
+
return pool;
|
|
168
|
+
}
|
|
169
|
+
async #attach(link) {
|
|
170
|
+
const attempt = this.#attachOnce(link);
|
|
171
|
+
link.inFlight = attempt.finally(() => {
|
|
172
|
+
if (link.inFlight === attempt)
|
|
173
|
+
link.inFlight = undefined;
|
|
174
|
+
});
|
|
175
|
+
await link.inFlight;
|
|
176
|
+
}
|
|
177
|
+
/** (Re)connects one role link and, for a topic role, re-subscribes it.
|
|
178
|
+
* Never throws -- a failure logs and schedules a backoff retry, the
|
|
179
|
+
* same self-healing shape every persistent connection in this
|
|
180
|
+
* ecosystem already uses. */
|
|
181
|
+
async #attachOnce(link) {
|
|
182
|
+
if (link.closing)
|
|
183
|
+
return;
|
|
184
|
+
// Defensive: a redundant call on an already-live link must never
|
|
185
|
+
// overwrite its session with a second connection under the same
|
|
186
|
+
// identity to the same station (exactly the collision this whole
|
|
187
|
+
// design exists to avoid). #scheduleReconnect's own idempotency
|
|
188
|
+
// guard is what should prevent this from ever being reachable in
|
|
189
|
+
// practice; this is the belt-and-suspenders backstop.
|
|
190
|
+
if (link.session)
|
|
191
|
+
return;
|
|
192
|
+
link.status = "connecting";
|
|
193
|
+
let session;
|
|
194
|
+
try {
|
|
195
|
+
session = await Session.connect(link.seed.host, link.seed.port, link.identity);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
console.error(`macula-ts pool: connect to ${link.seed.host}:${link.seed.port} failed:`, err);
|
|
199
|
+
this.#scheduleReconnect(link);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (link.closing) {
|
|
203
|
+
await session.close(link.identity).catch(() => { });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (link.onConnected) {
|
|
207
|
+
link.closedDuringConnect = false;
|
|
208
|
+
try {
|
|
209
|
+
await link.onConnected(link, session);
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
console.error(`macula-ts pool: subscribe on ${link.seed.host}:${link.seed.port} failed:`, err);
|
|
213
|
+
await session.close(link.identity).catch(() => { });
|
|
214
|
+
this.#scheduleReconnect(link);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (link.closedDuringConnect) {
|
|
218
|
+
console.error(`macula-ts pool: ${link.seed.host}:${link.seed.port}'s subscription closed before it could be marked live -- retrying`);
|
|
219
|
+
await session.close(link.identity).catch(() => { });
|
|
220
|
+
this.#scheduleReconnect(link);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
link.session = session;
|
|
225
|
+
link.status = "live";
|
|
226
|
+
link.reconnectAttempt = 0;
|
|
227
|
+
if (!link.onConnected)
|
|
228
|
+
this.#armHealthCheck(link);
|
|
229
|
+
}
|
|
230
|
+
/** Control-role links only (topic roles have their own subscribe()
|
|
231
|
+
* onClosed and never reach here -- `!link.onConnected` is what
|
|
232
|
+
* distinguishes them). publish() is fire-and-forget: found live
|
|
233
|
+
* 2026-09-04 that a publish() attempt on a session the station had
|
|
234
|
+
* already kicked can still locally "succeed" (the frame is handed off
|
|
235
|
+
* before the QUIC stack notices its own connection is gone), so a
|
|
236
|
+
* publish-only caller could go arbitrarily long without this pool
|
|
237
|
+
* ever discovering a dead control link. Every HEALTH_CHECK_INTERVAL_MS
|
|
238
|
+
* while the link is live, this calls a procedure name that is
|
|
239
|
+
* guaranteed never to be advertised (a fresh UUID-shaped string) and
|
|
240
|
+
* inspects the failure: a clean MaculaCallError means a real BOLT#4
|
|
241
|
+
* response came back over the wire (unknown_next_peer, as expected) --
|
|
242
|
+
* the connection is genuinely alive, nothing to do. Any OTHER
|
|
243
|
+
* thrown error means the call never got a wire-level answer at all --
|
|
244
|
+
* the same signal call()'s own doc uses to distinguish a real BOLT#4
|
|
245
|
+
* answer from "this never reached anyone" -- so it's treated exactly
|
|
246
|
+
* like a real operation failure and schedules a reconnect. */
|
|
247
|
+
#armHealthCheck(link) {
|
|
248
|
+
if (link.healthCheckTimer)
|
|
249
|
+
clearInterval(link.healthCheckTimer);
|
|
250
|
+
const timer = setInterval(() => {
|
|
251
|
+
const session = link.session;
|
|
252
|
+
if (!session)
|
|
253
|
+
return;
|
|
254
|
+
const probe = `_pool.healthcheck.${Math.random().toString(36).slice(2)}`;
|
|
255
|
+
session.call(probe, null, { deadlineMs: this.#healthCheckIntervalMs }).then(() => {
|
|
256
|
+
// A real provider somehow answering a random UUID-shaped
|
|
257
|
+
// procedure name is astronomically unlikely and not itself a
|
|
258
|
+
// problem either way -- the connection is clearly alive.
|
|
259
|
+
}, (err) => {
|
|
260
|
+
if (err instanceof MaculaCallError)
|
|
261
|
+
return; // alive: a real wire answer came back
|
|
262
|
+
if (link.session !== session)
|
|
263
|
+
return; // already superseded by a reconnect
|
|
264
|
+
console.error(`macula-ts pool: health check on ${link.seed.host}:${link.seed.port} failed:`, err);
|
|
265
|
+
this.#scheduleReconnect(link);
|
|
266
|
+
});
|
|
267
|
+
}, this.#healthCheckIntervalMs);
|
|
268
|
+
timer.unref?.();
|
|
269
|
+
link.healthCheckTimer = timer;
|
|
270
|
+
}
|
|
271
|
+
/** Marks `link` for backoff and schedules its next reconnect attempt.
|
|
272
|
+
* Closes whatever session it currently holds first (fire-and-forget,
|
|
273
|
+
* not awaited -- the retry timer must not wait on a graceful close
|
|
274
|
+
* round trip) so a still-alive session from an OPERATION failure
|
|
275
|
+
* (publish()/call() throwing for a reason that doesn't mean the
|
|
276
|
+
* connection itself already died, unlike a subscribe onClosed) is
|
|
277
|
+
* never simply orphaned. Found live 2026-09-04: without this, a
|
|
278
|
+
* publish/call failure left the old session's native handle both
|
|
279
|
+
* leaked AND, worse, still open under this link's identity -- the
|
|
280
|
+
* NEXT connect attempt for this SAME station under that SAME
|
|
281
|
+
* identity then races the still-live old one for the station's own
|
|
282
|
+
* per-identity dedupe kick, with no guarantee which one loses. The
|
|
283
|
+
* close is fired here, before scheduling, so it has the full backoff
|
|
284
|
+
* delay (at least RECONNECT_BASE_MS) to land before a new connect
|
|
285
|
+
* attempt begins.
|
|
286
|
+
*
|
|
287
|
+
* Idempotent per backoff episode: a no-op once `link.status` is
|
|
288
|
+
* already "backoff". Found live in review 2026-09-05: concurrent
|
|
289
|
+
* operations queued on one session (e.g. several call()s serialised
|
|
290
|
+
* by Session's own internal queue) can ALL fail once that session
|
|
291
|
+
* dies, and each one's catch handler calls this -- without this
|
|
292
|
+
* guard, every failure past the first would re-close an
|
|
293
|
+
* already-`undefined` `link.session` (harmless) but ALSO arm a
|
|
294
|
+
* second, third, ... retryTimer, each bumping reconnectAttempt
|
|
295
|
+
* independently, leaving multiple redundant reconnects racing each
|
|
296
|
+
* other. Deliberately NOT guarded against "connecting" -- this is
|
|
297
|
+
* also called from `#attachOnce`'s own failure paths, while status
|
|
298
|
+
* is still "connecting" from the top of that same function, and
|
|
299
|
+
* that path must proceed normally. */
|
|
300
|
+
#scheduleReconnect(link) {
|
|
301
|
+
if (link.closing)
|
|
302
|
+
return;
|
|
303
|
+
if (link.status === "backoff")
|
|
304
|
+
return;
|
|
305
|
+
if (link.healthCheckTimer) {
|
|
306
|
+
clearInterval(link.healthCheckTimer);
|
|
307
|
+
link.healthCheckTimer = undefined;
|
|
308
|
+
}
|
|
309
|
+
const stale = link.session;
|
|
310
|
+
link.status = "backoff";
|
|
311
|
+
link.session = undefined;
|
|
312
|
+
if (stale)
|
|
313
|
+
void stale.close(link.identity).catch(() => { });
|
|
314
|
+
const delay = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** link.reconnectAttempt);
|
|
315
|
+
link.reconnectAttempt += 1;
|
|
316
|
+
const timer = setTimeout(() => {
|
|
317
|
+
const attempt = this.#attachOnce(link);
|
|
318
|
+
link.inFlight = attempt.finally(() => {
|
|
319
|
+
if (link.inFlight === attempt)
|
|
320
|
+
link.inFlight = undefined;
|
|
321
|
+
});
|
|
322
|
+
}, delay);
|
|
323
|
+
// Deliberately NOT unref()'d, unlike healthCheckTimer/sweepTimer --
|
|
324
|
+
// this pool's own documented contract is "retry forever" for a
|
|
325
|
+
// link in backoff. Found live in review 2026-09-05: a pure
|
|
326
|
+
// subscribe()-only process (no other keep-alive handle: no HTTP
|
|
327
|
+
// listener, no stdio transport) would otherwise exit cleanly the
|
|
328
|
+
// moment its last live connection drops, silently breaking that
|
|
329
|
+
// promise instead of actually retrying through the outage.
|
|
330
|
+
link.retryTimer = timer;
|
|
331
|
+
}
|
|
332
|
+
#sweepDedup() {
|
|
333
|
+
const cutoff = Date.now() - this.#dedupWindowMs;
|
|
334
|
+
for (const [key, at] of this.#dedup) {
|
|
335
|
+
if (at < cutoff)
|
|
336
|
+
this.#dedup.delete(key);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
#liveControlLinks() {
|
|
340
|
+
return this.#controlLinks.filter((l) => l.status === "live" && l.session);
|
|
341
|
+
}
|
|
342
|
+
/** Refuses an `identity` that would double-connect to the same
|
|
343
|
+
* station as the control role or another already-tracked
|
|
344
|
+
* subscription -- the station kicks the OLDER of the two, this pool
|
|
345
|
+
* reconnects it, which gets IT kicked in turn: a perpetual ping-pong
|
|
346
|
+
* with no error naming the cause. Compares raw node-id bytes, not
|
|
347
|
+
* object identity, so two separately-loaded Identity instances over
|
|
348
|
+
* the SAME underlying keypair are still caught. */
|
|
349
|
+
#assertIdentityNotAlreadyUsed(identity) {
|
|
350
|
+
const nodeIdHex = Buffer.from(identity.nodeId).toString("hex");
|
|
351
|
+
if (nodeIdHex === Buffer.from(this.#controlIdentity.nodeId).toString("hex")) {
|
|
352
|
+
throw new Error("macula-ts pool: this identity is already the pool's own control identity -- reusing it for a subscription would double-connect one identity to one station");
|
|
353
|
+
}
|
|
354
|
+
for (const sub of this.#subscriptions.values()) {
|
|
355
|
+
if (Buffer.from(sub.identity.nodeId).toString("hex") === nodeIdHex) {
|
|
356
|
+
throw new Error(`macula-ts pool: this identity is already used by the "${sub.topic}" subscription -- reusing it here would double-connect one identity to one station`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/** Publishes to `replicationFactor` currently-live control links
|
|
361
|
+
* (default 1); partial success counts as success. A link whose
|
|
362
|
+
* publish attempt fails is marked for respawn immediately -- this is
|
|
363
|
+
* this v1's only liveness signal for the control role, since it
|
|
364
|
+
* cannot also carry a liveness-only subscribe (see this module's own
|
|
365
|
+
* header doc). Throws NoHealthyStationError if zero links are live. */
|
|
366
|
+
async publish(realm, topic, payload, opts = {}) {
|
|
367
|
+
if (this.#closed)
|
|
368
|
+
throw new Error("macula-ts pool: used after close()");
|
|
369
|
+
const targets = this.#liveControlLinks().slice(0, this.#replicationFactor);
|
|
370
|
+
if (targets.length === 0)
|
|
371
|
+
throw new NoHealthyStationError();
|
|
372
|
+
const results = await Promise.allSettled(targets.map(async (link) => {
|
|
373
|
+
const session = link.session;
|
|
374
|
+
if (link.status !== "live" || !session)
|
|
375
|
+
throw new NoHealthyStationError(); // superseded since targets was captured
|
|
376
|
+
try {
|
|
377
|
+
await session.publish(topic, payload, { realm, ttlMs: opts.ttlMs });
|
|
378
|
+
}
|
|
379
|
+
catch (err) {
|
|
380
|
+
if (link.session === session)
|
|
381
|
+
this.#scheduleReconnect(link);
|
|
382
|
+
throw err;
|
|
383
|
+
}
|
|
384
|
+
}));
|
|
385
|
+
if (!results.some((r) => r.status === "fulfilled")) {
|
|
386
|
+
const first = results.find((r) => r.status === "rejected");
|
|
387
|
+
throw first ? first.reason : new Error("macula-ts pool: publish failed on every targeted link");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/** Calls `procedure` against the pool's live control links in order
|
|
391
|
+
* until one succeeds or all have been tried. Throws
|
|
392
|
+
* NoHealthyStationError if zero links are live.
|
|
393
|
+
*
|
|
394
|
+
* A `MaculaCallError` (a real BOLT#4 response -- e.g.
|
|
395
|
+
* unknown_next_peer, unauthorized) does NOT mark its link for
|
|
396
|
+
* respawn: the connection plainly worked, it answered. Found in
|
|
397
|
+
* review 2026-09-05: treating every thrown error as evidence of a
|
|
398
|
+
* dead connection meant a single genuinely-answered error (a
|
|
399
|
+
* procedure nobody serves, a bad realm, a gated call this identity
|
|
400
|
+
* isn't authorized for) tore down and reconnected EVERY live control
|
|
401
|
+
* link in turn as call() moved through them re-trying the same
|
|
402
|
+
* doomed call -- and for a non-idempotent provider handler, actually
|
|
403
|
+
* re-invoked it on each one. Only an error that never got a
|
|
404
|
+
* wire-level answer at all (the same distinction call()'s own doc on
|
|
405
|
+
* Session draws) is treated as a dead link. Each link's own `session`
|
|
406
|
+
* reference is re-checked before scheduling a reconnect, in case a
|
|
407
|
+
* concurrent operation already superseded it between this loop
|
|
408
|
+
* starting and this iteration running. */
|
|
409
|
+
async call(realm, procedure, payload, opts = {}) {
|
|
410
|
+
if (this.#closed)
|
|
411
|
+
throw new Error("macula-ts pool: used after close()");
|
|
412
|
+
const targets = this.#liveControlLinks();
|
|
413
|
+
if (targets.length === 0)
|
|
414
|
+
throw new NoHealthyStationError();
|
|
415
|
+
let lastErr;
|
|
416
|
+
for (const link of targets) {
|
|
417
|
+
const session = link.session;
|
|
418
|
+
if (link.status !== "live" || !session)
|
|
419
|
+
continue; // superseded since `targets` was captured
|
|
420
|
+
try {
|
|
421
|
+
return await session.call(procedure, payload, { realm, deadlineMs: opts.deadlineMs });
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
lastErr = err;
|
|
425
|
+
if (!(err instanceof MaculaCallError) && link.session === session)
|
|
426
|
+
this.#scheduleReconnect(link);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
throw lastErr;
|
|
430
|
+
}
|
|
431
|
+
/** Subscribes `handler` to `(realm, topic)`: opens one subscribe-only
|
|
432
|
+
* session against every configured seed, replayed automatically on
|
|
433
|
+
* every future respawn. Mints a dedicated identity for this topic by
|
|
434
|
+
* default (disposed on unsubscribe); pass `identity` to supply the
|
|
435
|
+
* pool's own instead (e.g. for a stable, caller-controlled identity
|
|
436
|
+
* across restarts, matching macula-mcp's own observeRoomIdentityPath
|
|
437
|
+
* pattern) -- the pool never disposes an identity it didn't mint.
|
|
438
|
+
* Returns an unsubscribe function. */
|
|
439
|
+
async subscribe(realm, topic, handler, identity) {
|
|
440
|
+
if (this.#closed)
|
|
441
|
+
throw new Error("macula-ts pool: used after close()");
|
|
442
|
+
const key = subKey(realm, topic);
|
|
443
|
+
if (this.#subscriptions.has(key))
|
|
444
|
+
throw new Error(`macula-ts pool: already subscribed to ${topic}${realm ? ` (realm ${realm})` : ""}`);
|
|
445
|
+
if (identity)
|
|
446
|
+
this.#assertIdentityNotAlreadyUsed(identity);
|
|
447
|
+
const ownsIdentity = identity === undefined;
|
|
448
|
+
const subIdentity = identity ?? Identity.generate();
|
|
449
|
+
const sub = { realm, topic, handler, identity: subIdentity, ownsIdentity, links: [] };
|
|
450
|
+
const onConnected = async (link, session) => {
|
|
451
|
+
await session.subscribe(topic, (evt) => {
|
|
452
|
+
const dkey = dedupKey(realm, evt.publisher, evt.seq, topic);
|
|
453
|
+
if (this.#dedup.has(dkey))
|
|
454
|
+
return;
|
|
455
|
+
this.#dedup.set(dkey, Date.now());
|
|
456
|
+
sub.handler(evt);
|
|
457
|
+
}, {
|
|
458
|
+
realm,
|
|
459
|
+
onClosed: (err) => {
|
|
460
|
+
if (link.session === session) {
|
|
461
|
+
console.error(`macula-ts pool: subscription to ${topic} on ${link.seed.host}:${link.seed.port} dropped (${err.message}) -- reconnecting`);
|
|
462
|
+
this.#scheduleReconnect(link);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// subscribe-start hasn't resolved on this side yet (or
|
|
466
|
+
// this link has already moved on) -- flag it so
|
|
467
|
+
// #attachOnce notices once `onConnected` itself returns,
|
|
468
|
+
// rather than marking a link live with a subscription
|
|
469
|
+
// that already silently died. See closedDuringConnect's
|
|
470
|
+
// own doc.
|
|
471
|
+
link.closedDuringConnect = true;
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
sub.links = this.#seeds.map((seed) => this.#newRoleLink(seed, subIdentity, onConnected));
|
|
477
|
+
this.#subscriptions.set(key, sub);
|
|
478
|
+
await Promise.all(sub.links.map((link) => this.#attach(link)));
|
|
479
|
+
return async () => {
|
|
480
|
+
const tracked = this.#subscriptions.get(key);
|
|
481
|
+
if (!tracked)
|
|
482
|
+
return;
|
|
483
|
+
this.#subscriptions.delete(key);
|
|
484
|
+
await Promise.all(tracked.links.map(async (link) => {
|
|
485
|
+
link.closing = true;
|
|
486
|
+
if (link.retryTimer)
|
|
487
|
+
clearTimeout(link.retryTimer);
|
|
488
|
+
if (link.inFlight)
|
|
489
|
+
await link.inFlight.catch(() => { });
|
|
490
|
+
if (link.session)
|
|
491
|
+
await link.session.close(link.identity).catch(() => { });
|
|
492
|
+
}));
|
|
493
|
+
if (tracked.ownsIdentity)
|
|
494
|
+
tracked.identity.dispose();
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
/** Live/backing-off CONTROL link counts (publish/call reachability).
|
|
498
|
+
* Every configured seed is exactly one or the other. Per-topic
|
|
499
|
+
* subscription link health is not reflected here -- inspect a
|
|
500
|
+
* specific subscription's own behavior (events arriving or not)
|
|
501
|
+
* instead; exposing N independent per-topic health vectors here
|
|
502
|
+
* would not simplify what a caller actually needs to know. */
|
|
503
|
+
status() {
|
|
504
|
+
const healthy = this.#liveControlLinks().length;
|
|
505
|
+
return { healthyLinks: healthy, failedLinks: this.#controlLinks.length - healthy };
|
|
506
|
+
}
|
|
507
|
+
/** Closes every control and subscription link and disposes every
|
|
508
|
+
* identity this pool owns (the caller-supplied control identity
|
|
509
|
+
* included). Awaits each link's in-flight connect/reconnect first,
|
|
510
|
+
* so a still-connecting link never has its identity yanked out from
|
|
511
|
+
* under it. */
|
|
512
|
+
async close() {
|
|
513
|
+
if (this.#closed)
|
|
514
|
+
return;
|
|
515
|
+
this.#closed = true;
|
|
516
|
+
clearInterval(this.#sweepTimer);
|
|
517
|
+
const allLinks = [...this.#controlLinks, ...[...this.#subscriptions.values()].flatMap((s) => s.links)];
|
|
518
|
+
await Promise.all(allLinks.map(async (link) => {
|
|
519
|
+
link.closing = true;
|
|
520
|
+
if (link.retryTimer)
|
|
521
|
+
clearTimeout(link.retryTimer);
|
|
522
|
+
if (link.healthCheckTimer)
|
|
523
|
+
clearInterval(link.healthCheckTimer);
|
|
524
|
+
if (link.inFlight)
|
|
525
|
+
await link.inFlight.catch(() => { });
|
|
526
|
+
if (link.session)
|
|
527
|
+
await link.session.close(link.identity).catch(() => { });
|
|
528
|
+
link.session = undefined;
|
|
529
|
+
link.status = "backoff"; // reflects reality for status()/#liveControlLinks() if either is called after close()
|
|
530
|
+
}));
|
|
531
|
+
for (const sub of this.#subscriptions.values())
|
|
532
|
+
if (sub.ownsIdentity)
|
|
533
|
+
sub.identity.dispose();
|
|
534
|
+
this.#subscriptions.clear();
|
|
535
|
+
this.#controlIdentity.dispose();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
//# sourceMappingURL=pool.js.map
|
package/dist/pool.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@macula-io/ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "TypeScript SDK for the Macula mesh (FFI over macula-go)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"build:prebuilds": "npm run build:go && prebuildify --napi --strip && npm pkg delete scripts.install",
|
|
47
47
|
"build": "npm run build:addon:dev && tsc",
|
|
48
48
|
"test": "vitest run",
|
|
49
|
-
"test:live": "MACULA_TS_LIVE=1 vitest run src/session.live.test.ts src/rpc.live.test.ts src/dht.live.test.ts src/pubsub.live.test.ts src/content.live.test.ts src/ucan.live.test.ts src/directdial.live.test.ts",
|
|
49
|
+
"test:live": "MACULA_TS_LIVE=1 vitest run src/session.live.test.ts src/rpc.live.test.ts src/dht.live.test.ts src/pubsub.live.test.ts src/content.live.test.ts src/ucan.live.test.ts src/directdial.live.test.ts src/pool.live.test.ts",
|
|
50
50
|
"typecheck": "tsc --noEmit"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|