@hedwigjs/broker 0.1.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/LICENSE +21 -0
- package/README.md +670 -0
- package/dist/index.cjs +2238 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +1507 -0
- package/dist/index.d.ts +1507 -0
- package/dist/index.mjs +2227 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,1507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backpressure strategies to control message processing rate
|
|
3
|
+
*
|
|
4
|
+
* Control how messages are processed to prevent UI freezing and optimize performance.
|
|
5
|
+
* All strategies are mutually exclusive - only one can be used per subscription.
|
|
6
|
+
*/
|
|
7
|
+
interface BackpressureOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Throttle: Limit handler calls to once per `throttle` milliseconds
|
|
10
|
+
*
|
|
11
|
+
* First call executes immediately, subsequent calls are delayed.
|
|
12
|
+
* Guarantees maximum call rate without losing the last message.
|
|
13
|
+
*
|
|
14
|
+
* Use case: Real-time charts, high-frequency updates
|
|
15
|
+
*/
|
|
16
|
+
throttle?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Debounce: Delay handler execution until `debounce` milliseconds of silence
|
|
19
|
+
*
|
|
20
|
+
* Resets timer on each new message. Only the last message is processed.
|
|
21
|
+
*
|
|
22
|
+
* Use case: Search autocomplete, input validation
|
|
23
|
+
*/
|
|
24
|
+
debounce?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Rate limiting: Drop messages exceeding rate limit
|
|
27
|
+
*
|
|
28
|
+
* Allows maximum `max` messages per `window` milliseconds.
|
|
29
|
+
* Messages exceeding the limit are dropped (lost permanently).
|
|
30
|
+
*
|
|
31
|
+
* Use case: Prevent flooding, protect from bursts, spam protection
|
|
32
|
+
*/
|
|
33
|
+
rateLimit?: {
|
|
34
|
+
/** Maximum number of messages allowed */
|
|
35
|
+
max: number;
|
|
36
|
+
/** Time window in milliseconds */
|
|
37
|
+
window: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Callback when messages are dropped (rate limiting only)
|
|
41
|
+
*
|
|
42
|
+
* @param droppedCount - Number of messages dropped so far
|
|
43
|
+
*/
|
|
44
|
+
onDrop?: (droppedCount: number) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* All structured log events emitted by the broker infrastructure.
|
|
49
|
+
*
|
|
50
|
+
* These are machine-readable codes — stable across versions.
|
|
51
|
+
* Use them as filter keys in Sentry / Datadog / Grafana.
|
|
52
|
+
*/
|
|
53
|
+
type BrokerLogEvent = 'broker.subscribe.after_destroy' | 'broker.bridge.add.after_destroy' | 'broker.bridge.replaced' | 'broker.client.register.after_destroy' | 'broker.replay.history_disabled' | 'facade.createClient.reset' | 'handler.failed' | 'hook.after_send.failed' | 'hook.failed' | 'bridge.message.parse_failed' | 'backpressure.handler.failed' | 'backpressure.on_drop.failed' | 'replay.handler.failed' | 'replay.query.failed' | 'system_events.listener.failed';
|
|
54
|
+
/**
|
|
55
|
+
* Pluggable logger interface for broker infrastructure events.
|
|
56
|
+
*
|
|
57
|
+
* Implement this to redirect broker warnings and errors to your
|
|
58
|
+
* observability stack (Sentry, Datadog, pino, etc.).
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* initBroker({
|
|
62
|
+
* logger: {
|
|
63
|
+
* warn: (event, meta) => myLogger.warn(event, meta),
|
|
64
|
+
* error: (event, meta) => Sentry.captureMessage(event, { extra: meta }),
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
*/
|
|
68
|
+
interface BrokerLogger {
|
|
69
|
+
warn(event: BrokerLogEvent, meta?: Record<string, unknown>): void;
|
|
70
|
+
error(event: BrokerLogEvent, meta?: Record<string, unknown>): void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Default logger — forwards to console.
|
|
74
|
+
* Used when no logger is provided in BrokerConfig.
|
|
75
|
+
*/
|
|
76
|
+
declare const defaultLogger: BrokerLogger;
|
|
77
|
+
|
|
78
|
+
/** Unique client identifier in the system */
|
|
79
|
+
type ClientID = string;
|
|
80
|
+
/**
|
|
81
|
+
* Internal message format for inter-module communication
|
|
82
|
+
*/
|
|
83
|
+
interface Message<T extends string = string, P = any> {
|
|
84
|
+
/** Unique message identifier (e.g. "abc-42") for debugging and DevTools */
|
|
85
|
+
id: string;
|
|
86
|
+
/** Message topic (e.g. 'user.login.v1') */
|
|
87
|
+
topic: T;
|
|
88
|
+
/** Message source - client ID that emitted the message */
|
|
89
|
+
source: string;
|
|
90
|
+
/** Target client ID or '*' for broadcast */
|
|
91
|
+
target: string;
|
|
92
|
+
/** Message payload data */
|
|
93
|
+
data: P;
|
|
94
|
+
/** Unix timestamp in milliseconds */
|
|
95
|
+
timestamp: number;
|
|
96
|
+
/** Indicates if this is a replayed historical message */
|
|
97
|
+
replayed?: boolean;
|
|
98
|
+
/** Indicates if this message was received from external source (bridge) */
|
|
99
|
+
fromExternal?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Marks a debug/test message injected via `broker.$debug.send(...)`.
|
|
102
|
+
* Routing, hooks, history and bridge forwarding all treat it as a
|
|
103
|
+
* real message — the flag is purely metadata so DevTools and integration
|
|
104
|
+
* tests can distinguish spoofed traffic from production events.
|
|
105
|
+
*/
|
|
106
|
+
synthetic?: boolean;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Message handler function
|
|
110
|
+
* Can return data for Request-Reply pattern
|
|
111
|
+
*/
|
|
112
|
+
type HandlerFn<T extends string, P = unknown> = (message: Message<T, P>) => void | any | Promise<void | any>;
|
|
113
|
+
/**
|
|
114
|
+
* Type-erased message handler for internal core usage
|
|
115
|
+
*
|
|
116
|
+
* Same contract as HandlerFn but without generic type parameters.
|
|
117
|
+
* Used in Broker, Subscriptions, Router, BackpressureHandler
|
|
118
|
+
* where specific message/payload types are already erased.
|
|
119
|
+
*/
|
|
120
|
+
type MessageHandler = (message: Message) => void | any | Promise<void | any>;
|
|
121
|
+
/**
|
|
122
|
+
* Options for message emission and requests
|
|
123
|
+
*/
|
|
124
|
+
interface MessageOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Record this message to history for replay
|
|
127
|
+
*
|
|
128
|
+
* Important: History has limited capacity (default 1000 messages).
|
|
129
|
+
* Only mark truly important messages that late subscribers need to replay.
|
|
130
|
+
*
|
|
131
|
+
* @default false
|
|
132
|
+
*/
|
|
133
|
+
history?: boolean;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Options for replaying historical messages
|
|
137
|
+
*/
|
|
138
|
+
interface ReplayOptions {
|
|
139
|
+
/**
|
|
140
|
+
* Maximum number of historical messages to replay
|
|
141
|
+
* If not specified, replays all matching messages
|
|
142
|
+
*/
|
|
143
|
+
limit?: number;
|
|
144
|
+
/**
|
|
145
|
+
* Replay messages starting from this timestamp (Unix ms)
|
|
146
|
+
*/
|
|
147
|
+
since?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Replay messages until this timestamp (Unix ms)
|
|
150
|
+
*/
|
|
151
|
+
until?: number;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Options for message subscription
|
|
155
|
+
*
|
|
156
|
+
* Controls how messages are processed and delivered to handlers.
|
|
157
|
+
* All options are opt-in and can be combined.
|
|
158
|
+
*/
|
|
159
|
+
interface SubscriptionOptions {
|
|
160
|
+
/**
|
|
161
|
+
* Backpressure control strategies for incoming messages
|
|
162
|
+
*
|
|
163
|
+
* Controls the rate and manner of message processing to prevent
|
|
164
|
+
* UI freezing and optimize performance.
|
|
165
|
+
*/
|
|
166
|
+
backpressure?: BackpressureOptions;
|
|
167
|
+
/**
|
|
168
|
+
* Replay historical messages when subscribing
|
|
169
|
+
*
|
|
170
|
+
* Allows late subscribers to catch up on missed messages.
|
|
171
|
+
* Messages are replayed asynchronously after subscription is established.
|
|
172
|
+
*/
|
|
173
|
+
replay?: ReplayOptions;
|
|
174
|
+
}
|
|
175
|
+
interface ClientSubscriptionInfo {
|
|
176
|
+
topic: string;
|
|
177
|
+
/**
|
|
178
|
+
* Options of the first handler registered on this (client, topic) pair.
|
|
179
|
+
* A pair may hold multiple handlers, each with distinct options — this
|
|
180
|
+
* field surfaces one representative set for observability tools that
|
|
181
|
+
* predate the multi-handler model.
|
|
182
|
+
*/
|
|
183
|
+
options?: SubscriptionOptions;
|
|
184
|
+
/** Total number of handlers this client has attached to the topic. */
|
|
185
|
+
handlerCount: number;
|
|
186
|
+
}
|
|
187
|
+
/** Point-in-time snapshot of a single registered client. */
|
|
188
|
+
interface ClientInfo {
|
|
189
|
+
id: ClientID;
|
|
190
|
+
/** Unix timestamp (ms) when the client registered. */
|
|
191
|
+
connectedAt: number;
|
|
192
|
+
subscriptions: ClientSubscriptionInfo[];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Configuration for Broker
|
|
196
|
+
*/
|
|
197
|
+
interface BrokerConfig {
|
|
198
|
+
/** Message history configuration */
|
|
199
|
+
history?: {
|
|
200
|
+
/** Enable message history */
|
|
201
|
+
enabled: boolean;
|
|
202
|
+
/** Maximum number of messages to keep in memory (default: 1000) */
|
|
203
|
+
maxSize?: number;
|
|
204
|
+
/** Time to live for messages (ms). undefined = no expiration */
|
|
205
|
+
ttl?: number;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Pluggable logger for broker infrastructure events.
|
|
209
|
+
*
|
|
210
|
+
* Receives structured event codes (e.g. `'handler.failed'`) and a metadata
|
|
211
|
+
* object. Use this to redirect broker warnings and errors to your
|
|
212
|
+
* observability stack (Sentry, Datadog, pino, etc.).
|
|
213
|
+
*
|
|
214
|
+
* Defaults to `console.warn` / `console.error` when not provided.
|
|
215
|
+
*/
|
|
216
|
+
logger?: BrokerLogger;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
declare const RoutingReason: {
|
|
220
|
+
readonly DELIVERED: "DELIVERED";
|
|
221
|
+
readonly DISPATCHED: "DISPATCHED";
|
|
222
|
+
readonly REPLAY_DELIVERED: "REPLAY_DELIVERED";
|
|
223
|
+
readonly HOOK_REJECTED: "HOOK_REJECTED";
|
|
224
|
+
readonly NO_SUBSCRIBERS: "NO_SUBSCRIBERS";
|
|
225
|
+
readonly NOT_SUBSCRIBED: "NOT_SUBSCRIBED";
|
|
226
|
+
readonly HANDLER_FAILED: "HANDLER_FAILED";
|
|
227
|
+
readonly BROKER_DESTROYED: "BROKER_DESTROYED";
|
|
228
|
+
};
|
|
229
|
+
type RoutingReasonType = (typeof RoutingReason)[keyof typeof RoutingReason];
|
|
230
|
+
/**
|
|
231
|
+
* RoutingResult - Message delivery result (Value Object)
|
|
232
|
+
*
|
|
233
|
+
* Immutable object representing the result of a message dispatch operation.
|
|
234
|
+
* Can only be created through the static factory method create().
|
|
235
|
+
*/
|
|
236
|
+
declare class RoutingResult<TResponse = unknown> {
|
|
237
|
+
readonly status: 'ACK' | 'NACK';
|
|
238
|
+
readonly reason: RoutingReasonType;
|
|
239
|
+
readonly message: string;
|
|
240
|
+
readonly timestamp: number;
|
|
241
|
+
/** Recipient client ID — set for unicast, undefined for multicast. */
|
|
242
|
+
readonly recipientId?: ClientID;
|
|
243
|
+
/** All recipient client IDs — set for multicast, undefined for unicast. */
|
|
244
|
+
readonly recipientIds?: ClientID[];
|
|
245
|
+
readonly data?: TResponse;
|
|
246
|
+
private constructor();
|
|
247
|
+
/**
|
|
248
|
+
* @param status - ACK for success, NACK for failure
|
|
249
|
+
* @param reason - Machine-readable reason code (use RoutingReason constants)
|
|
250
|
+
* @param message - Human-readable result description
|
|
251
|
+
* @param recipientId - Recipient client ID (unicast only)
|
|
252
|
+
* @param data - Response data from handler (Request-Reply pattern only)
|
|
253
|
+
* @param recipientIds - All recipient client IDs (multicast only)
|
|
254
|
+
*/
|
|
255
|
+
static create<T = unknown>(status: 'ACK' | 'NACK', reason: RoutingReasonType, message: string, recipientId?: ClientID, data?: T, recipientIds?: ClientID[]): RoutingResult<T>;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Pluggable wire used by {@link BridgeConfig} / {@link MessageBroker.addBridge}.
|
|
260
|
+
*
|
|
261
|
+
* This is the extension point for cross-context transports. Built-in
|
|
262
|
+
* implementations (`PostMessageTransport`, `BroadcastChannelTransport`,
|
|
263
|
+
* `WebSocketTransport`, `SSETransport`) are exported from `@hedwigjs/broker`;
|
|
264
|
+
* custom transports (WebRTC, Service Worker, Electron IPC, etc.) plug in by
|
|
265
|
+
* implementing this interface — no other broker internals need to be touched.
|
|
266
|
+
*
|
|
267
|
+
* ## Contract
|
|
268
|
+
*
|
|
269
|
+
* ### Outbound — `send(data)`
|
|
270
|
+
* Called by the {@link Bridge} whenever a local broker message matches the
|
|
271
|
+
* bridge's forward patterns. Implementation must serialize (if needed) and
|
|
272
|
+
* hand the payload to the underlying wire. Errors should be caught and
|
|
273
|
+
* logged, not thrown — a failing wire must not crash the broker pipeline.
|
|
274
|
+
*
|
|
275
|
+
* ### Inbound — `onMessage(callback)`
|
|
276
|
+
* Called once by the {@link Bridge} at construction time to subscribe to
|
|
277
|
+
* incoming messages. Implementation must invoke `callback` for every valid
|
|
278
|
+
* inbound payload after any transport-level validation (e.g. origin checks
|
|
279
|
+
* for postMessage, channel filtering for BroadcastChannel). Returns an
|
|
280
|
+
* unsubscribe function; the {@link Bridge} calls it in `destroy()`.
|
|
281
|
+
*
|
|
282
|
+
* ### Cleanup — `destroy()`
|
|
283
|
+
* Called when the bridge is removed or the broker is destroyed. Must release
|
|
284
|
+
* all resources (event listeners, sockets, channels). Must be idempotent.
|
|
285
|
+
*
|
|
286
|
+
* ## Security note
|
|
287
|
+
*
|
|
288
|
+
* Transports are the trust boundary between the broker and the outside
|
|
289
|
+
* world. If your wire crosses origins (postMessage, WebSocket, SSE),
|
|
290
|
+
* validate the source before invoking the inbound callback — the broker
|
|
291
|
+
* will otherwise route whatever it receives.
|
|
292
|
+
*
|
|
293
|
+
* @example Custom transport skeleton
|
|
294
|
+
* ```ts
|
|
295
|
+
* class MyTransport implements BridgeTransport {
|
|
296
|
+
* #cb: ((data: unknown) => void) | null = null;
|
|
297
|
+
*
|
|
298
|
+
* send(data: unknown): void {
|
|
299
|
+
* try { myWire.publish(data); }
|
|
300
|
+
* catch (e) { console.error('[MyTransport] send failed:', e); }
|
|
301
|
+
* }
|
|
302
|
+
*
|
|
303
|
+
* onMessage(cb: (data: unknown) => void): () => void {
|
|
304
|
+
* this.#cb = cb;
|
|
305
|
+
* const off = myWire.subscribe((payload) => {
|
|
306
|
+
* if (!isTrusted(payload)) return;
|
|
307
|
+
* this.#cb?.(payload);
|
|
308
|
+
* });
|
|
309
|
+
* return () => { off(); this.#cb = null; };
|
|
310
|
+
* }
|
|
311
|
+
*
|
|
312
|
+
* destroy(): void { this.#cb = null; myWire.close(); }
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
interface BridgeTransport {
|
|
317
|
+
/** Outbound: send a payload to the wire. See interface docs for contract. */
|
|
318
|
+
send(data: unknown): void;
|
|
319
|
+
/** Inbound: subscribe to payloads from the wire. Returns unsubscribe. */
|
|
320
|
+
onMessage(callback: (data: unknown) => void): () => void;
|
|
321
|
+
/** Release resources. Must be idempotent. */
|
|
322
|
+
destroy(): void;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Bridge configuration
|
|
326
|
+
*/
|
|
327
|
+
interface BridgeConfig {
|
|
328
|
+
/** Low-level duplex link (see {@link BridgeTransport}) */
|
|
329
|
+
transport: BridgeTransport;
|
|
330
|
+
/** Message patterns to forward (e.g. ['user.*', 'theme.*']) */
|
|
331
|
+
forward: string[];
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Bridge interface - transport layer for cross-context communication
|
|
335
|
+
*
|
|
336
|
+
* Bridge forwards messages
|
|
337
|
+
* between broker and external contexts (iframes, tabs, servers).
|
|
338
|
+
*/
|
|
339
|
+
interface Bridge {
|
|
340
|
+
/** Topic patterns this bridge forwards to its transport. */
|
|
341
|
+
readonly forwardPatterns: ReadonlyArray<string>;
|
|
342
|
+
/**
|
|
343
|
+
* Transport class name with the `Transport` suffix stripped
|
|
344
|
+
* (e.g. `WebSocket`, `SSE`). Used only by the Inspector to label
|
|
345
|
+
* bridges in DevTools. `undefined` for transports whose constructor
|
|
346
|
+
* is an anonymous class.
|
|
347
|
+
*/
|
|
348
|
+
readonly transportKind?: string;
|
|
349
|
+
/**
|
|
350
|
+
* Check if topic should be forwarded to transport
|
|
351
|
+
*/
|
|
352
|
+
shouldForward(topic: string): boolean;
|
|
353
|
+
/**
|
|
354
|
+
* Send message to transport
|
|
355
|
+
*/
|
|
356
|
+
send(message: Message): void;
|
|
357
|
+
/**
|
|
358
|
+
* Cleanup resources
|
|
359
|
+
*/
|
|
360
|
+
destroy(): void;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* System events - internal signals about broker state transitions.
|
|
365
|
+
*
|
|
366
|
+
* These are NOT user messages. System events expose infrastructure-level
|
|
367
|
+
* state changes (client/subscription/bridge lifecycle) that are not
|
|
368
|
+
* observable through the hook pipeline.
|
|
369
|
+
*
|
|
370
|
+
* Intended consumers:
|
|
371
|
+
* - DevTools (message-broker-devtools)
|
|
372
|
+
* - Tracing / metrics integrations
|
|
373
|
+
*
|
|
374
|
+
* These events are FIRE-AND-FORGET. Listeners cannot influence the pipeline;
|
|
375
|
+
* exceptions thrown from a listener are caught and logged, not propagated.
|
|
376
|
+
*
|
|
377
|
+
* Exposed on the broker under `$systemEvents` (the `$` prefix signals that
|
|
378
|
+
* this is a broker-internal channel, distinct from user message pub/sub).
|
|
379
|
+
*/
|
|
380
|
+
interface SystemEventMap<T extends string, P extends Record<T, any>> {
|
|
381
|
+
'client.registered': {
|
|
382
|
+
clientId: ClientID;
|
|
383
|
+
/** Unix ms when the client was registered. */
|
|
384
|
+
at: number;
|
|
385
|
+
};
|
|
386
|
+
'client.unregistered': {
|
|
387
|
+
clientId: ClientID;
|
|
388
|
+
/** Unix ms when the client was unregistered. */
|
|
389
|
+
at: number;
|
|
390
|
+
};
|
|
391
|
+
'subscription.added': {
|
|
392
|
+
clientId: ClientID;
|
|
393
|
+
topic: T;
|
|
394
|
+
options?: SubscriptionOptions;
|
|
395
|
+
};
|
|
396
|
+
'subscription.removed': {
|
|
397
|
+
clientId: ClientID;
|
|
398
|
+
topic: T;
|
|
399
|
+
};
|
|
400
|
+
/**
|
|
401
|
+
* Fired when an `onSubscribe` hook denied a subscription attempt. The
|
|
402
|
+
* broker still throws on the caller so the subscription is NOT registered;
|
|
403
|
+
* this event exists so observability tools (DevTools, ACL audit) can pick
|
|
404
|
+
* up the denial without racing the exception.
|
|
405
|
+
*/
|
|
406
|
+
'subscription.rejected': {
|
|
407
|
+
clientId: ClientID;
|
|
408
|
+
topic: T;
|
|
409
|
+
reason: string;
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* Fired when a `beforeSend` hook denied an outgoing message. The message
|
|
413
|
+
* also surfaces in the delivery result as `NACK HOOK_REJECTED`, but this
|
|
414
|
+
* event lets pure-observability consumers listen for security signals on a
|
|
415
|
+
* dedicated channel without inspecting every RoutingResult.
|
|
416
|
+
*/
|
|
417
|
+
'message.rejected': {
|
|
418
|
+
source: ClientID;
|
|
419
|
+
target: ClientID | '*';
|
|
420
|
+
topic: T;
|
|
421
|
+
reason: string;
|
|
422
|
+
};
|
|
423
|
+
'bridge.added': {
|
|
424
|
+
bridgeId: string;
|
|
425
|
+
};
|
|
426
|
+
'bridge.removed': {
|
|
427
|
+
bridgeId: string;
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
type SystemEventName<T extends string, P extends Record<T, any>> = keyof SystemEventMap<T, P>;
|
|
431
|
+
type SystemEventPayload<T extends string, P extends Record<T, any>, K extends SystemEventName<T, P>> = SystemEventMap<T, P>[K];
|
|
432
|
+
/**
|
|
433
|
+
* Listener for a specific system event. Receives the event payload.
|
|
434
|
+
* Exceptions are caught by the dispatcher.
|
|
435
|
+
*/
|
|
436
|
+
type SystemEventListener<T extends string, P extends Record<T, any>, K extends SystemEventName<T, P>> = (payload: SystemEventPayload<T, P, K>) => void;
|
|
437
|
+
/**
|
|
438
|
+
* Listener that receives every system event with its name.
|
|
439
|
+
* Useful for universal recorders (e.g. DevTools event log).
|
|
440
|
+
*/
|
|
441
|
+
type SystemAnyEventListener<T extends string, P extends Record<T, any>> = <K extends SystemEventName<T, P>>(event: K, payload: SystemEventPayload<T, P, K>) => void;
|
|
442
|
+
/**
|
|
443
|
+
* Subscriber contract for the system event channel.
|
|
444
|
+
*
|
|
445
|
+
* Consumers (DevTools, tracing, metrics) use this to subscribe. Listeners
|
|
446
|
+
* are fire-and-forget — they cannot influence the pipeline, and exceptions
|
|
447
|
+
* they throw are caught and logged, never propagated.
|
|
448
|
+
*
|
|
449
|
+
* `emit()` and `clear()` are intentionally absent: only `BrokerCore`
|
|
450
|
+
* (which holds the concrete `SystemEvents` instance) can publish events
|
|
451
|
+
* or tear down listeners.
|
|
452
|
+
*/
|
|
453
|
+
interface SystemEventsEmitter<T extends string, P extends Record<T, any>> {
|
|
454
|
+
/**
|
|
455
|
+
* Subscribe to a specific system event.
|
|
456
|
+
* @returns Unsubscribe function.
|
|
457
|
+
*/
|
|
458
|
+
on<K extends SystemEventName<T, P>>(event: K, listener: SystemEventListener<T, P, K>): () => void;
|
|
459
|
+
/**
|
|
460
|
+
* Subscribe once — listener is automatically removed after first invocation.
|
|
461
|
+
* @returns Unsubscribe function.
|
|
462
|
+
*/
|
|
463
|
+
once<K extends SystemEventName<T, P>>(event: K, listener: SystemEventListener<T, P, K>): () => void;
|
|
464
|
+
/**
|
|
465
|
+
* Remove all listeners for a given event. Omit `event` to clear everything.
|
|
466
|
+
*/
|
|
467
|
+
off<K extends SystemEventName<T, P>>(event?: K): void;
|
|
468
|
+
/**
|
|
469
|
+
* Subscribe to every system event with one listener.
|
|
470
|
+
* Useful for DevTools panels that need a unified event feed.
|
|
471
|
+
* @returns Unsubscribe function.
|
|
472
|
+
*/
|
|
473
|
+
onAny(listener: SystemAnyEventListener<T, P>): () => void;
|
|
474
|
+
/**
|
|
475
|
+
* Number of registered listeners for a given event, or total across all
|
|
476
|
+
* events when called without arguments. Useful for tests and fast-path checks.
|
|
477
|
+
*/
|
|
478
|
+
listenerCount<K extends SystemEventName<T, P>>(event?: K): number;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Result of hook execution
|
|
483
|
+
*/
|
|
484
|
+
type HookResult = {
|
|
485
|
+
allowed: true;
|
|
486
|
+
} | {
|
|
487
|
+
allowed: false;
|
|
488
|
+
message: string;
|
|
489
|
+
};
|
|
490
|
+
/**
|
|
491
|
+
* Hook function types
|
|
492
|
+
*/
|
|
493
|
+
type OnSubscribeHook<T> = (topic: T, clientId: ClientID) => HookResult;
|
|
494
|
+
/** Called before each message is sent. Return { allowed: false } to block. */
|
|
495
|
+
type BeforeSendHook<T extends string, P extends Record<T, any>> = (message: Readonly<Message<T, P[T]>>) => HookResult;
|
|
496
|
+
/** Called after each message is sent. Receives delivery result. */
|
|
497
|
+
type AfterSendHook<T extends string, P extends Record<T, any>> = (message: Readonly<Message<T, P[T]>>, messageResult: RoutingResult) => void;
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* BrokerCore — low-level message broker engine, internal implementation
|
|
501
|
+
* of the public {@link MessageBroker} interface.
|
|
502
|
+
*
|
|
503
|
+
* Responsibilities:
|
|
504
|
+
* - Message routing and delivery pipeline (hooks → routing → history → bridges)
|
|
505
|
+
* - Subscription management (delegates to Subscriptions)
|
|
506
|
+
* - Coordinate Router, HooksRegistry, ClientRegistry
|
|
507
|
+
* - Bridge management for cross-context communication
|
|
508
|
+
* - Message history & replay
|
|
509
|
+
* - Emit system events on the internal system events channel
|
|
510
|
+
* - Expose state snapshots via the inspect facade
|
|
511
|
+
* - Lifecycle management
|
|
512
|
+
*
|
|
513
|
+
* The class is exported within the package for unit tests. Consumers
|
|
514
|
+
* receive only the `MessageBroker<T, P>` contract from `initBroker()` /
|
|
515
|
+
* `getBroker()`. Methods tagged `@internal` (subscribe, unsubscribe,
|
|
516
|
+
* processMessage, registerClient, unregisterClient, resetClient,
|
|
517
|
+
* getClient) form the internal protocol between
|
|
518
|
+
* BrokerClient, Bridge and the facade — they are stable only inside the
|
|
519
|
+
* package and may change without notice.
|
|
520
|
+
*/
|
|
521
|
+
declare class BrokerCore<T extends string, P extends Record<T, any>> implements MessageBroker<T, P> {
|
|
522
|
+
#private;
|
|
523
|
+
/**
|
|
524
|
+
* Infrastructure logger configured via {@link BrokerConfig.logger}.
|
|
525
|
+
*
|
|
526
|
+
* @internal Used by the facade layer.
|
|
527
|
+
*/
|
|
528
|
+
readonly logger: BrokerLogger;
|
|
529
|
+
constructor(config?: BrokerConfig);
|
|
530
|
+
/**
|
|
531
|
+
* Broker-internal system event channel (push model).
|
|
532
|
+
*
|
|
533
|
+
* The `$` prefix marks this as a broker-internal API. Intended for tooling:
|
|
534
|
+
* DevTools, tracing collectors, metrics integrations.
|
|
535
|
+
*
|
|
536
|
+
* This is NOT for extending broker behaviour — extension hooks are exposed
|
|
537
|
+
* via `useBeforeSendHook`, `useAfterSendHook`, `useOnSubscribeHook`.
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* broker.$systemEvents.on('client.registered', ({ clientId }) => { ... });
|
|
541
|
+
* broker.$systemEvents.on('subscription.added', ({ clientId, topic }) => { ... });
|
|
542
|
+
*/
|
|
543
|
+
get $systemEvents(): SystemEventsEmitter<T, P>;
|
|
544
|
+
/**
|
|
545
|
+
* Point-in-time state snapshots (pull model).
|
|
546
|
+
*
|
|
547
|
+
* Read-only view over broker state for DevTools and debugging tools.
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* const clients = broker.inspect.getClients();
|
|
551
|
+
* const history = broker.inspect.getHistory();
|
|
552
|
+
*/
|
|
553
|
+
get inspect(): Inspector<T, P>;
|
|
554
|
+
/**
|
|
555
|
+
* Subscribe a client to a topic.
|
|
556
|
+
*
|
|
557
|
+
* Multiple handlers may be attached to the same `(clientId, topic)` pair —
|
|
558
|
+
* each call returns a distinct subscription id that identifies THIS
|
|
559
|
+
* handler for later removal via {@link unsubscribeOne}.
|
|
560
|
+
*
|
|
561
|
+
* @returns Subscription id, or `0` when the call was a no-op (broker
|
|
562
|
+
* destroyed). Zero is never a valid id.
|
|
563
|
+
*
|
|
564
|
+
* @throws Error if subscription is blocked on onSubscribe hook
|
|
565
|
+
*
|
|
566
|
+
* @internal Called by {@link BrokerClient.on}. Not part of the public
|
|
567
|
+
* `MessageBroker` contract.
|
|
568
|
+
*/
|
|
569
|
+
subscribe(clientId: ClientID, topic: T, handler: MessageHandler, options?: SubscriptionOptions): number;
|
|
570
|
+
/**
|
|
571
|
+
* Unsubscribe a client from a topic — removes every handler this client
|
|
572
|
+
* has attached to the topic.
|
|
573
|
+
*
|
|
574
|
+
* @internal Called by {@link BrokerClient.off}. Not part of the public
|
|
575
|
+
* `MessageBroker` contract.
|
|
576
|
+
*/
|
|
577
|
+
unsubscribe(clientId: ClientID, topic: T): void;
|
|
578
|
+
/**
|
|
579
|
+
* Unsubscribe a single handler by its subscription id.
|
|
580
|
+
*
|
|
581
|
+
* Fires `subscription.removed` only if this was the last handler that
|
|
582
|
+
* client had on the topic — otherwise the client is still subscribed.
|
|
583
|
+
*
|
|
584
|
+
* @internal Called by the unsubscribe closure returned from {@link BrokerClient.on}.
|
|
585
|
+
*/
|
|
586
|
+
unsubscribeOne(subscriptionId: number): void;
|
|
587
|
+
/**
|
|
588
|
+
* Process a message originating from a local client.
|
|
589
|
+
*
|
|
590
|
+
* Runs the full lifecycle pipeline: beforeSend → history → routing →
|
|
591
|
+
* afterSend → forward to bridges.
|
|
592
|
+
*
|
|
593
|
+
* @param topic - Type of message
|
|
594
|
+
* @param sender - Client ID of sender
|
|
595
|
+
* @param recipient - Target recipient: specific ClientID (unicast) or '*' (multicast)
|
|
596
|
+
* @param data - Message payload
|
|
597
|
+
* @param options - Message options (history)
|
|
598
|
+
* @returns Promise resolving to RoutingResult with delivery status
|
|
599
|
+
*
|
|
600
|
+
* @internal Called by {@link BrokerClient.emit} / {@link BrokerClient.request}.
|
|
601
|
+
* Not part of the public `MessageBroker` contract.
|
|
602
|
+
*/
|
|
603
|
+
processMessage<K extends T, R = unknown>(topic: K, sender: ClientID, recipient: ClientID | '*', data: P[K], options?: MessageOptions): Promise<RoutingResult<R>>;
|
|
604
|
+
/**
|
|
605
|
+
* Broker-internal debug channel.
|
|
606
|
+
*
|
|
607
|
+
* `send()` runs the full message pipeline exactly like a normal
|
|
608
|
+
* `Client.emit()` / `Client.request()` — routing, hooks, history and
|
|
609
|
+
* bridge forwarding all apply — but with two differences:
|
|
610
|
+
*
|
|
611
|
+
* 1. `source` is an arbitrary string, not tied to a registered client.
|
|
612
|
+
* Nothing gets reset in the client registry: safe to «impersonate»
|
|
613
|
+
* any client id for testing subscribers without breaking that
|
|
614
|
+
* client's own subscriptions.
|
|
615
|
+
* 2. `message.synthetic === true` on the resulting Message, so
|
|
616
|
+
* DevTools and integration tests can distinguish spoofed traffic
|
|
617
|
+
* from production events (e.g. render a `SYNTHETIC` badge).
|
|
618
|
+
*
|
|
619
|
+
* Multicast vs unicast is picked by `target`: `'*'` fans out to all
|
|
620
|
+
* subscribers, a specific `ClientID` targets one recipient and captures
|
|
621
|
+
* that handler's return value in `RoutingResult.data`.
|
|
622
|
+
*
|
|
623
|
+
* The `$` prefix marks this as a broker-internal API — for DevTools
|
|
624
|
+
* and integration tests, not for business code.
|
|
625
|
+
*/
|
|
626
|
+
get $debug(): {
|
|
627
|
+
send<K extends T, R = unknown>(source: ClientID, topic: K, target: ClientID | '*', data: P[K], options?: MessageOptions): Promise<RoutingResult<R>>;
|
|
628
|
+
};
|
|
629
|
+
/**
|
|
630
|
+
* Add a bridge for cross-context communication (idempotent)
|
|
631
|
+
*
|
|
632
|
+
* If a bridge with the given ID already exists, the old bridge is destroyed
|
|
633
|
+
* and replaced with the new one. This prevents duplicate bridges during HMR.
|
|
634
|
+
*
|
|
635
|
+
* @param id - Unique identifier for the bridge (e.g. 'cross-tab', 'iframe-checkout')
|
|
636
|
+
* @param config - Bridge configuration (transport + forward patterns)
|
|
637
|
+
* @returns Function to remove the bridge
|
|
638
|
+
*/
|
|
639
|
+
addBridge(id: string, config: BridgeConfig): () => void;
|
|
640
|
+
/**
|
|
641
|
+
* Register a client instance.
|
|
642
|
+
*
|
|
643
|
+
* @internal Called by the `BrokerClient` constructor.
|
|
644
|
+
*/
|
|
645
|
+
registerClient(client: BrokerClient<T, P>): void;
|
|
646
|
+
/**
|
|
647
|
+
* Unregister a client and remove all its subscriptions.
|
|
648
|
+
*
|
|
649
|
+
* @internal Called by {@link BrokerClient.destroy}.
|
|
650
|
+
*/
|
|
651
|
+
unregisterClient(clientId: ClientID): void;
|
|
652
|
+
/**
|
|
653
|
+
* Get a registered client by ID.
|
|
654
|
+
*
|
|
655
|
+
* @param clientId - Unique client identifier
|
|
656
|
+
* @returns Client instance or undefined if not found
|
|
657
|
+
*
|
|
658
|
+
* @internal Used by the `createClient` facade for idempotency checks.
|
|
659
|
+
*/
|
|
660
|
+
getClient(clientId: ClientID): BrokerClient<T, P> | undefined;
|
|
661
|
+
/**
|
|
662
|
+
* Reset a client: clear all its subscriptions and backpressure strategies
|
|
663
|
+
* while keeping the client registered.
|
|
664
|
+
*
|
|
665
|
+
* Used for idempotent client creation (HMR, re-mounting).
|
|
666
|
+
* Iterates the client's subscriptions and calls unsubscribe() for each,
|
|
667
|
+
* which correctly flushes/destroys backpressure strategies.
|
|
668
|
+
*
|
|
669
|
+
* @param clientId - Unique client identifier
|
|
670
|
+
*
|
|
671
|
+
* @internal Called by {@link BrokerClient.reset} and by the
|
|
672
|
+
* `createClient` facade on idempotent re-creation.
|
|
673
|
+
*/
|
|
674
|
+
resetClient(clientId: ClientID): void;
|
|
675
|
+
/**
|
|
676
|
+
* Register a beforeSend hook
|
|
677
|
+
*
|
|
678
|
+
* Called before routing for ALL messages, including those from bridges.
|
|
679
|
+
* Use message.fromExternal to distinguish local vs external if needed.
|
|
680
|
+
*/
|
|
681
|
+
useBeforeSendHook(hook: BeforeSendHook<T, P>): () => void;
|
|
682
|
+
/**
|
|
683
|
+
* Register an afterSend hook
|
|
684
|
+
* Note: afterSend hooks are called for ALL messages (check message.fromExternal if needed)
|
|
685
|
+
*/
|
|
686
|
+
useAfterSendHook(hook: AfterSendHook<T, P>): () => void;
|
|
687
|
+
/**
|
|
688
|
+
* Register an onSubscribe hook
|
|
689
|
+
*/
|
|
690
|
+
useOnSubscribeHook(hook: OnSubscribeHook<T>): () => void;
|
|
691
|
+
/**
|
|
692
|
+
* Destroy the broker and clean up all resources
|
|
693
|
+
*/
|
|
694
|
+
destroy(): void;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Client — the public contract of a broker-connected client.
|
|
699
|
+
*
|
|
700
|
+
* Instances are obtained via `createClient(id)`. Consumers should treat
|
|
701
|
+
* this as the stable surface — the underlying class is an internal
|
|
702
|
+
* implementation detail and may change in minor releases.
|
|
703
|
+
*
|
|
704
|
+
* A client provides three things:
|
|
705
|
+
* - subscription (`on` / `off`) to a topic,
|
|
706
|
+
* - message emission: broadcast (`emit`) or targeted (`request`),
|
|
707
|
+
* - lifecycle control (`reset`, `destroy`).
|
|
708
|
+
*/
|
|
709
|
+
interface Client<T extends string, P extends Record<T, any>> {
|
|
710
|
+
/** Unique client identifier passed to `createClient(id)`. */
|
|
711
|
+
readonly id: ClientID;
|
|
712
|
+
/**
|
|
713
|
+
* Subscribe to a topic.
|
|
714
|
+
*
|
|
715
|
+
* @param topic - Topic name (e.g. `'user.login.v1'`).
|
|
716
|
+
* @param handler - Handler invoked for every matching message.
|
|
717
|
+
* @param options - Subscription options (backpressure strategy, replay).
|
|
718
|
+
* @returns Unsubscribe function. Equivalent to `client.off(topic)`.
|
|
719
|
+
* @throws If an `onSubscribe` hook rejects the subscription.
|
|
720
|
+
*/
|
|
721
|
+
on<K extends T>(topic: K, handler: HandlerFn<K, P[K]>, options?: SubscriptionOptions): () => void;
|
|
722
|
+
/**
|
|
723
|
+
* Unsubscribe from a topic. No-op if the client was not subscribed.
|
|
724
|
+
*/
|
|
725
|
+
off<K extends T>(topic: K): void;
|
|
726
|
+
/**
|
|
727
|
+
* Broadcast a message to every subscriber of `topic` (multicast).
|
|
728
|
+
*
|
|
729
|
+
* @returns Promise resolving to the aggregated {@link RoutingResult}.
|
|
730
|
+
*/
|
|
731
|
+
emit<K extends T>(topic: K, data: P[K], options?: MessageOptions): Promise<RoutingResult>;
|
|
732
|
+
/**
|
|
733
|
+
* Send a targeted message to a specific recipient (unicast).
|
|
734
|
+
*
|
|
735
|
+
* @typeParam R - Expected shape of the handler's return value, surfaced
|
|
736
|
+
* on `RoutingResult.data`. Defaults to `unknown` — caller must specify
|
|
737
|
+
* to get a typed response (e.g. `client.request<'user.fetch', User>(…)`).
|
|
738
|
+
* Not enforced against the handler signature; treated as a boundary cast.
|
|
739
|
+
* @returns Promise resolving to the {@link RoutingResult} for that one
|
|
740
|
+
* recipient.
|
|
741
|
+
*/
|
|
742
|
+
request<K extends T, R = unknown>(recipient: ClientID, topic: K, data: P[K], options?: MessageOptions): Promise<RoutingResult<R>>;
|
|
743
|
+
/**
|
|
744
|
+
* Reset the client: flush and drop every subscription and its
|
|
745
|
+
* backpressure strategy while keeping the client registered.
|
|
746
|
+
*
|
|
747
|
+
* Used by `createClient(id)` for idempotent creation (HMR, re-mount).
|
|
748
|
+
*/
|
|
749
|
+
reset(): void;
|
|
750
|
+
/**
|
|
751
|
+
* Destroy the client: unregister it and remove all its subscriptions.
|
|
752
|
+
* After `destroy()` the client instance becomes inert.
|
|
753
|
+
*/
|
|
754
|
+
destroy(): void;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* BrokerClient — concrete implementation of the {@link Client} contract.
|
|
759
|
+
*
|
|
760
|
+
* Created by the facade function `createClient(id)`. Use cases:
|
|
761
|
+
* - Communication between microfrontends in the same browser context
|
|
762
|
+
* - Fastest possible message delivery (no serialization)
|
|
763
|
+
* - Default choice for most applications
|
|
764
|
+
*
|
|
765
|
+
* Consumers should treat `Client<T, P>` as the stable surface — this
|
|
766
|
+
* class is an internal implementation detail. It is exported within the
|
|
767
|
+
* package so unit tests can construct instances directly with a
|
|
768
|
+
* `BrokerCore` stub, but it is NOT part of the public API: transport of
|
|
769
|
+
* `BrokerClient` through the package exports map is disabled.
|
|
770
|
+
*/
|
|
771
|
+
declare class BrokerClient<T extends string, P extends Record<T, any>> implements Client<T, P> {
|
|
772
|
+
#private;
|
|
773
|
+
readonly id: ClientID;
|
|
774
|
+
constructor(id: ClientID, core: BrokerCore<T, P>);
|
|
775
|
+
/**
|
|
776
|
+
* Subscribe to a topic with handler
|
|
777
|
+
*
|
|
778
|
+
* @param topic - Topic to subscribe to (e.g. 'user.login.v1')
|
|
779
|
+
* @param handler - Message handler function
|
|
780
|
+
* @param options - Subscription options (backpressure, replay)
|
|
781
|
+
* @returns Unsubscribe function
|
|
782
|
+
*/
|
|
783
|
+
on<K extends T>(topic: K, handler: HandlerFn<K, P[K]>, options?: SubscriptionOptions): () => void;
|
|
784
|
+
/**
|
|
785
|
+
* Emit message to all subscribers (multicast)
|
|
786
|
+
*/
|
|
787
|
+
emit<K extends T>(topic: K, data: P[K], options?: MessageOptions): Promise<RoutingResult>;
|
|
788
|
+
/**
|
|
789
|
+
* Send request to specific client (unicast).
|
|
790
|
+
*
|
|
791
|
+
* The recipient's handler return value (if any) is captured in
|
|
792
|
+
* `RoutingResult.data`. Caller specifies `R` to type that payload.
|
|
793
|
+
* The broker does not enforce that the handler actually returns `R` —
|
|
794
|
+
* the cast happens at the boundary, same trust level as `as R`.
|
|
795
|
+
*/
|
|
796
|
+
request<K extends T, R = unknown>(recipient: ClientID, topic: K, data: P[K], options?: MessageOptions): Promise<RoutingResult<R>>;
|
|
797
|
+
/**
|
|
798
|
+
* Unsubscribe from a topic
|
|
799
|
+
*/
|
|
800
|
+
off<K extends T>(topic: K): void;
|
|
801
|
+
/**
|
|
802
|
+
* Reset client: clear all subscriptions and backpressure strategies
|
|
803
|
+
* while keeping the client registered in the broker.
|
|
804
|
+
*
|
|
805
|
+
* After reset, the client can subscribe to messages again with fresh handlers.
|
|
806
|
+
* Existing backpressure strategies are flushed and destroyed.
|
|
807
|
+
*/
|
|
808
|
+
reset(): void;
|
|
809
|
+
/**
|
|
810
|
+
* Destroy client and cleanup resources
|
|
811
|
+
*/
|
|
812
|
+
destroy(): void;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* ClientRegistry - Manages registered clients
|
|
817
|
+
*
|
|
818
|
+
* Responsibilities:
|
|
819
|
+
* - Register/unregister clients
|
|
820
|
+
* - Track active clients
|
|
821
|
+
* - Provide client lookup
|
|
822
|
+
*
|
|
823
|
+
* Used for observability and DevTools integration
|
|
824
|
+
*/
|
|
825
|
+
declare class ClientRegistry<T extends string, P extends Record<T, any>> {
|
|
826
|
+
#private;
|
|
827
|
+
/**
|
|
828
|
+
* Register a client
|
|
829
|
+
*/
|
|
830
|
+
register(client: BrokerClient<T, P>): void;
|
|
831
|
+
/**
|
|
832
|
+
* Unregister a client
|
|
833
|
+
*/
|
|
834
|
+
unregister(clientId: ClientID): void;
|
|
835
|
+
/**
|
|
836
|
+
* Get the timestamp when a client registered (Unix ms)
|
|
837
|
+
*/
|
|
838
|
+
getConnectedAt(clientId: ClientID): number | undefined;
|
|
839
|
+
/**
|
|
840
|
+
* Get client by ID
|
|
841
|
+
*/
|
|
842
|
+
get(clientId: ClientID): BrokerClient<T, P> | undefined;
|
|
843
|
+
/**
|
|
844
|
+
* Check if client is registered
|
|
845
|
+
*/
|
|
846
|
+
has(clientId: ClientID): boolean;
|
|
847
|
+
/**
|
|
848
|
+
* Get all registered clients
|
|
849
|
+
*/
|
|
850
|
+
getAll(): BrokerClient<T, P>[];
|
|
851
|
+
/**
|
|
852
|
+
* Get all client IDs
|
|
853
|
+
*/
|
|
854
|
+
getAllIds(): ClientID[];
|
|
855
|
+
/**
|
|
856
|
+
* Clear all clients
|
|
857
|
+
*/
|
|
858
|
+
clear(): void;
|
|
859
|
+
/**
|
|
860
|
+
* Get number of registered clients
|
|
861
|
+
*/
|
|
862
|
+
get size(): number;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Subscriptions - Efficient subscription and handler management
|
|
867
|
+
*
|
|
868
|
+
* A single `(clientId, topic)` pair may hold MANY handlers. Each individual
|
|
869
|
+
* subscription is identified by a monotonic numeric id returned from
|
|
870
|
+
* {@link subscribe}, so callers can remove one handler without touching the
|
|
871
|
+
* others. Bulk operations (`unsubscribe(clientId, topic)`,
|
|
872
|
+
* `unsubscribeAll(clientId)`) still nuke every handler in scope.
|
|
873
|
+
*
|
|
874
|
+
* Bidirectional indexes for O(1) lookups:
|
|
875
|
+
* - Topic → Clients mapping (multicast recipient lookup — a client appears
|
|
876
|
+
* once no matter how many handlers it registered)
|
|
877
|
+
* - Client → Topics mapping (unsubscribe fan-out)
|
|
878
|
+
* - Composite key → ordered handler entries
|
|
879
|
+
* - Subscription id → location (for O(1) per-handler removal)
|
|
880
|
+
*
|
|
881
|
+
* @internal This class is used internally by BrokerCore and Router
|
|
882
|
+
*/
|
|
883
|
+
/** Opaque handle returned by {@link Subscriptions.subscribe}. */
|
|
884
|
+
type SubscriptionId = number;
|
|
885
|
+
type SubscriptionEntry = {
|
|
886
|
+
readonly id: SubscriptionId;
|
|
887
|
+
readonly handler: MessageHandler;
|
|
888
|
+
readonly options?: SubscriptionOptions;
|
|
889
|
+
};
|
|
890
|
+
declare class Subscriptions<T extends string> {
|
|
891
|
+
#private;
|
|
892
|
+
/**
|
|
893
|
+
* Reserve a subscription id ahead of {@link subscribe}.
|
|
894
|
+
*
|
|
895
|
+
* Callers that need the id BEFORE the handler is finalized (e.g. to key
|
|
896
|
+
* a backpressure strategy by that id) can pre-allocate here and then
|
|
897
|
+
* pass the reserved id to {@link subscribe}.
|
|
898
|
+
*/
|
|
899
|
+
reserveId(): SubscriptionId;
|
|
900
|
+
/**
|
|
901
|
+
* Subscribe a handler to a (client, topic) pair.
|
|
902
|
+
*
|
|
903
|
+
* Appends a new entry — previously registered handlers on the same pair
|
|
904
|
+
* are preserved. Returns the subscription id so the caller can remove
|
|
905
|
+
* this specific handler later via {@link unsubscribeOne}.
|
|
906
|
+
*
|
|
907
|
+
* If `preReservedId` is provided (from {@link reserveId}), that id is
|
|
908
|
+
* used instead of generating a new one.
|
|
909
|
+
*/
|
|
910
|
+
subscribe(clientId: ClientID, topic: T, handler: MessageHandler, options?: SubscriptionOptions, preReservedId?: SubscriptionId): SubscriptionId;
|
|
911
|
+
/**
|
|
912
|
+
* Remove a single handler by its subscription id.
|
|
913
|
+
*
|
|
914
|
+
* If this was the last handler for the pair, the pair is fully removed
|
|
915
|
+
* from the bidirectional indexes (mirroring `unsubscribe` semantics).
|
|
916
|
+
*
|
|
917
|
+
* @returns Removal outcome: the removed entry, the pair it belonged to,
|
|
918
|
+
* and whether it was the last handler on that pair. `undefined`
|
|
919
|
+
* when no such id existed.
|
|
920
|
+
*/
|
|
921
|
+
unsubscribeOne(id: SubscriptionId): {
|
|
922
|
+
entry: SubscriptionEntry;
|
|
923
|
+
clientId: ClientID;
|
|
924
|
+
topic: T;
|
|
925
|
+
wasLast: boolean;
|
|
926
|
+
} | undefined;
|
|
927
|
+
/**
|
|
928
|
+
* Unsubscribe every handler a client holds on a topic.
|
|
929
|
+
*
|
|
930
|
+
* @returns Entries that were actually removed. Empty when the client had
|
|
931
|
+
* no handlers on the topic. Callers use this to release
|
|
932
|
+
* per-handler resources (e.g. backpressure strategies).
|
|
933
|
+
*/
|
|
934
|
+
unsubscribe(clientId: ClientID, topic: T): readonly SubscriptionEntry[];
|
|
935
|
+
/**
|
|
936
|
+
* Remove every subscription held by a given client.
|
|
937
|
+
*
|
|
938
|
+
* @returns Per-topic entry buckets that were removed, in iteration order.
|
|
939
|
+
* Empty when the client had no active subscriptions. Callers use
|
|
940
|
+
* this to release per-handler resources and emit per-topic
|
|
941
|
+
* `subscription.removed` events.
|
|
942
|
+
*/
|
|
943
|
+
unsubscribeAll(clientId: ClientID): ReadonlyArray<{
|
|
944
|
+
topic: T;
|
|
945
|
+
entries: readonly SubscriptionEntry[];
|
|
946
|
+
}>;
|
|
947
|
+
/**
|
|
948
|
+
* Get all topics a client is subscribed to
|
|
949
|
+
*/
|
|
950
|
+
getClientTopics(clientId: ClientID): ReadonlySet<T> | undefined;
|
|
951
|
+
/**
|
|
952
|
+
* Check if a client has at least one handler on a topic.
|
|
953
|
+
*/
|
|
954
|
+
isSubscribed(clientId: ClientID, topic: T): boolean;
|
|
955
|
+
/**
|
|
956
|
+
* All handler entries a client has on a topic, in registration order.
|
|
957
|
+
*/
|
|
958
|
+
getEntries(clientId: ClientID, topic: T): readonly SubscriptionEntry[];
|
|
959
|
+
/**
|
|
960
|
+
* Options of the first handler registered on `(clientId, topic)`.
|
|
961
|
+
*
|
|
962
|
+
* Convenience for read-only observers (e.g. Inspector) that predate the
|
|
963
|
+
* multi-handler model and expect a single options blob per pair.
|
|
964
|
+
*/
|
|
965
|
+
getFirstOptions(clientId: ClientID, topic: T): SubscriptionOptions | undefined;
|
|
966
|
+
/**
|
|
967
|
+
* Number of handlers a client holds on a topic (0 = not subscribed).
|
|
968
|
+
*/
|
|
969
|
+
getHandlerCount(clientId: ClientID, topic: T): number;
|
|
970
|
+
/**
|
|
971
|
+
* Get all subscribers for a topic (read-only)
|
|
972
|
+
*/
|
|
973
|
+
getSubscribers(topic: T): ReadonlySet<ClientID>;
|
|
974
|
+
/**
|
|
975
|
+
* Get list of all clients that have active subscriptions
|
|
976
|
+
*/
|
|
977
|
+
getAllSubscribedClients(): ClientID[];
|
|
978
|
+
/**
|
|
979
|
+
* Get detailed subscription map for all clients
|
|
980
|
+
*/
|
|
981
|
+
getAllSubscriptions(): Record<string, string[]>;
|
|
982
|
+
/**
|
|
983
|
+
* Clear all subscriptions and handlers.
|
|
984
|
+
*
|
|
985
|
+
* @returns All entries that were held, so the caller can release
|
|
986
|
+
* per-handler resources (e.g. backpressure strategies).
|
|
987
|
+
*/
|
|
988
|
+
clear(): readonly SubscriptionEntry[];
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Entry in message history with metadata
|
|
993
|
+
*/
|
|
994
|
+
interface HistoryEntry<T extends string = string, P = any> {
|
|
995
|
+
/** The message itself (immutable) */
|
|
996
|
+
message: Readonly<Message<T, P>>;
|
|
997
|
+
/** Unix timestamp (ms) when message was recorded */
|
|
998
|
+
timestamp: number;
|
|
999
|
+
/** Sequence number for guaranteed ordering */
|
|
1000
|
+
sequence: number;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Filter for querying message history
|
|
1004
|
+
*/
|
|
1005
|
+
interface HistoryFilter<T extends string = string> {
|
|
1006
|
+
/** Topics to filter (supports glob patterns like 'user.*') */
|
|
1007
|
+
topics?: T[];
|
|
1008
|
+
/** Filter by event sources */
|
|
1009
|
+
sources?: ClientID[];
|
|
1010
|
+
/** Start timestamp (inclusive) */
|
|
1011
|
+
since?: number;
|
|
1012
|
+
/** End timestamp (inclusive) */
|
|
1013
|
+
until?: number;
|
|
1014
|
+
/** Maximum number of messages to return */
|
|
1015
|
+
limit?: number;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Configuration for message history
|
|
1019
|
+
*/
|
|
1020
|
+
interface HistoryConfig {
|
|
1021
|
+
/** Enable message history */
|
|
1022
|
+
enabled: boolean;
|
|
1023
|
+
/** Maximum number of messages to keep in memory (default: 1000) */
|
|
1024
|
+
maxSize?: number;
|
|
1025
|
+
/** Time to live for messages (ms). undefined = no expiration */
|
|
1026
|
+
ttl?: number;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Statistics about message history
|
|
1030
|
+
*/
|
|
1031
|
+
interface HistoryStats {
|
|
1032
|
+
/** Total number of messages in history */
|
|
1033
|
+
count: number;
|
|
1034
|
+
/** Unix timestamp (ms) of oldest message */
|
|
1035
|
+
oldestTimestamp?: number;
|
|
1036
|
+
/** Unix timestamp (ms) of newest message */
|
|
1037
|
+
newestTimestamp?: number;
|
|
1038
|
+
/** Memory usage estimate (bytes) */
|
|
1039
|
+
memoryUsage?: number;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* MessageHistory - In-memory message history
|
|
1044
|
+
*
|
|
1045
|
+
* Features:
|
|
1046
|
+
* - FIFO eviction when maxSize is reached
|
|
1047
|
+
* - TTL-based automatic cleanup
|
|
1048
|
+
* - Glob pattern matching for topics
|
|
1049
|
+
* - Immutable messages (deepFreeze)
|
|
1050
|
+
* - Efficient filtering
|
|
1051
|
+
*/
|
|
1052
|
+
declare class MessageHistory<T extends string, P extends Record<T, any>> {
|
|
1053
|
+
#private;
|
|
1054
|
+
constructor(config: HistoryConfig);
|
|
1055
|
+
/**
|
|
1056
|
+
* Record a message to history
|
|
1057
|
+
*/
|
|
1058
|
+
record(message: Message<T, P[T]>): void;
|
|
1059
|
+
/**
|
|
1060
|
+
* Query messages from history
|
|
1061
|
+
*/
|
|
1062
|
+
query(filter?: HistoryFilter<T>): Promise<HistoryEntry<T, P[T]>[]>;
|
|
1063
|
+
/**
|
|
1064
|
+
* Clear messages from history
|
|
1065
|
+
*/
|
|
1066
|
+
clear(filter?: HistoryFilter<T>): Promise<void>;
|
|
1067
|
+
/**
|
|
1068
|
+
* Return a point-in-time snapshot of all entries (oldest → newest).
|
|
1069
|
+
*/
|
|
1070
|
+
getSnapshot(): ReadonlyArray<HistoryEntry<T, P[T]>>;
|
|
1071
|
+
/**
|
|
1072
|
+
* Get history statistics
|
|
1073
|
+
*/
|
|
1074
|
+
getStats(): HistoryStats;
|
|
1075
|
+
/**
|
|
1076
|
+
* Cleanup and destroy
|
|
1077
|
+
*/
|
|
1078
|
+
destroy(): void;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Read-only view of a registered bridge.
|
|
1083
|
+
*
|
|
1084
|
+
* Produced by `broker.inspect.getBridges()`. Does NOT leak the internal
|
|
1085
|
+
* `Bridge` instance (`transport`, lifecycle methods, etc.) — only the
|
|
1086
|
+
* information useful for DevTools / debugging.
|
|
1087
|
+
*/
|
|
1088
|
+
interface BridgeInfo {
|
|
1089
|
+
/** Unique bridge identifier passed to `addBridge(id, ...)`. */
|
|
1090
|
+
id: string;
|
|
1091
|
+
/** Topic patterns this bridge forwards to its transport. */
|
|
1092
|
+
forwardPatterns: ReadonlyArray<string>;
|
|
1093
|
+
/**
|
|
1094
|
+
* Transport class name with the `Transport` suffix stripped —
|
|
1095
|
+
* e.g. `WebSocket`, `PostMessage`, `SSE`, `BroadcastChannel`, or the
|
|
1096
|
+
* bare constructor name for custom implementations. `undefined` when
|
|
1097
|
+
* the transport was created from an anonymous class expression.
|
|
1098
|
+
*/
|
|
1099
|
+
transportKind?: string;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Inspector - read-only view over broker state.
|
|
1104
|
+
*
|
|
1105
|
+
* Exposed via `broker.inspect`. Intended for DevTools, debugging tools, and
|
|
1106
|
+
* diagnostic integrations that need point-in-time state snapshots (pull model).
|
|
1107
|
+
* Pair with `broker.$systemEvents` (push model) for incremental updates.
|
|
1108
|
+
*
|
|
1109
|
+
* This class is a pure facade over internal registries — it does NOT own data,
|
|
1110
|
+
* it only aggregates and projects it. Dependencies are injected as references,
|
|
1111
|
+
* so adding new snapshot methods does not require changing the constructor
|
|
1112
|
+
* shape or threading callbacks through `BrokerCore`.
|
|
1113
|
+
*
|
|
1114
|
+
* All collection-returning methods return `ReadonlyArray<T>` to prevent
|
|
1115
|
+
* accidental mutation of broker state by external callers.
|
|
1116
|
+
*/
|
|
1117
|
+
declare class Inspector<T extends string, P extends Record<T, any>> {
|
|
1118
|
+
#private;
|
|
1119
|
+
constructor(clients: ClientRegistry<T, P>, subscriptions: Subscriptions<T>, bridges: ReadonlyMap<string, Bridge>, getHistory: () => MessageHistory<T, P> | undefined);
|
|
1120
|
+
/**
|
|
1121
|
+
* Snapshot of every registered client together with its active subscriptions.
|
|
1122
|
+
*
|
|
1123
|
+
* Use together with `$systemEvents.on('client.*' | 'subscription.*')` to
|
|
1124
|
+
* build an accurate initial state without race conditions: read the snapshot
|
|
1125
|
+
* first, then subscribe to events for incremental updates.
|
|
1126
|
+
*/
|
|
1127
|
+
getClients(): ReadonlyArray<ClientInfo>;
|
|
1128
|
+
/**
|
|
1129
|
+
* IDs of clients that have at least one active subscription.
|
|
1130
|
+
*/
|
|
1131
|
+
getSubscribedClientIds(): ReadonlyArray<ClientID>;
|
|
1132
|
+
/**
|
|
1133
|
+
* Lifecycle info for every registered bridge. Does NOT expose internal
|
|
1134
|
+
* `Bridge` instances (see `BridgeInfo`).
|
|
1135
|
+
*/
|
|
1136
|
+
getBridges(): ReadonlyArray<BridgeInfo>;
|
|
1137
|
+
/**
|
|
1138
|
+
* All messages currently stored in the replay buffer (oldest → newest).
|
|
1139
|
+
* Returns an empty array when history is not enabled.
|
|
1140
|
+
*/
|
|
1141
|
+
getHistory(): ReadonlyArray<HistoryEntry>;
|
|
1142
|
+
/**
|
|
1143
|
+
* Replay buffer statistics. Always returns `{ enabled: false, count: 0 }`
|
|
1144
|
+
* when history is not enabled.
|
|
1145
|
+
*/
|
|
1146
|
+
getHistoryStats(): HistoryStats & {
|
|
1147
|
+
enabled: boolean;
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Broker-internal debug channel — see {@link MessageBroker.$debug}.
|
|
1153
|
+
*/
|
|
1154
|
+
interface DebugChannel<T extends string, P extends Record<T, any>> {
|
|
1155
|
+
/**
|
|
1156
|
+
* Inject a message into the pipeline with an arbitrary `source`.
|
|
1157
|
+
*
|
|
1158
|
+
* Runs full routing/hooks/history/bridge-forward like a normal emit;
|
|
1159
|
+
* only difference is `message.synthetic === true` and `source` is not
|
|
1160
|
+
* validated against the client registry. Multicast when `target === '*'`,
|
|
1161
|
+
* unicast otherwise (return value from the handler is captured in
|
|
1162
|
+
* `RoutingResult.data`).
|
|
1163
|
+
*/
|
|
1164
|
+
send<K extends T, R = unknown>(source: ClientID, topic: K, target: ClientID | '*', data: P[K], options?: MessageOptions): Promise<RoutingResult<R>>;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* MessageBroker — the public contract of the broker instance.
|
|
1168
|
+
*
|
|
1169
|
+
* This is the type returned by `initBroker()` / `getBroker()`. It exposes
|
|
1170
|
+
* the stable, user-facing surface of the broker and intentionally HIDES
|
|
1171
|
+
* low-level internals (message pipeline, subscription management, client
|
|
1172
|
+
* registry plumbing) which are accessed via the {@link Client} instances
|
|
1173
|
+
* returned by `createClient()`.
|
|
1174
|
+
*
|
|
1175
|
+
* The broker surface is organized in three groups:
|
|
1176
|
+
*
|
|
1177
|
+
* 1. **Tooling APIs** — stable channels for DevTools / observers.
|
|
1178
|
+
* - `$systemEvents` (push) for lifecycle events.
|
|
1179
|
+
* - `inspect` (pull) for point-in-time state snapshots.
|
|
1180
|
+
*
|
|
1181
|
+
* 2. **Extensibility** — hooks that let adapters and plugins alter or
|
|
1182
|
+
* observe broker behaviour without reaching into internals.
|
|
1183
|
+
* - `useBeforeSendHook`, `useAfterSendHook`, `useOnSubscribeHook`.
|
|
1184
|
+
*
|
|
1185
|
+
* 3. **Infrastructure wiring** — bridges for cross-context delivery.
|
|
1186
|
+
* - `addBridge(id, { transport, forward })`. Pass a {@link BridgeTransport}
|
|
1187
|
+
* instance; built-in implementations are used internally by framework
|
|
1188
|
+
* adapters and are not part of the public surface.
|
|
1189
|
+
*
|
|
1190
|
+
* 4. **Lifecycle**
|
|
1191
|
+
* - `destroy()` for clean shutdown.
|
|
1192
|
+
*/
|
|
1193
|
+
interface MessageBroker<T extends string, P extends Record<T, any>> {
|
|
1194
|
+
/**
|
|
1195
|
+
* Broker-internal system event channel (push model).
|
|
1196
|
+
*
|
|
1197
|
+
* The `$` prefix marks this as a broker-internal API. Intended for
|
|
1198
|
+
* tooling: DevTools, tracing collectors, metrics integrations.
|
|
1199
|
+
*
|
|
1200
|
+
* Not for extending broker behaviour — use the `use*Hook` methods
|
|
1201
|
+
* for that.
|
|
1202
|
+
*
|
|
1203
|
+
* @example
|
|
1204
|
+
* broker.$systemEvents.on('client.registered', ({ clientId }) => { ... });
|
|
1205
|
+
* broker.$systemEvents.on('subscription.added', ({ clientId, topic }) => { ... });
|
|
1206
|
+
*/
|
|
1207
|
+
readonly $systemEvents: SystemEventsEmitter<T, P>;
|
|
1208
|
+
/**
|
|
1209
|
+
* Point-in-time state snapshots (pull model).
|
|
1210
|
+
*
|
|
1211
|
+
* Read-only view over broker state for DevTools and debugging tools.
|
|
1212
|
+
*
|
|
1213
|
+
* @example
|
|
1214
|
+
* const clients = broker.inspect.getClients();
|
|
1215
|
+
* const history = broker.inspect.getHistory();
|
|
1216
|
+
*/
|
|
1217
|
+
readonly inspect: Inspector<T, P>;
|
|
1218
|
+
/**
|
|
1219
|
+
* Broker-internal debug channel (test / DevTools).
|
|
1220
|
+
*
|
|
1221
|
+
* `$debug.send(source, topic, target, data)` runs the full pipeline
|
|
1222
|
+
* with an arbitrary source id and no client-registry side effects.
|
|
1223
|
+
* Messages are tagged `synthetic: true` so tools can distinguish
|
|
1224
|
+
* spoofed traffic from production events. See {@link DebugChannel}.
|
|
1225
|
+
*
|
|
1226
|
+
* The `$` prefix marks this as broker-internal — for DevTools and
|
|
1227
|
+
* integration tests, not for business code.
|
|
1228
|
+
*/
|
|
1229
|
+
readonly $debug: DebugChannel<T, P>;
|
|
1230
|
+
/**
|
|
1231
|
+
* Register a bridge for cross-context communication (idempotent).
|
|
1232
|
+
*
|
|
1233
|
+
* A bridge forwards messages whose topic matches `forward` patterns to
|
|
1234
|
+
* the given {@link BridgeTransport}, and injects messages coming back from
|
|
1235
|
+
* the transport into this broker. Framework adapters supply the transport;
|
|
1236
|
+
* this package does not export concrete transport classes.
|
|
1237
|
+
*
|
|
1238
|
+
* If a bridge with the given `id` already exists, the old one is
|
|
1239
|
+
* destroyed and replaced. This keeps the operation HMR-safe.
|
|
1240
|
+
*
|
|
1241
|
+
* @param id - Unique bridge identifier (e.g. `'cross-tab'`, `'iframe-checkout'`).
|
|
1242
|
+
* @param config - Bridge configuration: `transport` + `forward` patterns.
|
|
1243
|
+
* @returns Function that removes the bridge and tears down its listeners.
|
|
1244
|
+
*/
|
|
1245
|
+
addBridge(id: string, config: BridgeConfig): () => void;
|
|
1246
|
+
/**
|
|
1247
|
+
* Register a `beforeSend` hook.
|
|
1248
|
+
*
|
|
1249
|
+
* Invoked synchronously before every message enters the routing stage,
|
|
1250
|
+
* for both locally-emitted AND externally-injected (bridge) messages.
|
|
1251
|
+
* Use `message.fromExternal` to distinguish.
|
|
1252
|
+
*
|
|
1253
|
+
* A hook returning `{ allowed: false, message }` short-circuits the
|
|
1254
|
+
* pipeline with a `NACK(HOOK_REJECTED)`.
|
|
1255
|
+
*/
|
|
1256
|
+
useBeforeSendHook(hook: BeforeSendHook<T, P>): () => void;
|
|
1257
|
+
/**
|
|
1258
|
+
* Register an `afterSend` hook.
|
|
1259
|
+
*
|
|
1260
|
+
* Invoked after routing completes, regardless of success. Receives the
|
|
1261
|
+
* frozen message and the final {@link RoutingResult}. Runs for both
|
|
1262
|
+
* local and external messages.
|
|
1263
|
+
*/
|
|
1264
|
+
useAfterSendHook(hook: AfterSendHook<T, P>): () => void;
|
|
1265
|
+
/**
|
|
1266
|
+
* Register an `onSubscribe` hook.
|
|
1267
|
+
*
|
|
1268
|
+
* Invoked synchronously when a client subscribes to a topic. A hook
|
|
1269
|
+
* returning `{ allowed: false, message }` prevents the subscription —
|
|
1270
|
+
* `BrokerClient.on()` will throw with `message`.
|
|
1271
|
+
*/
|
|
1272
|
+
useOnSubscribeHook(hook: OnSubscribeHook<T>): () => void;
|
|
1273
|
+
/**
|
|
1274
|
+
* Shut the broker down and release all resources.
|
|
1275
|
+
*
|
|
1276
|
+
* Destroys every bridge, clears subscriptions, history, hooks and the
|
|
1277
|
+
* client registry. After `destroy()` the broker becomes inert: further
|
|
1278
|
+
* calls are no-ops with console warnings.
|
|
1279
|
+
*/
|
|
1280
|
+
destroy(): void;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Initialize the message broker (idempotent).
|
|
1285
|
+
*
|
|
1286
|
+
* Called once by the host application (e.g. the shell / app bootstrap).
|
|
1287
|
+
* If the broker is already initialized, the existing instance is returned
|
|
1288
|
+
* without changes. To reinitialize, call {@link destroyBroker} first.
|
|
1289
|
+
*
|
|
1290
|
+
* @param config - Broker configuration (history, etc.).
|
|
1291
|
+
* @returns The broker instance typed against the caller's Topics/Payloads.
|
|
1292
|
+
*/
|
|
1293
|
+
declare function initBroker<T extends string = string, P extends Record<T, any> = any>(config?: BrokerConfig): MessageBroker<T, P>;
|
|
1294
|
+
/**
|
|
1295
|
+
* Create or retrieve a client for message communication (idempotent).
|
|
1296
|
+
*
|
|
1297
|
+
* If a client with the given ID already exists, its subscriptions and
|
|
1298
|
+
* backpressure strategies are reset and the existing instance is returned.
|
|
1299
|
+
* Safe for HMR and component re-mounting scenarios.
|
|
1300
|
+
*
|
|
1301
|
+
* Pass explicit type parameters to get a fully typed client without a cast:
|
|
1302
|
+
*
|
|
1303
|
+
* @example
|
|
1304
|
+
* import type { Topic, TopicPayloads } from '@hedwigjs/registry';
|
|
1305
|
+
* const client = createClient<Topic, TopicPayloads>('cart');
|
|
1306
|
+
*
|
|
1307
|
+
* @param id - Unique identifier for the client.
|
|
1308
|
+
* @throws Error if the broker has not been initialized.
|
|
1309
|
+
*/
|
|
1310
|
+
declare function createClient<T extends string = string, P extends Record<T, any> = any>(id: string): Client<T, P>;
|
|
1311
|
+
/**
|
|
1312
|
+
* Get the current broker instance.
|
|
1313
|
+
*
|
|
1314
|
+
* Use when you need access to broker methods (hooks, `$systemEvents`,
|
|
1315
|
+
* `inspect`, `addBridge`) without holding the reference returned by
|
|
1316
|
+
* {@link initBroker}.
|
|
1317
|
+
*
|
|
1318
|
+
* Types can be passed explicitly: `getBroker<MyTopics, MyPayloads>()`.
|
|
1319
|
+
*
|
|
1320
|
+
* @throws Error if the broker has not been initialized.
|
|
1321
|
+
*/
|
|
1322
|
+
declare function getBroker<T extends string = string, P extends Record<T, any> = any>(): MessageBroker<T, P>;
|
|
1323
|
+
/**
|
|
1324
|
+
* Destroy the broker and release all resources.
|
|
1325
|
+
*
|
|
1326
|
+
* Destroys bridges, clears subscriptions, history, and the client registry.
|
|
1327
|
+
*/
|
|
1328
|
+
declare function destroyBroker(): void;
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* Configuration for PostMessageTransport
|
|
1332
|
+
*/
|
|
1333
|
+
interface PostMessageTransportConfig {
|
|
1334
|
+
/** Target window to communicate with (iframe.contentWindow, window.parent, etc.) */
|
|
1335
|
+
target: Window;
|
|
1336
|
+
/**
|
|
1337
|
+
* Target origin for outbound `postMessage` calls.
|
|
1338
|
+
* Use `'*'` only for trusted contexts — the browser will otherwise refuse
|
|
1339
|
+
* to deliver the message if the target's origin doesn't match.
|
|
1340
|
+
* @default '*'
|
|
1341
|
+
*/
|
|
1342
|
+
origin?: string;
|
|
1343
|
+
/**
|
|
1344
|
+
* Explicit allowlist for **inbound** message origins.
|
|
1345
|
+
*
|
|
1346
|
+
* When set, only messages whose `e.origin` is included in this list are
|
|
1347
|
+
* forwarded to the broker; others are dropped with a `console.warn`. This
|
|
1348
|
+
* is the trust boundary between the broker and cross-origin iframes.
|
|
1349
|
+
*
|
|
1350
|
+
* When omitted, inbound validation falls back to `origin`:
|
|
1351
|
+
* - If `origin` is an explicit URL, it acts as a single-item allowlist.
|
|
1352
|
+
* - If `origin` is `'*'`, all origins are accepted (a `console.warn` is
|
|
1353
|
+
* emitted at construction time — this mode is intended only for
|
|
1354
|
+
* trusted contexts and should not be used in production against
|
|
1355
|
+
* untrusted iframes).
|
|
1356
|
+
*
|
|
1357
|
+
* Prefer setting `allowedOrigins` explicitly for cross-origin scenarios.
|
|
1358
|
+
*/
|
|
1359
|
+
allowedOrigins?: string[];
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* PostMessageTransport - Transport for cross-window communication
|
|
1363
|
+
*
|
|
1364
|
+
* Uses `window.postMessage` for iframe/popup communication.
|
|
1365
|
+
*
|
|
1366
|
+
* Security:
|
|
1367
|
+
* - Validates message source window (must match configured `target`).
|
|
1368
|
+
* - Validates message origin against `allowedOrigins` (explicit allowlist)
|
|
1369
|
+
* or `origin` (fallback single-item allowlist).
|
|
1370
|
+
*/
|
|
1371
|
+
declare class PostMessageTransport implements BridgeTransport {
|
|
1372
|
+
#private;
|
|
1373
|
+
constructor(config: PostMessageTransportConfig);
|
|
1374
|
+
/**
|
|
1375
|
+
* Send data to target window via postMessage
|
|
1376
|
+
*/
|
|
1377
|
+
send(data: unknown): void;
|
|
1378
|
+
/**
|
|
1379
|
+
* Subscribe to incoming messages from target window
|
|
1380
|
+
*/
|
|
1381
|
+
onMessage(callback: (data: unknown) => void): () => void;
|
|
1382
|
+
/**
|
|
1383
|
+
* Cleanup: remove event listener
|
|
1384
|
+
*/
|
|
1385
|
+
destroy(): void;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* BroadcastChannelTransport - Transport for cross-tab communication
|
|
1390
|
+
*
|
|
1391
|
+
* Uses BroadcastChannel API for communication between browser tabs
|
|
1392
|
+
* of the same origin.
|
|
1393
|
+
*
|
|
1394
|
+
* Use cases:
|
|
1395
|
+
* - Sync user session across tabs
|
|
1396
|
+
* - Sync theme/locale preferences
|
|
1397
|
+
* - Broadcast notifications to all tabs
|
|
1398
|
+
*/
|
|
1399
|
+
declare class BroadcastChannelTransport implements BridgeTransport {
|
|
1400
|
+
#private;
|
|
1401
|
+
/**
|
|
1402
|
+
* @param channelName - Unique channel name for this application
|
|
1403
|
+
*/
|
|
1404
|
+
constructor(channelName: string);
|
|
1405
|
+
/**
|
|
1406
|
+
* Broadcast data to all other tabs
|
|
1407
|
+
*/
|
|
1408
|
+
send(data: unknown): void;
|
|
1409
|
+
/**
|
|
1410
|
+
* Subscribe to messages from other tabs
|
|
1411
|
+
*/
|
|
1412
|
+
onMessage(callback: (data: unknown) => void): () => void;
|
|
1413
|
+
/**
|
|
1414
|
+
* Cleanup: close the channel
|
|
1415
|
+
*/
|
|
1416
|
+
destroy(): void;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* WebSocketTransport - Transport wrapper for WebSocket
|
|
1421
|
+
*
|
|
1422
|
+
* Simple wrapper that forwards messages to/from an existing WebSocket.
|
|
1423
|
+
* All connection management (connect, reconnect, etc.) is handled externally.
|
|
1424
|
+
*/
|
|
1425
|
+
declare class WebSocketTransport implements BridgeTransport {
|
|
1426
|
+
#private;
|
|
1427
|
+
/**
|
|
1428
|
+
* @param socket - WebSocket instance (managed externally)
|
|
1429
|
+
*/
|
|
1430
|
+
constructor(socket: WebSocket);
|
|
1431
|
+
/**
|
|
1432
|
+
* Send data to server via WebSocket
|
|
1433
|
+
*/
|
|
1434
|
+
send(data: unknown): void;
|
|
1435
|
+
/**
|
|
1436
|
+
* Subscribe to messages from server
|
|
1437
|
+
*/
|
|
1438
|
+
onMessage(callback: (data: unknown) => void): () => void;
|
|
1439
|
+
/**
|
|
1440
|
+
* Cleanup: remove listener (does NOT close socket)
|
|
1441
|
+
*/
|
|
1442
|
+
destroy(): void;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* Configuration for {@link SSETransport}.
|
|
1447
|
+
*/
|
|
1448
|
+
interface SSETransportConfig {
|
|
1449
|
+
/**
|
|
1450
|
+
* URL of the Server-Sent Events endpoint. `EventSource` is created
|
|
1451
|
+
* internally and the browser handles reconnection.
|
|
1452
|
+
*/
|
|
1453
|
+
url: string;
|
|
1454
|
+
/**
|
|
1455
|
+
* If set, subscribes to a named SSE event (`event: <name>`) instead of
|
|
1456
|
+
* the default unnamed `message` stream. Broker Messages already carry
|
|
1457
|
+
* their own `topic` field, so most integrations leave this unset and
|
|
1458
|
+
* multiplex on the topic.
|
|
1459
|
+
*/
|
|
1460
|
+
eventName?: string;
|
|
1461
|
+
/**
|
|
1462
|
+
* Passed to `new EventSource(url, { withCredentials })`. Enables sending
|
|
1463
|
+
* cookies for same-origin auth on cross-origin SSE endpoints.
|
|
1464
|
+
*/
|
|
1465
|
+
withCredentials?: boolean;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* SSETransport — inbound-only transport backed by `EventSource`.
|
|
1469
|
+
*
|
|
1470
|
+
* SSE is server → client by design. `send()` is a no-op with a warning;
|
|
1471
|
+
* bridges built on top of this transport are effectively receive-only.
|
|
1472
|
+
* If your integration needs client → server frames, use
|
|
1473
|
+
* {@link WebSocketTransport} or pair SSE with a separate POST endpoint.
|
|
1474
|
+
*
|
|
1475
|
+
* Reconnect handling is delegated to the browser's built-in EventSource
|
|
1476
|
+
* behavior — no external backoff required, unlike WebSocket where the
|
|
1477
|
+
* transport wraps an already-connected socket.
|
|
1478
|
+
*
|
|
1479
|
+
* Expects incoming payloads to be JSON-encoded broker Messages
|
|
1480
|
+
* (`{id, topic, source, target, data, timestamp}`) — the same wire
|
|
1481
|
+
* format all the other bridges use.
|
|
1482
|
+
*/
|
|
1483
|
+
declare class SSETransport implements BridgeTransport {
|
|
1484
|
+
#private;
|
|
1485
|
+
constructor(config: SSETransportConfig);
|
|
1486
|
+
/**
|
|
1487
|
+
* SSE has no upstream channel from the browser. This method exists to
|
|
1488
|
+
* satisfy the {@link BridgeTransport} contract but never actually
|
|
1489
|
+
* transmits — it logs a warning so misconfigurations surface early.
|
|
1490
|
+
*
|
|
1491
|
+
* Practical guidance: keep the bridge's `forward` list to topics that
|
|
1492
|
+
* are ONLY emitted by the server (never by local clients), so this
|
|
1493
|
+
* warning never fires in normal operation.
|
|
1494
|
+
*/
|
|
1495
|
+
send(_data: unknown): void;
|
|
1496
|
+
/**
|
|
1497
|
+
* Subscribe to incoming SSE messages. Parses JSON payloads before
|
|
1498
|
+
* forwarding to the bridge.
|
|
1499
|
+
*/
|
|
1500
|
+
onMessage(callback: (data: unknown) => void): () => void;
|
|
1501
|
+
/**
|
|
1502
|
+
* Cleanup: remove listener and close the underlying EventSource.
|
|
1503
|
+
*/
|
|
1504
|
+
destroy(): void;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
export { type AfterSendHook, type BackpressureOptions, type BeforeSendHook, type BridgeConfig, type BridgeInfo, type BridgeTransport, BroadcastChannelTransport, type BrokerConfig, type BrokerLogEvent, type BrokerLogger, type Client, type ClientID, type ClientInfo, type ClientSubscriptionInfo, type HandlerFn, type HistoryEntry, type HistoryStats, type HookResult, Inspector, type Message, type MessageBroker, type MessageHandler, type MessageOptions, type OnSubscribeHook, PostMessageTransport, type PostMessageTransportConfig, type ReplayOptions, RoutingReason, type RoutingReasonType, RoutingResult, SSETransport, type SSETransportConfig, type SubscriptionOptions, type SystemAnyEventListener, type SystemEventListener, type SystemEventMap, type SystemEventName, type SystemEventPayload, type SystemEventsEmitter, WebSocketTransport, createClient, defaultLogger, destroyBroker, getBroker, initBroker };
|