first commit
This commit is contained in:
107
docs/foundation/data/web-workers/src/message-bus/message-bus.md
Normal file
107
docs/foundation/data/web-workers/src/message-bus/message-bus.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# `message-bus`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`message-bus.ts` declares `MessageBus`, an in-process publish/subscribe +
|
||||
request/response message bus. It's the right tool when you want to
|
||||
decouple modules without actually crossing a worker boundary — every
|
||||
listener and handler runs synchronously on the main thread.
|
||||
|
||||
The bus complements the worker managers: managers handle cross-thread
|
||||
RPC over `postMessage`, while the bus handles same-thread routing.
|
||||
Modules that produce events (e.g. an UI store) publish to a topic, and
|
||||
modules that consume them (e.g. an analytics pipeline) subscribe.
|
||||
|
||||
## Features
|
||||
|
||||
- Topic-based pub/sub: `subscribe(topic, handler)`, `subscribeOnce`,
|
||||
`publish<T>(topic, payload)`, `broadcast<T>(payload)`.
|
||||
- Request/response: `registerHandler(topic, handler)`, `request(topic, payload, options?)`
|
||||
with `timeout` and `signal`.
|
||||
- Returns delivery counts from `publish` / `broadcast` so callers know how
|
||||
many subscribers received the message.
|
||||
- `subscriberCount`, `getTopics` (only topics with live subscribers),
|
||||
`clear` (drop everything + emit `bus:cleared`).
|
||||
- `EventBus<MessageBusEventMap>` lifecycle event channel.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { MessageBus } from "@suaveplan/web-workers";
|
||||
|
||||
const bus = new MessageBus();
|
||||
bus.subscribe<UserEvent>("user-login", (event) =>
|
||||
analytics.track("login", event)
|
||||
);
|
||||
bus.publish("user-login", { userId, at: Date.now() });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { MessageBus, WEB_WORKERS_CODES } from "@suaveplan/web-workers";
|
||||
|
||||
const bus = new MessageBus();
|
||||
|
||||
// Request handler with async work + abort support.
|
||||
bus.registerHandler<SearchQuery, SearchResult>(
|
||||
"search.execute",
|
||||
async (query) => {
|
||||
const docs = await searchIndex.query(query);
|
||||
return { hits: docs.length, results: docs };
|
||||
}
|
||||
);
|
||||
|
||||
const ctrl = new AbortController();
|
||||
try {
|
||||
const result = await bus.request<SearchQuery, SearchResult>(
|
||||
"search.execute",
|
||||
{ text: "hello", filter: { lang: "en" } },
|
||||
{ signal: ctrl.signal, timeout: 5_000 }
|
||||
);
|
||||
renderResults(result);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof Error &&
|
||||
(err as { code?: string }).code === WEB_WORKERS_CODES.REQUEST_TIMEOUT
|
||||
) {
|
||||
renderTimeout();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class MessageBus`.
|
||||
- `constructor(options?: MessageBusOptions)`.
|
||||
- `subscribe<T>(topic, handler): () => void`.
|
||||
- `subscribeOnce<T>(topic, handler): () => void`.
|
||||
- `publish<T>(topic, payload): number` (delivery count).
|
||||
- `broadcast<T>(payload): number`.
|
||||
- `registerHandler<TReq, TRes>(topic, handler)`.
|
||||
- `request<TReq, TRes>(topic, payload, options?: BusRequestOptions): Promise<TRes>`.
|
||||
- throws `WEB_WORKERS.HANDLER_NOT_FOUND`,
|
||||
`WEB_WORKERS.REQUEST_TIMEOUT`, `WEB_WORKERS.REQUEST_ABORTED`.
|
||||
- `subscriberCount(topic): number`, `getTopics(): string[]`,
|
||||
`clear(): void`.
|
||||
- `readonly events: EventBus<MessageBusEventMap>`.
|
||||
- `type MessageHandler<T> = (payload: T, message: WorkerMessage<T>) => void`.
|
||||
- `type RequestHandler<TReq, TRes> = (payload: TReq) => TRes | Promise<TRes>`.
|
||||
- `interface BusRequestOptions { timeout?: number; signal?: AbortSignal }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The bus uses two separate maps: `subscriptions` (per-topic array of
|
||||
once/persistent handlers) and `requestHandlers` (single handler per topic).
|
||||
This mirrors the upstream API so consumers can pub/sub and request/respond
|
||||
without coupling the two channels. `subscribeOnce` is implemented by
|
||||
marking the entry `once: true` and filtering it out on the publish loop —
|
||||
the unsubscribe closure returned by `subscribeOnce` removes the entry
|
||||
proactively if the consumer wants to cancel before the first publish.
|
||||
`request` is `async` to keep the type contract clean, but the handler is
|
||||
invoked synchronously inside the promise body so consumers can rely on
|
||||
the handler running before the next microtask.
|
||||
Reference in New Issue
Block a user