@vielzeug/codex 2.0.0 → 2.0.2

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.
Files changed (47) hide show
  1. package/data/catalog.json +139 -130
  2. package/data/llms-full.txt +13091 -17593
  3. package/data/llms.txt +12 -11
  4. package/data/manifest.json +1 -1
  5. package/data/packages/arsenal.json +1 -1
  6. package/data/packages/assay.json +1 -1
  7. package/data/packages/clockwork.json +2 -2
  8. package/data/packages/codex.json +1 -1
  9. package/data/packages/coins.json +1 -1
  10. package/data/packages/conduit.json +1 -1
  11. package/data/packages/courier.json +1 -1
  12. package/data/packages/dnd.json +14 -12
  13. package/data/packages/familiar.json +26 -16
  14. package/data/packages/flux.json +1 -1
  15. package/data/packages/forge.json +1 -1
  16. package/data/packages/herald.json +19 -33
  17. package/data/packages/keymap.json +13 -19
  18. package/data/packages/ledger.json +28 -25
  19. package/data/packages/lingua.json +30 -28
  20. package/data/packages/necromancer.json +50 -0
  21. package/data/packages/orbit.json +34 -39
  22. package/data/packages/ore.json +1 -1
  23. package/data/packages/prism.json +37 -40
  24. package/data/packages/pulse.json +26 -24
  25. package/data/packages/refine.json +1 -1
  26. package/data/packages/ripple.json +1 -1
  27. package/data/packages/rune.json +6 -7
  28. package/data/packages/sandbox.json +7 -6
  29. package/data/packages/scout.json +10 -10
  30. package/data/packages/scroll.json +18 -17
  31. package/data/packages/sourcerer.json +1 -1
  32. package/data/packages/spell.json +1 -1
  33. package/data/packages/tempo.json +49 -81
  34. package/data/packages/vault.json +37 -40
  35. package/data/packages/ward.json +5 -17
  36. package/data/packages/wayfinder.json +9 -9
  37. package/data/refine.json +4914 -4914
  38. package/data/search.json +210 -211
  39. package/dist/cli.js +1 -1
  40. package/dist/cli.js.map +1 -1
  41. package/dist/http.js +46 -6
  42. package/dist/http.js.map +1 -1
  43. package/dist/server.js +1 -1
  44. package/dist/server.js.map +1 -1
  45. package/dist/tools/index.js +13 -5
  46. package/dist/tools/index.js.map +1 -1
  47. package/package.json +4 -4
@@ -1,9 +1,9 @@
1
1
  {
2
- "apiSource": "export { createBehaviorBus } from './behavior-bus';\nexport { combineSignals, createBus } from './bus';\nexport { BusDisposedError, HeraldConfigError, HeraldError } from './errors';\nexport { pipeEvents } from './pipe';\nexport type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';\n// _delegate.ts is intentionally not exported — internal use only.\n",
2
+ "apiSource": "export { combineSignals, createBus } from './bus';\nexport { BusDisposedError, HeraldConfigError, HeraldError } from './errors';\nexport { pipeEvents } from './pipe';\nexport type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';\n",
3
3
  "docs": {
4
- "index": "---\ntitle: Herald — Typed event bus for TypeScript\ndescription: Zero-dependency typed event bus with subscribe/emit, wait(), async streams, AbortSignal support, bus piping, and test helpers.\npackage: herald\ncategory: events\nkeywords: [event-bus, typed-events, pub-sub, reactive, decoupled, async-streams]\nrelated: [ripple, wayfinder, familiar]\nexports:\n [\n createBus,\n createBehaviorBus,\n pipeEvents,\n combineSignals,\n BusDisposedError,\n HeraldConfigError,\n debugBus,\n debugBehaviorBus,\n createTestBus,\n ]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"herald\" />\n\n## Why Herald?\n\nManual event emitters lack TypeScript inference across event names and payloads, and offer no async patterns `await`ing an event or streaming all future emits requires bespoke wiring.\n\n```ts\n// Before — manual typed event bus\ntype Handlers = { 'user:login': (p: { userId: string }) => void };\nconst listeners = new Map<keyof Handlers, Set<Function>>();\nfunction on<K extends keyof Handlers>(event: K, fn: Handlers[K]) {\n /* ... */\n}\nfunction emit<K extends keyof Handlers>(event: K, payload: Parameters<Handlers[K]>[0]) {\n /* ... */\n}\n// No await, no stream, no AbortSignal, no error isolation\n\n// After — Herald\nimport { createBus } from '@vielzeug/herald';\nconst bus = createBus<AppEvents>();\nbus.on('user:login', ({ userId }) => loadProfile(userId));\nbus.emit('user:login', { userId: '42', email: 'alice@example.com' });\nconst session = await bus.wait('user:login'); // async one-shot\nfor await (const event of bus.events('cart:updated')) {\n} // async stream\n```\n\n| Feature | Herald | mitt | EventEmitter3 |\n| -------------------- | ----------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------- |\n| Bundle size | <PackageInfo package=\"herald\" type=\"size\" /> | ~200 B | ~1.5 kB |\n| TypeScript inference | <ore-icon name=\"check\" size=\"16\"></ore-icon> Full | <ore-icon name=\"triangle-alert\" size=\"16\"></ore-icon> Basic | <ore-icon name=\"triangle-alert\" size=\"16\"></ore-icon> Basic |\n| Async/await (`wait`) | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Async streaming | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| AbortSignal | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Event piping | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Wildcard (`onAny`) | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Disposal signal | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Error isolation | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Herald when** you need a fully-typed event bus with async patterns (`wait`, `events` generator) and AbortSignal-based lifecycle management.\n\n**Consider mitt when** you only need a bare-minimum synchronous pub/sub with the smallest possible footprint.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/herald\n```\n\n```sh [npm]\nnpm install @vielzeug/herald\n```\n\n```sh [yarn]\nyarn add @vielzeug/herald\n```\n\n:::\n\n## Quick Start\n\n```ts\nimport { BusDisposedError, createBus, pipeEvents } from '@vielzeug/herald';\n\ntype AppEvents = {\n 'user:login': { userId: string; email: string };\n 'user:logout': void;\n};\n\nconst bus = createBus<AppEvents>();\n\nbus.on('user:login', ({ userId }) => {\n console.log('Logged in:', userId);\n});\n\nbus.emit('user:login', { email: 'alice@example.com', userId: '42' });\nbus.emit('user:logout');\n\nconst nextLogin = await bus.wait('user:login');\nconst nextSessionChange = await bus.waitAny(['user:login', 'user:logout']);\n\nif (nextSessionChange.event === 'user:login') {\n console.log(nextSessionChange.payload.userId);\n}\n\nfor await (const payload of bus.events('user:login', { signal: AbortSignal.timeout(5_000) })) {\n console.log(payload.email);\n}\n\n// Forward selected events to another bus\nconst auditBus = createBus<AppEvents>();\nconst unpipe = pipeEvents(bus, auditBus, ['user:login', 'user:logout']);\n\n// Disposal signal — use as an AbortSignal for external cleanup\notherBus.on('count', handler, { signal: bus.disposalSignal });\n\ntry {\n await bus.wait('user:login', { signal: AbortSignal.timeout(500) });\n} catch (err) {\n if (err instanceof BusDisposedError) {\n console.log('Bus was disposed');\n }\n}\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- **Typed event maps** for strict event/payload correctness\n- **Persistent + one-shot listeners** with `on` and `once` — each registration is independent, including duplicate handlers\n- **Wildcard listeners** with `onAny` — subscribe to all events for cross-cutting concerns like logging and analytics\n- **Listener management APIs** with unsubscribe handles, `wildcardCount()`, and `eventNames()`\n- **Async event coordination** with `wait`\n- **First-event racing** with `waitAny`\n- **Async streaming** with `events` — eager subscription buffers events from the moment `events()` is called; `await using` ensures cleanup on early `break`\n- **Event piping** with `pipeEvents` — forward events across buses with optional renaming and automatic teardown\n- **Middleware pipeline** via `createBus({ middleware: [...] })` — intercept or block dispatches before listeners run\n- **Payload validation** via `createBus({ validatePayload: ... })` — schema-level guards applied before middleware\n- **Disposal signal** via `bus.disposalSignal` — use as an `AbortSignal` to tie external lifecycles to the bus\n- **Leak detection** via `maxListeners` — warn when a single event accumulates too many listeners\n- **Named buses** via `createBus({ name: 'myBus' })` — name appears in debug log prefixes and `BusDisposedError` messages for easier debugging across multiple bus instances\n- **Debug logging** via `logger.debug` or `debugBus()` / `debugBehaviorBus()` (`@vielzeug/herald/devtools`) logs subscribe/emit/dispose activity with `[herald:*]` prefixes; tree-shaken from production bundles\n- **Abort-aware APIs** for lifecycle-safe teardown\n- **`onAny()` wildcard listener** for bus-wide observability; `wildcardCount()` to inspect active wildcards\n- **Custom logger** via `createBus({ logger: { debug, warn } })` — route or suppress debug and warn output\n- **`onError` hook** for listener-error isolation and resilience\n- **`dispose` and `[Symbol.dispose]`** for deterministic cleanup\n- **Testing helper** via `@vielzeug/herald/testing`\n- **Zero dependencies** — <PackageInfo package=\"herald\" type=\"size\" /> gzipped, <PackageInfo package=\"herald\" type=\"dependencies\" /> dependencies\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Ripple](/ripple/) — reactive signals and computed state that pair naturally with event-driven update patterns\n- [Wayfinder](/wayfinder/) — client-side router whose navigation lifecycle hooks integrate with bus-dispatched events\n- [Familiar](/familiar/) — Web Worker pool that can use a bus to stream task progress and completion events\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
- "api": "---\ntitle: Herald — API Reference\ndescription: Complete API reference for @vielzeug/herald.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --------------------- | ---------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------- |\n| `createBus()` | Create a typed event bus instance | Sync | Use a strict event map to avoid payload drift |\n| `createBehaviorBus()` | Create a bus that replays the last value to new subs | Sync | `events()` and `wait()` do not replay; `once()` fires immediately if buffer exists |\n| `pipeEvents()` | Forward events from one bus to another | Sync | Supports cross-type buses and event renaming |\n| `bus.on()` | Persistent subscription with optional `once` option | Sync | Pass `{ signal }` to auto-unsubscribe |\n| `bus.emit()` | Emit an event; returns listener invocation count | Sync | Every listener runs even if one throws; first error rethrows after |\n| `bus.events()` | Stream future emits as an async generator | Async | Subscribes eagerly; use `maxBuffer` to cap the buffer |\n| `combineSignals()` | Merge N AbortSignals into one | Sync | Returns the first already-aborted signal early |\n| `bus.wait()` | Await a one-time event occurrence | Async | Pass `{ signal }` for timeout / cancellation |\n| `bus.waitAny()` | Await the first event from many | Async | Result is a discriminated union by event key |\n| `bus.onAny()` | Subscribe to all events | Sync | Fires after event-specific listeners |\n| `bus.eventNames()` | Inspect events with active listeners | Sync | Snapshot reflects current subscriptions |\n| `createTestBus()` | Create deterministic test bus utilities | Sync | Reset emitted events between test cases |\n\n## Package Entry Points\n\n| Import | Purpose |\n| --------------------------- | ---------------------------------------------------------- |\n| `@vielzeug/herald` | Main runtime API and types |\n| `@vielzeug/herald/devtools` | `debugBus`, `debugBehaviorBus` — debug wrappers (dev only) |\n| `@vielzeug/herald/testing` | Test helpers (`createTestBus`, `TestBus<T>` type) |\n\n## Types\n\n`EventMap`, `EventKey<T>`, `Listener<T>`, `Unsubscribe` — simple type aliases:\n\n```ts\ntype EventMap = Record<string, unknown>;\ntype EventKey<T extends EventMap> = keyof T & string;\ntype Listener<T> = (payload: T) => void;\ntype Unsubscribe = () => void;\n```\n\n`BusOptions<T>` — options object passed to `createBus()` and `createBehaviorBus()`:\n\n```ts\ntype SubscribeOptions = {\n once?: boolean; // auto-remove after first invocation\n signal?: AbortSignal; // auto-remove when signal aborts\n};\n\ntype EmissionErrorContext<T extends EventMap = EventMap> = {\n err: unknown; // the thrown error\n event: EventKey<T>; // event key that triggered the failing listener\n payload: unknown; // payload passed to the listener\n timestamp: number; // ms since epoch at emit() call time\n};\n\ntype BusLogger = {\n debug?: (msg: string) => void; // omit to silence debug output\n warn?: (msg: string) => void; // omit to silence warn output\n};\n\ntype BusOptions<T extends EventMap> = {\n logger?: BusLogger;\n maxListeners?: number;\n middleware?: readonly Middleware<T>[];\n name?: string; // optional display name — appears in log prefixes and BusDisposedError messages; avoid sensitive/user-derived values\n onError?: (context: EmissionErrorContext<T>) => void;\n validatePayload?: <K extends EventKey<T>>(event: K, payload: T[K]) => void;\n};\n```\n\n`Middleware<T>` — a function called in sequence on every `emit()`, before listeners run. Call `next()` to continue; omit it to block dispatch:\n\n```ts\ntype Middleware<T extends EventMap = EventMap> = (event: EventKey<T>, payload: unknown, next: () => void) => void;\n```\n\n`PipeableKey<S, T>` — keys shared between two event maps with compatible payload types:\n\n```ts\ntype PipeableKey<S extends EventMap, T extends EventMap> = {\n [K in EventKey<S> & EventKey<T>]: S[K] extends T[K] ? K : never;\n}[EventKey<S> & EventKey<T>];\n```\n\n`PipeEntry<S, T>` — a single entry for `pipeEvents`, either a key string or a `{ from, to }` rename:\n\n```ts\ntype PipeEntry<S extends EventMap, T extends EventMap> = PipeableKey<S, T> | { from: EventKey<S>; to: EventKey<T> };\n```\n\n`EventStream<T>` — returned by `bus.events()`. Extends `AsyncGenerator<T>` with `AsyncDisposable`:\n\n```ts\ntype EventStream<T> = AsyncGenerator<T> & AsyncDisposable;\n```\n\n`WaitAnyResult<T, K>` — discriminated-union result returned by `waitAny()`:\n\n```ts\ntype WaitAnyResult<T extends EventMap, K extends readonly EventKey<T>[]> = {\n [I in keyof K]: K[I] extends EventKey<T> ? { event: K[I]; payload: T[K[I]] } : never;\n}[number];\n```\n\n`Bus<T>` — the runtime bus interface. Individual method docs are in the [Bus Interface](#bus-interface) section.\n\n```ts\ntype Bus<T extends EventMap> = {\n readonly disposed: boolean;\n readonly disposalSignal: AbortSignal;\n [Symbol.dispose](): void;\n dispose(): void;\n emit<K extends EventKey<T>>(event: K, ...args: T[K] extends void ? [] : [payload: T[K]]): number;\n eventNames(): EventKey<T>[];\n events<K extends EventKey<T>>(event: K, options?: { maxBuffer?: number; signal?: AbortSignal }): EventStream<T[K]>;\n listenerCount(event?: EventKey<T>): number;\n on<K extends EventKey<T>>(event: K, listener: Listener<T[K]>, opts?: SubscribeOptions): Unsubscribe;\n onAny(listener: (event: EventKey<T>, payload: unknown) => void, opts?: SubscribeOptions): Unsubscribe;\n once<K extends EventKey<T>>(event: K, listener: Listener<T[K]>, opts?: { signal?: AbortSignal }): Unsubscribe;\n wait<K extends EventKey<T>>(event: K, opts?: { signal?: AbortSignal }): Promise<T[K]>;\n waitAny<const K extends readonly [EventKey<T>, EventKey<T>, ...EventKey<T>[]]>(\n events: K,\n opts?: { signal?: AbortSignal },\n ): Promise<WaitAnyResult<T, K>>;\n wildcardCount(): number;\n};\n```\n\n`BehaviorBus<T>` — extends `Bus<T>` with last-value replay. Full docs in the [`createBehaviorBus()`](#createbehaviorbus) section.\n\n```ts\ntype BehaviorInitial<T extends EventMap> = { [K in EventKey<T>]?: T[K] };\n\ntype BehaviorBus<T extends EventMap> = Bus<T> & {\n current<K extends EventKey<T>>(event: K): T[K] | undefined;\n reset(event?: EventKey<T>): void;\n snapshot(): Partial<T>;\n};\n```\n\n`TestBus<T>` — extends `Bus<T>` with emission recording. Full docs in the [Testing Utilities](#testing-utilities) section.\n\n```ts\ntype TestBus<T extends EventMap> = Bus<T> & {\n allEmitted(): { [K in EventKey<T>]?: T[K][] };\n emitted<K extends EventKey<T>>(event: K): T[K][];\n emittedCount<K extends EventKey<T>>(event: K): number;\n removeAllListeners<K extends EventKey<T>>(event: K): void;\n reset(): void;\n};\n```\n\n## `createBus()`\n\nSignature: `createBus<T extends EventMap>(options?: BusOptions<T>): Bus<T>`\n\nCreates and returns a new `Bus<T>` instance.\n\n| Parameter | Type | Description |\n| --------- | --------------- | --------------------------- |\n| `options` | `BusOptions<T>` | Optional hook configuration |\n\n**Returns:** `Bus<T>`\n\n```ts\nimport { createBus } from '@vielzeug/herald';\n\ntype AppEvents = {\n 'user:login': { userId: string };\n 'user:logout': void;\n};\n\nconst bus = createBus<AppEvents>({\n logger: { debug: myLogger.debug, warn: myLogger.warn }, // provide debug to enable logging; pass {} to silence all\n onError: ({ err, event, payload }) => console.error('[bus] error in', event, err, payload),\n middleware: [\n (event, _payload, next) => {\n // run before listeners; omit next() to block dispatch\n console.debug('[mw]', event);\n next();\n },\n ],\n validatePayload: (event, payload) => {\n // throw to reject the emit before any middleware or listener runs\n if (event === 'count' && typeof payload !== 'number') throw new TypeError('must be number');\n },\n});\n```\n\n## Bus Interface\n\n### `bus.disposed`\n\nType: `readonly boolean`\n\n`true` after `dispose()` has been called. Use this to guard against using a torn-down bus.\n\n```ts\nif (!bus.disposed) {\n bus.emit('user:login', payload);\n}\n```\n\n---\n\n### `bus.disposalSignal`\n\nType: `readonly AbortSignal`\n\nAn `AbortSignal` that fires when the bus is disposed. Use it to tie external lifecycles to the bus lifetime without polling `bus.disposed`.\n\n```ts\n// Automatically unsubscribe from another bus when this bus is torn down\notherBus.on('count', syncState, { signal: bus.disposalSignal });\n\n// Cancel a fetch when the bus is disposed\nfetch('/api/stream', { signal: bus.disposalSignal });\n\n// Combine with a timeout\nconst signal = AbortSignal.any([bus.disposalSignal, AbortSignal.timeout(10_000)]);\n```\n\nThe signal is already aborted when `bus.disposed` is `true`.\n\n---\n\n### `bus.on()`\n\nSignature: `on(event, listener, opts?) => Unsubscribe`\n\nSubscribe to an event. The listener runs synchronously on every emit.\n\n| Parameter | Type | Description |\n| ---------- | ------------------ | --------------------------------------------------- |\n| `event` | `K` | Event key to subscribe to |\n| `listener` | `Listener<T[K]>` | Callback for each emit |\n| `opts` | `SubscribeOptions` | Optional `{ signal?, once? }` for lifecycle control |\n\n**Returns:** `Unsubscribe` — call to remove the listener manually.\n\nIf `opts.signal` is already aborted, `on()` returns a no-op unsubscribe immediately without adding the listener.\n\n::: tip Multiple registrations\nRegistering the same listener function twice creates **two independent subscriptions**. The listener will fire twice per emit and each registration has its own unsubscribe handle. There is no deduplication.\n:::\n\n```ts\nconst unsub = bus.on('user:login', ({ userId }) => {\n console.log('login:', userId);\n});\nunsub();\n\n// Auto-unsubscribe via AbortSignal\nconst controller = new AbortController();\nbus.on('theme:change', applyTheme, { signal: controller.signal });\ncontroller.abort(); // listener removed\n\n// One-shot subscription inline\nbus.on('session:expired', redirectToLogin, { once: true });\n\n// Both options combined\nbus.on('cart:updated', syncState, { once: true, signal: controller.signal });\n```\n\n---\n\n### `bus.once()`\n\nSignature: `once(event, listener, opts?) => Unsubscribe`\n\nConvenience wrapper around `bus.on(event, listener, { once: true, signal: opts?.signal })`. The listener fires exactly once and is automatically removed.\n\n| Parameter | Type | Description |\n| ---------- | -------------------------- | ---------------------------------- |\n| `event` | `K` | Event key |\n| `listener` | `Listener<T[K]>` | One-shot callback |\n| `opts` | `{ signal?: AbortSignal }` | Optional `signal` for early cancel |\n\n**Returns:** `Unsubscribe`\n\n```ts\nbus.once('session:expired', () => redirectToLogin());\n\n// Cancel before it fires\nconst controller = new AbortController();\nbus.once('session:expired', redirectToLogin, { signal: controller.signal });\ncontroller.abort();\n```\n\n---\n\n### `bus.onAny()`\n\nSignature: `onAny(listener, opts?) => Unsubscribe`\n\nSubscribe to **all** events. The listener is called after event-specific listeners on every emit.\n\n| Parameter | Type | Description |\n| ---------- | ------------------------------------------------ | --------------------------------------------------- |\n| `listener` | `(event: EventKey<T>, payload: unknown) => void` | Wildcard callback |\n| `opts` | `SubscribeOptions` | Optional `{ signal?, once? }` for lifecycle control |\n\n**Returns:** `Unsubscribe`\n\n`onAny` listeners count is tracked separately via `wildcardCount()` and are not included in per-event `listenerCount()` results.\n\n```ts\nconst unsub = bus.onAny((event, payload) => {\n analytics.track(event, payload);\n});\n\nunsub(); // remove when done\n\n// With AbortSignal\nconst controller = new AbortController();\nbus.onAny(logger, { signal: controller.signal });\ncontroller.abort();\n\n// Fire exactly once\nbus.onAny(logFirstEvent, { once: true });\n```\n\n---\n\n### `bus.wait()`\n\nSignature: `wait(event, opts?) => Promise<payload>`\n\nReturns a `Promise` that resolves with the payload of the next emit of `event`.\n\n| Parameter | Type | Description |\n| --------- | -------------------------- | -------------------------- |\n| `event` | `K` | Event key to await |\n| `opts` | `{ signal?: AbortSignal }` | Optional `signal` to abort |\n\n**Returns:** `Promise<T[K]>`\n\n**Rejects when:**\n\n- The bus is disposed before the event fires — rejects with `BusDisposedError`\n- The provided `signal` aborts — rejects with `signal.reason`\n\n```ts\nconst login = await bus.wait('user:login');\n\n// With timeout\nconst timedLogin = await bus.wait('user:login', { signal: AbortSignal.timeout(5_000) });\n```\n\n---\n\n### `bus.waitAny()`\n\nSignature: `waitAny(events, opts?) => Promise<{ event, payload }>`\n\nWaits for the first emitted event among a list of event keys.\n\n| Parameter | Type | Description |\n| --------- | -------------------------- | -------------------------- |\n| `events` | `readonly K[]` | Event keys to race |\n| `opts` | `{ signal?: AbortSignal }` | Optional `signal` to abort |\n\n**Returns:** `Promise<WaitAnyResult<T, K>>`\n\n**Throws synchronously:** `HeraldConfigError` if fewer than 2 event keys are provided.\n\n**Rejects when:**\n\n- The bus is disposed before any listed event fires — rejects with `BusDisposedError`\n- The provided `signal` aborts — rejects with `signal.reason`\n\n```ts\nconst winner = await bus.waitAny(['user:login', 'user:logout']);\n\nif (winner.event === 'user:login') {\n console.log(winner.payload.userId);\n}\n```\n\n---\n\n### `bus.events()`\n\nSignature: `events(event, options?) => EventStream<payload>`\n\nReturns an `EventStream<T[K]>` — an `AsyncGenerator` extended with `AsyncDisposable` and chainable `.filter()`, `.map()`, and `.take()` operators — that yields payloads for every future emit of `event`.\n\n- `event: K` — event key to stream\n- `options?: { signal?: AbortSignal; maxBuffer?: number }` — optional early termination and buffering\n\n::: warning Synchronous validation\n`events()` validates `maxBuffer` **synchronously** at call time. If `maxBuffer` is `0` or negative, a `HeraldConfigError` is thrown immediately — not on the first `await`.\n:::\n\n::: tip Eager subscription\n`events()` subscribes **when called**, not when the first `for await` iteration begins. Events emitted before iteration starts are buffered and yielded immediately on the first iteration.\n:::\n\n**Terminates when:**\n\n- The bus is disposed — generator returns cleanly (no exception thrown)\n- The provided `signal` aborts — generator returns cleanly (no exception thrown)\n\n**`AsyncDisposable` support:** Use the `await using` keyword for guaranteed cleanup:\n\n```ts\n// Standard iteration\nfor await (const payload of bus.events('cart:updated')) {\n renderCart(payload.items);\n // loop exits cleanly when bus is disposed or signal aborts\n}\n\n// Stop early with a signal\nconst ctl = new AbortController();\nfor await (const payload of bus.events('data:loaded', { signal: ctl.signal, maxBuffer: 50 })) {\n if (payload.count === 0) ctl.abort();\n}\n\n// await using — cleanup guaranteed even on break or throw\nawait using stream = bus.events('user:login');\nfor await (const { userId } of stream) {\n if (userId === targetId) break; // subscription torn down automatically\n}\n\n// Stop early with a break — subscription torn down automatically via AsyncDisposable\nawait using stream2 = bus.events('count');\nfor await (const n of stream2) {\n if (n > 100) break;\n}\n```\n\n---\n\n### `bus.emit()`\n\nSignature: `emit(event, payload?) => number`\n\nEmit an event, calling all registered listeners synchronously in subscription order. Returns the number of listeners that were invoked.\n\n- For `void` events, no second argument is accepted.\n- For payload events, the second argument is required and type-checked.\n- Returns `0` when: the bus is disposed, a `middleware` function blocked dispatch, or `validatePayload` threw an error (with `onError` configured).\n- **Listener throws:** every registered listener (specific and wildcard) for the emission still runs, regardless of whether an earlier one threw. Without `onError` configured, the first thrown error is rethrown once every listener has been called — it never short-circuits the rest of the broadcast. With `onError` configured, every error is forwarded per-listener and `emit()` never throws for a listener failure.\n\n```ts\nconst count = bus.emit('user:login', { userId: '42', email: 'alice@example.com' });\nbus.emit('user:logout'); // void — no argument\nconsole.log(count); // number of listeners invoked\n```\n\n---\n\n### `bus.dispose()`\n\nSignature: `dispose() => void`\n\nPermanently tears down the bus:\n\n- All listeners are removed.\n- All pending `wait()` promises are rejected with `BusDisposedError`.\n- Subsequent `emit()` and `on()` calls become no-ops.\n\nIdempotent — safe to call multiple times.\n\n```ts\nbus.dispose();\nbus.disposed; // true\n```\n\n---\n\n### `bus[Symbol.dispose]()`\n\nSignature: `[Symbol.dispose]() => void`\n\nAlias for `dispose()`. Enables the `using` keyword (TypeScript 5.2+):\n\n```ts\n{\n using bus = createBus<AppEvents>();\n // ...\n} // dispose() called automatically\n```\n\n---\n\n### `bus.listenerCount()`\n\nSignature: `listenerCount(event?) => number`\n\nReturns the number of active listeners.\n\n| Parameter | Type | Description |\n| --------- | ------------------------ | ---------------------------------------- |\n| `event` | `EventKey<T>` (optional) | Specific event key; omit for total count |\n\n**Returns:** `number`\n\nWhen called with an event key, the count includes **both** event-specific listeners **and** any `onAny` wildcard listeners — since wildcards fire on every emission of every event. When called without an argument, wildcards are counted once in the total.\n\n```ts\nbus.on('user:login', handler1);\nbus.on('user:login', handler2);\nbus.on('user:logout', handler3);\nbus.onAny(wildcardHandler);\n\nbus.listenerCount('user:login'); // 3 — 2 specific + 1 wildcard\nbus.listenerCount('user:logout'); // 2 — 1 specific + 1 wildcard\nbus.listenerCount(); // 4 — 3 specific + 1 wildcard (wildcard counted once)\n\nbus.dispose();\nbus.listenerCount(); // 0\n```\n\n---\n\n### `bus.eventNames()`\n\nSignature: `eventNames() => EventKey<T>[]`\n\nReturns a snapshot of event keys that currently have at least one active listener.\n\n```ts\nbus.on('user:login', handler1);\nbus.on('user:logout', handler2);\n\nbus.eventNames(); // ['user:login', 'user:logout']\n```\n\n---\n\n### `bus.wildcardCount()`\n\nSignature: `wildcardCount() => number`\n\nReturns the number of active `onAny` wildcard listeners. Wildcards are counted separately from event-specific listeners — this is the count that `listenerCount(event)` adds on top of the specific count for each event.\n\n```ts\nbus.onAny(logAll);\nbus.onAny(trackAll);\n\nbus.wildcardCount(); // 2\nbus.listenerCount('user:login'); // 0 specific + 2 wildcard = 2\n```\n\n## `BusOptions` — middleware\n\n`BusOptions.middleware` accepts an array of `Middleware<T>` functions that run on every `emit()`, after `validatePayload` and before listeners. Each middleware receives `(event, payload, next)` — call `next()` to continue the chain; omit it to block dispatch entirely.\n\n```ts\nimport { createBus } from '@vielzeug/herald';\n\nconst bus = createBus<AppEvents>({\n middleware: [\n // Logging middleware — logs every event then continues\n (event, payload, next) => {\n console.debug(`[mw] ${event}`, payload);\n next();\n },\n // Rate-limiting middleware — blocks dispatch under certain conditions\n (event, _payload, next) => {\n if (!rateLimiter.allow(event)) return; // omit next() to block listeners\n next();\n },\n ],\n});\n\nbus.on('user:login', handler);\nbus.emit('user:login', { userId: '1', email: 'a@b.com' });\n// → [mw] user:login { userId: '1', ... }\n// → handler fires (if rate limiter allows)\n```\n\n`emit()` returns `0` when any middleware omits `next()`.\n\n## `BusOptions` — validatePayload\n\n`BusOptions.validatePayload` is called on every `emit()` **before** middleware and listeners. Throw to reject the payload entirely — no middleware or listener will run.\n\n| Throw with `onError` | Error forwarded to `onError`; `emit()` returns `0`. |\n| Throw without `onError` | Error propagates to the `emit()` caller. |\n\n```ts\nconst bus = createBus<AppEvents>({\n validatePayload: (event, payload) => {\n if (event === 'count' && typeof payload !== 'number') {\n throw new TypeError(`\"count\" payload must be a number, got ${typeof payload}`);\n }\n },\n onError: ({ err, event }) => logger.warn('rejected emit', event, err),\n});\n\nbus.on('count', vi.fn());\nbus.emit('count', 'oops'); // → onError called; listener never fires; returns 0\nbus.emit('count', 42); // → listener fires; returns 1\n```\n\n## `createBehaviorBus()`\n\nSignature: `createBehaviorBus<T extends EventMap>(initial?, options?) => BehaviorBus<T>`\n\nCreates a bus that replays the last known value to new subscribers. Useful for state-like events where late subscribers should receive the current value immediately.\n\n| Parameter | Type | Description |\n| --------- | -------------------- | ---------------------------------------------- |\n| `initial` | `BehaviorInitial<T>` | Optional map of event names to starting values |\n| `options` | `BusOptions<T>` | Standard bus options (hooks, logger, etc.) |\n\n**Returns:** `BehaviorBus<T>`\n\n**Replay rules:**\n\n- `on()` and `once()` — replay the current value synchronously to new subscribers.\n- `events()`, `wait()`, `waitAny()` — no replay; behave like a regular bus.\n- The returned `BehaviorBus<T>` adds `current(event)`, `reset()`, and `snapshot()` methods.\n- The replay buffer is only updated when dispatch actually runs — payloads rejected by `validatePayload` or blocked by middleware that omits `next()` are never buffered.\n- **`once()` with a buffered value:** if a current value exists, the listener fires immediately (synchronously) and is never registered for future emits. Use `on()` if you need to receive the _next_ emit rather than the current state.\n\n```ts\nimport { createBehaviorBus } from '@vielzeug/herald';\n\ntype UIEvents = { theme: 'light' | 'dark'; zoom: number };\n\nconst bus = createBehaviorBus<UIEvents>({ theme: 'light', zoom: 1 });\n\n// New subscribers receive the current value immediately\nbus.on('theme', applyTheme); // called with 'light' right now\nbus.emit('theme', 'dark');\n\nbus.on('theme', applyTheme); // called with 'dark' right now\n\n// Read current value without subscribing\nbus.current('theme'); // 'dark'\nbus.current('zoom'); // 1 (from initial)\n```\n\n### `behaviorBus.current()`\n\nSignature: `current(event) => T[K] | undefined`\n\nReturns the last emitted value for the given event, or `undefined` if no value has been emitted and no initial value was provided.\n\n```ts\nbus.current('theme'); // 'dark'\nbus.current('unknown' as never); // undefined\n```\n\n---\n\n### `behaviorBus.reset()`\n\nSignature: `reset(event?) => void`\n\nClears the replay buffer for a specific event, or for all events when called without arguments. After reset, new subscribers will not receive a replayed value until the next `emit()`.\n\nDoes not affect active subscriptions or the disposed state of the bus.\n\n```ts\nbus.emit('theme', 'dark');\nbus.current('theme'); // 'dark'\n\nbus.reset('theme'); // clear only 'theme'\nbus.current('theme'); // undefined\n\nbus.reset(); // clear all buffers\n```\n\n---\n\n### `behaviorBus.snapshot()`\n\nSignature: `snapshot() => Partial<T>`\n\nReturns a plain object containing the most recently emitted value for every currently buffered event. Events with no value in the buffer are omitted from the result.\n\nUseful for serializing the current state of all channels at once, for debugging, or for hydrating a new bus from a snapshot.\n\n::: tip Dangerous event names are omitted\nAn event named `__proto__`, `constructor`, or `prototype` is silently excluded from the returned object — bracket-assigning one of these keys onto a plain object literal would hijack its prototype rather than set an own property. The value itself is still tracked internally and reachable via `current(event)`; only the object-literal snapshot omits it.\n:::\n\n```ts\ntype UIEvents = { theme: 'light' | 'dark'; zoom: number; sidebar: boolean };\n\nconst bus = createBehaviorBus<UIEvents>({ theme: 'light', zoom: 1 });\n\nbus.snapshot();\n// → { theme: 'light', zoom: 1 } (sidebar not buffered — omitted)\n\nbus.emit('theme', 'dark');\nbus.snapshot();\n// → { theme: 'dark', zoom: 1 }\n\nbus.reset('theme');\nbus.snapshot();\n// → { zoom: 1 }\n```\n\n## `pipeEvents()`\n\nSignature: `pipeEvents<S, T>(source, target, entries, opts?) => Unsubscribe`\n\nForwards a selected subset of events from a source bus to a target bus. Source and target may have different event map types — only the listed keys must be compatible.\n\n| Parameter | Type | Description |\n| --------- | -------------------------------------------------- | -------------------------------------------------------------------------------- |\n| `source` | `Bus<S>` | The bus to listen on |\n| `target` | `Bus<T>` | The bus to forward events to |\n| `entries` | `readonly [PipeEntry<S, T>, ...PipeEntry<S, T>[]]` | One or more string keys or `{ from, to }` renames — throws `HeraldConfigError` if empty |\n| `opts` | `{ signal?: AbortSignal }` (optional) | Optional signal to stop forwarding early |\n\n**Returns:** `Unsubscribe` — call to stop forwarding manually.\n\nForwarding stops automatically when the **target bus is disposed**. Source disposal is handled via the source bus's own subscription lifecycle.\n\n```ts\nimport { createBus, pipeEvents } from '@vielzeug/herald';\n\nconst appBus = createBus<AppEvents>();\nconst auditBus = createBus<AppEvents>();\n\n// Forward only auth events — tears down automatically when auditBus disposes\nconst unpipe = pipeEvents(appBus, auditBus, ['user:login', 'user:logout']);\n\n// Stop forwarding manually\nunpipe();\n\n// Scope to a signal\nconst controller = new AbortController();\npipeEvents(appBus, auditBus, ['user:login'], { signal: controller.signal });\ncontroller.abort(); // forwarding stops\n\n// Rename events during forwarding\ntype AuthEvents = { 'auth:login': { userId: string }; 'auth:logout': void };\ntype AppEventsTarget = { 'user:authenticated': { userId: string }; 'user:signed-out': void };\n\nconst authBus = createBus<AuthEvents>();\nconst targetBus = createBus<AppEventsTarget>();\n\npipeEvents(authBus, targetBus, [\n { from: 'auth:login', to: 'user:authenticated' },\n { from: 'auth:logout', to: 'user:signed-out' },\n]);\n```\n\n## Testing Utilities\n\nImport from `@vielzeug/herald/testing`.\n\n### `createTestBus()`\n\nSignature: `createTestBus<T extends EventMap>(options?: BusOptions<T>): TestBus<T>`\n\nCreates a `TestBus<T>` (a full `Bus<T>` plus recording helpers).\n\nBehavior:\n\n- Every `emit()` is recorded per event key\n- `emitted(event)` returns a snapshot array\n- `reset()` clears records without removing listeners\n- `dispose()` clears listeners and records\n- Accepts full `BusOptions<T>` including `onError`\n\n| Parameter | Type | Description |\n| --------- | --------------- | ----------------------------------------------- |\n| `options` | `BusOptions<T>` | Optional hooks; composed with internal recorder |\n\n**Returns:** `TestBus<T>`\n\n```ts\nimport { createTestBus } from '@vielzeug/herald/testing';\n\ntype AppEvents = {\n 'user:login': { userId: string };\n};\n\nconst bus = createTestBus<AppEvents>();\n\nbus.emit('user:login', { userId: '1' });\nconsole.log(bus.emitted('user:login')); // [{ userId: '1' }]\n```\n\n---\n\n### `testBus.emitted()`\n\nSignature: `emitted(event) => payload[]`\n\nReturns a **snapshot** of all payloads emitted for the given event key, in emission order. Each call returns a new array — mutations do not affect the internal records.\n\n```ts\nbus.emit('user:login', { userId: '1', email: 'a@example.com' });\nbus.emit('user:login', { userId: '2', email: 'b@example.com' });\n\nbus.emitted('user:login');\n// => [{ userId: '1', email: 'a@example.com' }, { userId: '2', email: 'b@example.com' }]\n```\n\n---\n\n### `testBus.emittedCount()`\n\nSignature: `emittedCount(event) => number`\n\nReturns the number of times the given event has been emitted. Shorthand for `emitted(event).length`.\n\n```ts\nbus.emit('user:login', { userId: '1' });\nbus.emit('user:login', { userId: '2' });\n\nbus.emittedCount('user:login'); // 2\nbus.emittedCount('user:logout'); // 0\n```\n\n---\n\n### `testBus.reset()`\n\nSignature: `reset() => void`\n\nClears all recorded payloads without disposing the bus or removing any listeners.\n\n```ts\nbus.reset();\nbus.emitted('user:login'); // => []\n```\n\n---\n\n### `testBus.allEmitted()`\n\nSignature: `allEmitted() => { [K in EventKey<T>]?: T[K][] }`\n\nReturns a snapshot object containing all recorded payloads for every event that has been emitted at least once. Keys absent from the result have never been emitted. Each call returns a new object — mutations do not affect internal records.\n\nUseful for asserting that **no other events** were emitted beyond the ones being tested.\n\n::: tip Dangerous event names are omitted\nAn event named `__proto__`, `constructor`, or `prototype` is silently excluded from the returned object — bracket-assigning one of these keys onto a plain object literal would hijack its prototype rather than set an own property. The recorded payloads are still tracked internally and reachable via `emitted(event)`; only the object-literal snapshot omits them.\n:::\n\n```ts\nbus.emit('user:login', { userId: '1' });\nbus.emit('theme:change', 'dark');\n\nbus.allEmitted();\n// => { 'user:login': [{ userId: '1' }], 'theme:change': ['dark'] }\n```\n\n---\n\n### `testBus.dispose()`\n\nSignature: `dispose() => void`\n\nClears recorded payloads and then calls the underlying `bus.dispose()`, removing all listeners and rejecting pending waits. Idempotent.\n\n---\n\n### `testBus.removeAllListeners()`\n\nSignature: `removeAllListeners(event) => void`\n\nUnsubscribes all listeners registered via `on()` for the given event key. Emission records are preserved — call `reset()` separately to clear them.\n\n```ts\nbus.on('user:login', handlerA);\nbus.on('user:login', handlerB);\nbus.emit('user:login', { userId: '1' });\n\nbus.removeAllListeners('user:login');\nbus.emit('user:login', { userId: '2' }); // neither handler fires\n\nbus.emitted('user:login'); // => [{ userId: '1' }, { userId: '2' }] — records intact\n```\n\n::: tip Test-only utility\n`removeAllListeners` is available on `TestBus` only — it is not part of the standard `Bus<T>` interface. Use unsubscribe handles or `{ signal }` options for lifecycle-managed cleanup in production code.\n:::\n\n## `combineSignals()`\n\nSignature: `combineSignals(first: AbortSignal, ...rest: AbortSignal[]) => AbortSignal`\n\nReturns a signal that aborts as soon as any of the provided signals abort. With a single argument, returns it directly (no allocation). Registers and cleans up its own event listeners — no leaks when no signal fires.\n\n| Parameter | Type | Description |\n| --------- | --------------- | ----------------------------------------------------- |\n| `first` | `AbortSignal` | First signal; returned directly if no others provided |\n| `...rest` | `AbortSignal[]` | Additional signals to race |\n\n**Returns:** `AbortSignal` — aborts when any input aborts.\n\n```ts\nimport { combineSignals, createBus } from '@vielzeug/herald';\n\nconst bus = createBus<AppEvents>();\nconst timeoutSignal = AbortSignal.timeout(5_000);\n\n// Unsubscribe when the bus disposes OR after 5 seconds\nconst signal = combineSignals(bus.disposalSignal, timeoutSignal);\nbus.on('user:login', handler, { signal });\n\n// Three signals — no nesting required\nconst signal3 = combineSignals(userSignal, timeoutSignal, bus.disposalSignal);\n```\n\n::: tip Why `combineSignals` over `AbortSignal.any()`?\n`AbortSignal.any([a, b])` is a platform equivalent, but it retains a strong reference to both signals until one fires. If neither signal ever fires, the internal `'abort'` listeners are never removed — a potential memory leak in long-lived buses.\n\n`combineSignals(a, b)` uses `once: true` listeners that clean up immediately as soon as either signal fires, making it the safer choice for `disposalSignal`-scoped subscriptions.\n:::\n\n## Errors\n\n### `BusDisposedError`\n\n```ts\nclass BusDisposedError extends Error {\n override name = 'BusDisposedError';\n // message: 'Bus is disposed' or 'Bus \"<name>\" is disposed' when name option is set\n}\n```\n\nThrown as the rejection reason when a pending `wait()` or `waitAny()` call is interrupted by `bus.dispose()`. Also used as the abort reason on `bus.disposalSignal`.\n\nWhen the bus was created with a `name` option, the message includes the name: `Bus \"myBus\" is disposed`.\n\nUse `instanceof` to distinguish from signal aborts and other rejections:\n\n```ts\nimport { BusDisposedError } from '@vielzeug/herald';\n\ntry {\n await bus.wait('user:login');\n} catch (err) {\n if (err instanceof BusDisposedError) {\n // bus was torn down before the event fired\n } else {\n throw err; // signal abort or unexpected error\n }\n}\n```\n\n---\n\n### `HeraldConfigError`\n\n```ts\nclass HeraldConfigError extends HeraldError {}\n```\n\nThrown synchronously for invalid arguments or configuration, before any async work begins:\n\n- `waitAny(events)` — fewer than 2 event keys provided\n- `events(event, { maxBuffer })` — `maxBuffer` is `0` or negative\n- `pipeEvents(source, target, entries)` — `entries` is empty\n\n```ts\nimport { HeraldConfigError } from '@vielzeug/herald';\n\ntry {\n bus.waitAny(['user:login']); // only one key — needs at least 2\n} catch (err) {\n if (err instanceof HeraldConfigError) {\n console.error('Invalid herald call:', err.message);\n }\n}\n```\n\n---\n\n### `HeraldError`\n\n```ts\nclass HeraldError extends Error {\n static is(err: unknown): err is HeraldError;\n}\n```\n\nBase class for every error herald throws (`BusDisposedError`, `HeraldConfigError`). Use `HeraldError.is()` to catch any herald-originated error without enumerating each subclass:\n\n```ts\nimport { HeraldError } from '@vielzeug/herald';\n\ntry {\n bus.waitAny(['user:login']);\n} catch (err) {\n if (HeraldError.is(err)) {\n // BusDisposedError or HeraldConfigError\n console.error('Herald error:', err.message);\n } else {\n throw err; // not from herald\n }\n}\n```\n\n## Devtools\n\nImport from `@vielzeug/herald/devtools` — a dedicated sub-path so `console.debug` references are tree-shaken from production bundles when this sub-path is not imported.\n\n### `debugBus()`\n\nSignature: `debugBus<T extends EventMap>(options?) => Bus<T>`\n\nEquivalent to `createBus({ logger: { debug: console.debug } })`. Pass `logger.warn` to also redirect or silence warnings; every other `BusOptions<T>` field (`maxListeners`, `middleware`, `name`, `onError`, `validatePayload`) passes through unchanged.\n\n```ts\nimport { debugBus } from '@vielzeug/herald/devtools';\n\nconst bus = debugBus<AppEvents>();\n// or redirect warnings:\nconst auditedBus = debugBus<AppEvents>({ logger: { warn: myLogger.warn } });\n```\n\n### `debugBehaviorBus()`\n\nSignature: `debugBehaviorBus<T extends EventMap>(initial?, options?) => BehaviorBus<T>`\n\nEquivalent to `createBehaviorBus(initial, { logger: { debug: console.debug } })`. Same `logger.warn` override and `BehaviorBusOptions<T>` passthrough as `debugBus()`.\n\n```ts\nimport { debugBehaviorBus } from '@vielzeug/herald/devtools';\n\nconst bus = debugBehaviorBus<UIState>({ theme: 'light' });\n// or redirect warnings:\nconst auditedBus = debugBehaviorBus<UIState>({ theme: 'light' }, { logger: { warn: myLogger.warn } });\n```\n",
6
- "usage": "---\ntitle: Herald — Usage Guide\ndescription: Event maps, subscriptions, wait(), async event streams, hooks, cleanup, and testing for @vielzeug/herald.\n---\n\n[[toc]]\n\n::: tip New to Herald?\nStart with the [Overview](./index.md) for a quick introduction and installation, then come back here for in-depth usage patterns.\n:::\n\n## Basic Usage\n\nAn event map is a plain TypeScript type where each key is an event name and each value is the payload type. Use `void` for signal events that carry no data.\n\n```ts\ntype AppEvents = {\n // events with payloads\n 'user:login': { userId: string; email: string };\n 'user:logout': void; // signal — no payload\n 'cart:updated': { items: CartItem[]; total: number };\n 'theme:change': 'light' | 'dark';\n 'data:loaded': { count: number; items: unknown[] };\n};\n\nconst bus = createBus<AppEvents>();\n```\n\n## Subscribing\n\n### `on()` — Persistent subscription\n\n`on()` registers a listener for every future emit of an event. It returns an `Unsubscribe` function.\n\n```ts\nconst unsub = bus.on('user:login', ({ userId, email }) => {\n console.log('logged in:', userId, email); // fully typed\n});\n\n// Remove the listener\nunsub();\n```\n\n::: tip Multiple registrations\nRegistering the **same listener function** twice creates two independent subscriptions — the listener fires twice per emit and each registration has its own independent unsubscribe handle. There is no deduplication.\n:::\n\n### `once()` — One-shot listener\n\n`once()` registers a listener that fires exactly once, then removes itself automatically.\n\n```ts\nbus.once('user:logout', () => {\n redirectToLogin();\n});\n```\n\n### `eventNames()` — Introspect active subscriptions\n\nGet a snapshot of event keys that currently have listeners.\n\n```ts\nbus.on('user:login', handler);\nbus.on('user:logout', handler);\n\nconsole.log(bus.eventNames()); // ['user:login', 'user:logout']\n```\n\n### AbortSignal and `SubscribeOptions`\n\nPass a `SubscribeOptions` object as the third argument to `on()`. Use `signal` to auto-unsubscribe when an `AbortSignal` fires, and `once` to auto-remove after the first invocation.\n\n```ts\nconst controller = new AbortController();\n\n// Auto-remove when signal aborts\nbus.on('user:login', handler, { signal: controller.signal });\n\n// Later — removes the listener automatically\ncontroller.abort();\n\n// One-shot subscription inline (equivalent to bus.once())\nbus.on('user:login', handler, { once: true });\n\n// Both combined — fires at most once, and only before signal aborts\nbus.on('cart:updated', handler, { once: true, signal: controller.signal });\n```\n\n## Emitting Events\n\n`emit()` calls all registered listeners synchronously and returns the number of listeners that were invoked.\n\n```ts\nconst count = bus.emit('user:login', { userId: '42', email: 'alice@example.com' });\nbus.emit('user:logout'); // void event — no second argument\nconsole.log(count); // number of listeners fired\n```\n\n`emit()` returns `0` when the bus is disposed, when middleware blocks dispatch, or when `validatePayload` rejects the payload with `onError` configured.\n\nEvery registered listener still runs even if an earlier one throws. Without `onError` configured, the first thrown error is rethrown once every listener has been called — it never short-circuits the rest of the broadcast. With `onError`, every error is captured per-listener and `emit()` never throws for a listener failure.\n\n### Middleware\n\nPass `middleware` to `createBus()` to intercept every `emit()` before listeners run. Middleware functions receive `(event, payload, next)` — call `next()` to continue, or omit it to block dispatch:\n\n```ts\nconst bus = createBus<AppEvents>({\n middleware: [\n (event, payload, next) => {\n console.debug('[dispatch]', event, payload);\n next();\n },\n // Rate limit: block dispatch if quota is exceeded\n (event, _payload, next) => {\n if (rateLimiter.allow(event)) next();\n },\n ],\n});\n```\n\nMultiple middleware run in array order. If any omits `next()`, subsequent middleware and all listeners are skipped and `emit()` returns `0`.\n\n### `validatePayload`\n\nUse `validatePayload` for schema-level guards that run **before** middleware. Throw to reject the emit:\n\n```ts\nconst bus = createBus<AppEvents>({\n validatePayload: (event, payload) => {\n if (event === 'count' && typeof payload !== 'number') {\n throw new TypeError('count must be a number');\n }\n },\n onError: ({ err, event }) => logger.warn('rejected', event, err),\n});\n\nbus.emit('count', 'oops'); // → onError called, listeners skipped, returns 0\nbus.emit('count', 42); // → listeners run, returns listener count\n```\n\nWithout `onError`, a `validatePayload` throw propagates directly to the `emit()` caller.\n\n## Awaiting Events\n\n`wait()` returns a `Promise` that resolves with the payload of the next emit. This is useful for one-off async coordination patterns.\n\n```ts\n// Waits for the next 'user:login' emit and resolves\nconst { userId } = await bus.wait('user:login');\n```\n\n`wait()` rejects if:\n\n- The bus is disposed before the event fires\n- A provided `AbortSignal` aborts\n\n```ts\n// Reject if login hasn't happened within 5 seconds\nconst { userId } = await bus.wait('user:login', { signal: AbortSignal.timeout(5_000) });\n```\n\n### `waitAny()`\n\n`waitAny()` resolves with the first event that fires from a list and returns both the winning event key and payload.\n\n```ts\nconst result = await bus.waitAny(['user:login', 'user:logout']);\n\nif (result.event === 'user:login') {\n console.log(result.payload.userId);\n}\n```\n\nLike `wait()`, it rejects when the bus is disposed or when the provided signal aborts:\n\n```ts\nconst result = await bus.waitAny(['user:login', 'user:logout'], { signal: AbortSignal.timeout(10_000) });\n```\n\n## Async Iteration\n\n`events()` returns an `AsyncGenerator` that yields every future emit of an event. It terminates when the bus is disposed or the provided signal aborts.\n\n`events()` subscribes **eagerly** — the listener is registered when `events()` is called, so events emitted before the first `await` are buffered and will be yielded on the next iteration.\n\n```ts\nfor await (const { items, total } of bus.events('cart:updated')) {\n renderCart(items, total);\n}\n```\n\nUse the `options` object to stop iterating early or cap the internal buffer:\n\n```ts\nconst controller = new AbortController();\n\nfor await (const payload of bus.events('data:loaded', { signal: controller.signal, maxBuffer: 100 })) {\n process(payload);\n if (isDone(payload)) controller.abort(); // exits the loop cleanly\n}\n// loop ends here — no exception thrown on abort or dispose\n```\n\nThe generator is `AsyncDisposable` — use `await using` for guaranteed cleanup even on early `break`:\n\n```ts\nawait using stream = bus.events('user:login');\nfor await (const { userId } of stream) {\n if (userId === targetId) break; // stream subscription is torn down automatically\n}\n```\n\n## Error Handling\n\nEvery registered listener for an emission runs regardless of whether an earlier one throws. By default (no `onError` configured), the first thrown error propagates to the `emit()` caller once every listener has been called.\n\nConfigure `onError` to capture errors instead of rethrowing. `emit()` never throws for a listener failure when `onError` is set.\n\n```ts\nconst bus = createBus<AppEvents>({\n onError: ({ err, event, payload, timestamp }) => {\n // Structured context — event, payload, and timestamp at the time emit() was called\n logger.error(`[herald] Error in \"${event}\" listener`, err, { payload, timestamp });\n },\n});\n```\n\n`onError` receives an `EmissionErrorContext<T>` object:\n\n- `err` — the thrown value\n- `event` — the event key that was being emitted\n- `payload` — the payload passed to the failing listener (typed as `unknown`)\n- `timestamp` — `Date.now()` captured at the moment `emit()` was called\n\n## Dispose & Cleanup\n\n`dispose()` permanently tears down the bus: all listeners are removed and all pending `wait()` promises are rejected with `BusDisposedError`.\n\n```ts\nbus.dispose();\nbus.disposed; // true\n\n// Calling dispose() again is safe — idempotent\nbus.dispose(); // no-op\n```\n\nUse `instanceof BusDisposedError` to distinguish bus teardown from other rejections:\n\n```ts\nimport { BusDisposedError } from '@vielzeug/herald';\n\ntry {\n const payload = await bus.wait('user:login');\n} catch (err) {\n if (err instanceof BusDisposedError) {\n // bus was torn down before the event fired\n } else {\n throw err; // signal abort reason or unexpected error\n }\n}\n```\n\n### `disposalSignal`\n\nEvery bus exposes a `disposalSignal: AbortSignal` property. The signal fires when `dispose()` is called, giving you a handle to tie external lifecycles to the bus lifetime without polling `bus.disposed`.\n\n```ts\nconst bus = createBus<AppEvents>();\n\n// Pass disposalSignal to another bus subscription — auto-unsubscribes on teardown\notherBus.on('count', syncState, { signal: bus.disposalSignal });\n\n// Use with any AbortSignal-aware API\nfetch('/api/stream', { signal: bus.disposalSignal });\n\n// Combine with other signals\nconst combined = AbortSignal.any([bus.disposalSignal, AbortSignal.timeout(10_000)]);\nbus.events('data:loaded', { signal: combined });\n```\n\nThe signal is already aborted when `bus.disposed` is `true`.\n\n## Wildcard Listeners\n\n`onAny()` subscribes to **all events** on the bus. The listener receives the event name and payload on every emit, after event-specific listeners have run. Useful for cross-cutting concerns like logging, analytics, or dev-mode tracing.\n\n```ts\nconst unsub = bus.onAny((event, payload) => {\n console.debug(`[bus] ${event}`, payload);\n});\n\nunsub(); // remove the wildcard listener when done\n```\n\nLike `on()`, `onAny()` accepts an optional `SubscribeOptions` object:\n\n```ts\nconst controller = new AbortController();\nbus.onAny(logger, { signal: controller.signal });\ncontroller.abort(); // removes the wildcard listener\n\n// Once-only wildcard\nbus.onAny(logFirstEvent, { once: true });\n```\n\nUse `wildcardCount()` to inspect the current number of active wildcard listeners.\n\n::: tip `onAny` for bus-wide observability\n`onAny` is a runtime listener — it can be added and removed dynamically, and accepts `{ signal, once }` options just like `on()`. Prefer it over global tracing hooks for cross-cutting concerns.\n:::\n\n## Event Piping\n\nUse `pipeEvents()` to forward events from one bus to another. It supports same-name forwarding and event renaming.\n\n```ts\nimport { createBus, pipeEvents } from '@vielzeug/herald';\n\ntype AppEvents = {\n 'user:login': { userId: string; email: string };\n 'user:logout': void;\n 'cart:updated': { items: CartItem[]; total: number };\n};\n\nconst appBus = createBus<AppEvents>();\nconst auditBus = createBus<AppEvents>();\n\n// Forward only auth events to the audit bus\nconst unpipe = pipeEvents(appBus, auditBus, ['user:login', 'user:logout']);\n```\n\n`pipeEvents` returns an `Unsubscribe` function to stop forwarding manually:\n\n```ts\nunpipe(); // stop forwarding\n```\n\nForwarding stops automatically when the **target** bus is disposed — no manual cleanup needed. Source disposal is handled by the source bus's own `on()` lifecycle.\n\nYou can scope piping to a signal:\n\n```ts\nconst controller = new AbortController();\npipeEvents(appBus, auditBus, ['user:login'], { signal: controller.signal });\n\n// Stop forwarding after 30 seconds\nsetTimeout(() => controller.abort(), 30_000);\n```\n\n### Event renaming\n\nPass a `{ from, to }` object to forward an event under a different name on the target bus. This enables cross-domain event translation without manually wiring `on()` + `emit()`.\n\n```ts\ntype AuthEvents = { 'auth:login': { userId: string }; 'auth:logout': void };\ntype AppEvents = { 'user:authenticated': { userId: string }; 'user:signed-out': void };\n\nconst authBus = createBus<AuthEvents>();\nconst appBus = createBus<AppEvents>();\n\npipeEvents(authBus, appBus, [\n { from: 'auth:login', to: 'user:authenticated' },\n { from: 'auth:logout', to: 'user:signed-out' },\n]);\n```\n\nMix string keys and `{ from, to }` objects freely in the same array:\n\n```ts\npipeEvents(sourceBus, targetBus, [\n 'config:updated', // same name\n { from: 'auth:login', to: 'user:authenticated' }, // renamed\n]);\n```\n\n## Behavior Bus\n\n`createBehaviorBus()` creates a bus that remembers and replays the last emitted value to new subscribers. This is useful for state-like events where late subscribers should receive the current value immediately — similar to a BehaviorSubject in RxJS.\n\n```ts\nimport { createBehaviorBus } from '@vielzeug/herald';\n\ntype UIState = { theme: 'light' | 'dark'; zoom: number };\n\n// Provide initial values — these are replayed to first subscribers\nconst bus = createBehaviorBus<UIState>({ theme: 'light', zoom: 1 });\n\nbus.on('theme', applyTheme); // called with 'light' immediately\nbus.on('zoom', setZoom); // called with 1 immediately\n\nbus.emit('theme', 'dark');\n\nbus.on('theme', applyTheme); // called with 'dark' immediately — gets current value\n```\n\n### `current()`\n\nRead the current value for any event without subscribing:\n\n```ts\nbus.current('theme'); // 'dark'\nbus.current('zoom'); // 1\n```\n\n### `snapshot()`\n\nRead all currently buffered values at once as a plain object:\n\n```ts\nbus.snapshot();\n// → { theme: 'dark', zoom: 1 } (only buffered events are included)\n```\n\nThis is useful for serializing state, hydrating a new bus, or debugging all channels simultaneously.\n\n### Replay rules\n\n| Method | Replays current value? |\n| -------------- | ---------------------------------------------------------- |\n| `on()` | <ore-icon name=\"check\" size=\"16\"></ore-icon> Yes |\n| `once()` | <ore-icon name=\"check\" size=\"16\"></ore-icon> Yes (then done) |\n| `on({ once })` | <ore-icon name=\"check\" size=\"16\"></ore-icon> Yes (then done) |\n| `events()` | <ore-icon name=\"x\" size=\"16\"></ore-icon> No |\n| `wait()` | <ore-icon name=\"x\" size=\"16\"></ore-icon> No |\n\n::: warning `once()` on a BehaviorBus fires immediately\nIf the bus has a buffered value for the event, `once()` (and `on(event, fn, { once: true })`) fires the listener **synchronously** with the current value and is immediately done — the listener is never registered for future emits. If you need to react to the _next_ new emit rather than the current state, use `on()` and unsubscribe manually after the first call.\n:::\n\n## Debug Mode\n\nImport `debugBus` from the dedicated sub-path to create a bus with debug logging pre-enabled. The sub-path is tree-shaken from production bundles when not imported.\n\n```ts\nimport { debugBus } from '@vielzeug/herald/devtools';\n\nconst bus = debugBus<AppEvents>();\n\nbus.on('user:login', handler);\n// → [herald:on] on(\"user:login\")\n\nbus.emit('user:login', { email: 'alice@example.com', userId: '42' });\n// → [herald:emit] emit(\"user:login\") — 1 listener(s)\n\nbus.dispose();\n// → [herald:lifecycle] dispose()\n```\n\nAlternatively, wire logging manually by passing `logger.debug` directly to `createBus()`:\n\n```ts\nconst bus = createBus<AppEvents>({ logger: { debug: console.debug } }); // equivalent\n```\n\n`debugBehaviorBus` is the same wrapper for `createBehaviorBus()`:\n\n```ts\nimport { debugBehaviorBus } from '@vielzeug/herald/devtools';\n\nconst bus = debugBehaviorBus<UIState>({ theme: 'light' });\nbus.on('theme', applyTheme); // replays 'light' immediately, logs the subscription\n```\n\nDebug logging has no effect on behavior and should not be enabled in production.\n\n### Custom logger\n\nProvide a `logger` object to route or silence debug and warn output:\n\n```ts\nconst bus = createBus<AppEvents>({\n logger: {\n debug: (msg) => myLogger.trace(msg), // enable + redirect debug output\n warn: (msg) => myLogger.warn(msg), // redirect warn output\n },\n});\n\n// Omit logger.debug to disable debug logging, omit logger.warn to silence warnings\nconst warnOnlyBus = createBus<AppEvents>({ logger: { warn: console.warn } });\n\n// Pass {} to suppress all bus logging entirely\nconst silentBus = createBus<AppEvents>({ logger: {} });\n```\n\n### Naming a bus with `name`\n\nPass `name` to identify a bus in log messages and error output. Useful when multiple buses run concurrently and you need to distinguish their activity:\n\n```ts\nconst authBus = createBus<AuthEvents>({ name: 'auth', logger: { debug: console.debug } });\nconst cartBus = createBus<CartEvents>({ name: 'cart', logger: { debug: console.debug } });\n\nauthBus.emit('user:login', { userId: '1' });\n// → [herald:emit] emit(\"user:login\") — 1 listener(s) (auth)\n\ncartBus.dispose();\n// → [herald:lifecycle] dispose() (cart)\n```\n\nWhen a named bus is disposed, `BusDisposedError` includes the name in its message:\n\n```ts\n// Bus \"auth\" is disposed\n```\n\n`name` has no effect on behavior and does not need to be unique.\n\n### Detecting listener leaks with `maxListeners`\n\nPass `maxListeners` to `createBus()` to receive a `console.warn` whenever a single event's listener count exceeds the threshold. This helps catch accidental listener accumulation during development.\n\n```ts\nconst bus = createBus<AppEvents>({ maxListeners: 10 });\n\n// Registering an 11th listener for 'cart:updated' prints:\n// [herald:warn] \"cart:updated\" has 11 listeners, exceeding maxListeners (10). Possible memory leak.\n```\n\nThe warning fires for both event-specific listeners (`on`, `once`) and wildcard listeners (`onAny`). There is no effect on bus behavior — all listeners are still registered and invoked normally.\n\n### Counting listeners\n\n`listenerCount()` lets you inspect active subscriptions without needing to track them manually:\n\n```ts\nbus.on('user:login', handler1);\nbus.on('user:login', handler2);\nbus.on('user:logout', handler3);\nbus.onAny(wildcardHandler);\n\nbus.listenerCount('user:login'); // 3 — 2 specific + 1 wildcard\nbus.listenerCount(); // 4 — 3 specific + 1 wildcard (wildcards counted once)\n```\n\nThis is useful for debugging, assertions in tests, or conditional emit optimizations.\n\nYou can combine this with `eventNames()` when you need a quick snapshot of which channels are active.\n\n### `using` keyword\n\n`Bus` implements `[Symbol.dispose]`, so it works with the `using` keyword (TypeScript 5.2+, `\"lib\": [\"esnext\"]`):\n\n```ts\n{\n using bus = createBus<AppEvents>();\n bus.on('user:login', handler);\n bus.emit('user:login', { userId: '1', email: 'a@b.com' });\n} // bus.dispose() is called automatically here\n```\n\nThis is especially useful in test cases, request handlers, or any scope where you want guaranteed cleanup.\n\n## Testing\n\nImport `createTestBus` from `@vielzeug/herald/testing`. It wraps `createBus` and records every emitted payload by event key.\n\n```ts\nimport { createTestBus } from '@vielzeug/herald/testing';\n\nconst bus = createTestBus<AppEvents>();\n\nbus.emit('user:login', { userId: '1', email: 'a@example.com' });\nbus.emit('user:login', { userId: '2', email: 'b@example.com' });\n\n// emitted() returns a typed snapshot — not a live reference\nexpect(bus.emitted('user:login')).toEqual([\n { userId: '1', email: 'a@example.com' },\n { userId: '2', email: 'b@example.com' },\n]);\n\nbus.reset(); // clear recorded payloads, keep listeners active\nbus.dispose(); // clear listeners and recorded payloads\n```\n\nUse `emittedCount(event)` when you only need the count, not the full payload list:\n\n```ts\nbus.emittedCount('user:login'); // number of times the event was emitted\n```\n\n`createTestBus` accepts the full `BusOptions<T>` including `onError`.\n\nUse `reset()` to clear recorded payloads between assertions without affecting active listeners:\n\n```ts\nbus.emit('user:login', { email: 'a@example.com', userId: '1' });\nbus.reset(); // clears emission records — listeners remain active\n\nbus.emitted('user:login'); // => []\n```\n\nUse `using` for automatic cleanup in test cases:\n\n```ts\nit('records emitted events', () => {\n using bus = createTestBus<AppEvents>();\n bus.emit('user:logout');\n expect(bus.emitted('user:logout')).toHaveLength(1);\n}); // bus disposed automatically\n```\n\n## Framework Integration\n\n::: code-group\n\n```tsx [React]\nimport { useEffect } from 'react';\nimport { createBus } from '@vielzeug/herald';\n\ntype AppEvents = {\n 'user:login': { userId: string; email: string };\n 'user:logout': void;\n};\n\n// Module-level bus shared across components\nconst bus = createBus<AppEvents>();\n\nfunction useEvent<K extends keyof AppEvents>(event: K, handler: (payload: AppEvents[K]) => void) {\n useEffect(() => {\n const controller = new AbortController();\n bus.on(event as any, handler as any, { signal: controller.signal });\n return () => controller.abort();\n }, [event, handler]);\n}\n\nfunction LoginButton() {\n useEvent('user:login', ({ userId }) => console.log('logged in:', userId));\n return <button onClick={() => bus.emit('user:login', { userId: '1', email: 'a@x.com' })}>Login</button>;\n}\n```\n\n```ts [Vue 3]\nimport { onScopeDispose } from 'vue';\nimport { createBus } from '@vielzeug/herald';\n\ntype AppEvents = {\n 'user:login': { userId: string; email: string };\n 'user:logout': void;\n};\n\nconst bus = createBus<AppEvents>();\n\nfunction useEvent<K extends keyof AppEvents>(event: K, handler: (payload: AppEvents[K]) => void) {\n const controller = new AbortController();\n bus.on(event as any, handler as any, { signal: controller.signal });\n onScopeDispose(() => controller.abort());\n}\n```\n\n```svelte [Svelte]\n<script lang=\"ts\">\n import { onDestroy } from 'svelte';\n import { createBus } from '@vielzeug/herald';\n\n type AppEvents = {\n 'user:login': { userId: string; email: string };\n 'user:logout': void;\n };\n\n const bus = createBus<AppEvents>();\n\n // Listen to events with automatic cleanup on component destroy\n const controller = new AbortController();\n bus.on('user:login', ({ userId }) => console.log('logged in:', userId), { signal: controller.signal });\n onDestroy(() => controller.abort());\n\n function login() {\n bus.emit('user:login', { userId: '1', email: 'alice@example.com' });\n }\n</script>\n\n<button on:click={login}>Login</button>\n```\n\n:::\n\n## Working with Other Vielzeug Libraries\n\n### With Rune\n\nUse Rune to trace all event dispatches in development.\n\n```ts\nimport { createBus } from '@vielzeug/herald';\nimport { createLogger } from '@vielzeug/rune';\n\nconst logger = createLogger({ scope: 'herald' });\n\nconst bus = createBus<AppEvents>({\n logger: { debug: logger.debug, warn: logger.warn },\n onError: ({ err, event }) => logger.error('handler failed', { err, event }),\n});\n```\n\n### With Ripple\n\nUse Ripple signals to reflect the latest event payload as reactive state.\n\n```ts\nimport { createBus } from '@vielzeug/herald';\nimport { signal } from '@vielzeug/ripple';\n\ntype AppEvents = { 'user:login': { userId: string; email: string }; 'user:logout': void };\nconst bus = createBus<AppEvents>();\n\nconst currentUser = signal<{ userId: string; email: string } | null>(null);\n\nbus.on('user:login', (payload) => {\n currentUser.value = payload;\n});\nbus.on('user:logout', () => {\n currentUser.value = null;\n});\n```\n\n## Best Practices\n\n- Create one bus per logical domain (e.g., one bus per micro-frontend module) rather than a single global bus.\n- Pass `AbortSignal` to `on()` and `once()` for lifecycle-bound listeners — avoids manual `unsub()` tracking.\n- Use `wait()` for one-off async coordination; use `events()` for continuous processing pipelines.\n- Configure `onError` on the bus rather than wrapping each listener in try/catch.\n- Call `dispose()` when a bus is no longer needed — it rejects all pending `wait()` promises.\n- Use `pipeEvents()` to forward events between buses rather than re-emitting manually inside listeners.\n- Pass `bus.disposalSignal` to tie external subscriptions and fetch calls to the bus lifetime.\n- Prefer typed `EventMap` interfaces over generic string keys for full payload inference.\n- Use `createTestBus` from `@vielzeug/herald/testing` in unit tests rather than mocking the bus.\n",
4
+ "index": "---\ntitle: Herald — Typed event bus for TypeScript\ndescription: Typed temporal event delivery with sync subscriptions, async waiting, streams, pipes, and AbortSignal lifecycle.\npackage: herald\ncategory: events\nkeywords: [event-bus, typed-events, pub-sub, async-streams, abort-signal]\nrelated: [ripple, wayfinder, familiar]\nexports: [createBus, pipeEvents, combineSignals, BusDisposedError, HeraldConfigError]\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"herald\" />\n\n## Why Herald?\n\nRaw event emitters lose payload inference and leave waiting, streaming, cancellation, and teardown to every caller. Herald keeps events temporal: use [Ripple](/ripple/) when you need retained state.\n\n```ts\n// Before\nconst listeners = new Set<(payload: unknown) => void>();\nlisteners.add((payload) => loadProfile((payload as { id: string }).id));\n\n// After\nimport { createBus } from '@vielzeug/herald';\n\ninterface AppEvents {\n 'user:login': { id: string };\n}\n\nfunction loadProfile(id: string): void {\n console.log(id);\n}\n\nconst bus = createBus<AppEvents>();\nbus.on('user:login', ({ id }) => loadProfile(id));\n```\n\n| Feature | Herald | mitt | EventEmitter3 |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"herald\" type=\"size\" /> | ~200 B | ~1.5 kB |\n| Typed payloads | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"triangle-alert\" size=\"16\"></ore-icon> | <ore-icon name=\"triangle-alert\" size=\"16\"></ore-icon> |\n| Async wait and streams | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| AbortSignal lifecycle | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Typed event pipes | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Herald when** modules need typed temporal event delivery with owned lifecycle.\n\n**Consider Ripple when** consumers need current state and replayed values.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/herald\n```\n\n```sh [npm]\nnpm install @vielzeug/herald\n```\n\n```sh [yarn]\nyarn add @vielzeug/herald\n```\n\n:::\n\n## Quick Start\n\n```ts\nimport { createBus } from '@vielzeug/herald';\n\ninterface AppEvents {\n 'user:login': { id: string };\n 'user:logout': void;\n}\n\nconst bus = createBus<AppEvents>();\nconst stop = bus.on('user:login', ({ id }) => console.log(id));\n\nbus.emit('user:login', { id: '42' });\nstop();\nbus.dispose();\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `on()` / `once()` — typed subscriptions with explicit teardown\n- `onAny()` — cross-cutting event observation\n- `wait()` / `waitAny()` one-shot async coordination\n- `events()` — bounded async event streams\n- `pipeEvents()` — compatible cross-bus forwarding\n- `AbortSignal` — cancellation and disposal ownership\n- `createTestBus()` — emitted-payload recording for tests\n- `debugBus()` — development logging from `/devtools`\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Ripple](/ripple/) — retained reactive state.\n- [Wayfinder](/wayfinder/) — route lifecycle events.\n- [Familiar](/familiar/) — worker completion events.\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
+ "api": "---\ntitle: Herald — API Reference\ndescription: Reference for typed temporal event delivery, lifecycle ownership, and compatible event piping.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| --- | --- | --- | --- |\n| `createBus()` | Create typed temporal event bus | Sync | `emit()` and middleware are synchronous |\n| `pipeEvents()` | Forward compatible source events | Sync | Payloads must be assignable to target event |\n| `combineSignals()` | Abort when any input aborts | Sync | Public composition has no manual teardown |\n| `createTestBus()` | Record dispatched test events | Sync | Available from `/testing` only |\n| `debugBus()` | Create console-debug instrumented bus | Sync | Available from `/devtools` only |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/herald` | Runtime bus, pipes, public types, and errors |\n| `@vielzeug/herald/testing` | `createTestBus()` and `TestBus` |\n| `@vielzeug/herald/devtools` | `debugBus()` |\n\n## Core Functions\n\n### `createBus()`\n\n```ts\nfunction createBus<T extends EventMap = Record<string, unknown>>(\n options?: BusOptions<T>,\n): Bus<T>;\n```\n\nCreates a synchronous bus for future event delivery.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `options` | `BusOptions<T>` | Optional middleware, validation, error handling, logging, and listener threshold configuration. |\n\n**Returns:** `Bus<T>`.\n\n```ts\nimport { createBus } from '@vielzeug/herald';\n\ninterface Events {\n count: number;\n ready: void;\n}\n\nconst bus = createBus<Events>();\nbus.emit('count', 1);\nbus.emit('ready');\nbus.dispose();\n```\n\n---\n\n### `pipeEvents()`\n\n```ts\nfunction pipeEvents<S extends EventMap, T extends EventMap>(\n source: Bus<S>,\n target: Bus<T>,\n entries: readonly [PipeEntry<S, T>, ...PipeEntry<S, T>[]],\n options?: { signal?: AbortSignal },\n): Unsubscribe;\n```\n\nForwards listed compatible events until manually stopped, either bus disposes, or `options.signal` aborts.\n\n| Parameter | Type | Description |\n| --- | --- | --- |\n| `source` | `Bus<S>` | Bus that emits source events. |\n| `target` | `Bus<T>` | Bus that receives compatible events. |\n| `entries` | non-empty `PipeEntry` tuple | Same-name keys or compatible `{ from, to }` mappings. |\n| `options.signal` | `AbortSignal` | Optional pipe lifetime signal. |\n\n**Returns:** Idempotent `Unsubscribe` function.\n\n```ts\nimport { createBus, pipeEvents } from '@vielzeug/herald';\n\ninterface SourceEvents {\n 'auth:login': { id: string };\n}\n\ninterface TargetEvents {\n 'user:authenticated': { id: string };\n}\n\nconst source = createBus<SourceEvents>();\nconst target = createBus<TargetEvents>();\nconst stop = pipeEvents(source, target, [{ from: 'auth:login', to: 'user:authenticated' }]);\n\nstop();\nsource.dispose();\ntarget.dispose();\n```\n\n---\n\n### `combineSignals()`\n\n```ts\nfunction combineSignals(first: AbortSignal, ...rest: AbortSignal[]): AbortSignal;\n```\n\nReturns a signal aborted with first input signal's reason.\n\n**Returns:** `AbortSignal`.\n\n```ts\nimport { combineSignals } from '@vielzeug/herald';\n\nconst signal = combineSignals(AbortSignal.timeout(1_000), controller.signal);\n```\n\nInput listeners remain until an input aborts. Bus APIs that accept `{ signal }` clean their internal signal composition when their owned operation ends.\n\n## Types\n\n### `EventMap` and `EventKey`\n\n```ts\ntype EventMap = object;\ntype EventKey<T extends EventMap> = Extract<keyof T, string>;\n```\n\n`EventMap` accepts interfaces and type aliases. Only string keys are event names.\n\n---\n\n### `BusOptions`\n\n```ts\ntype BusOptions<T extends EventMap> = {\n logger?: BusLogger;\n maxListeners?: number;\n middleware?: readonly Middleware<T>[];\n name?: string;\n onError?: (context: EmissionErrorContext<T>) => void;\n validatePayload?: <K extends EventKey<T>>(event: K, payload: T[K]) => void;\n};\n```\n\n| Field | Description |\n| --- | --- |\n| `logger` | Optional debug and warning output. |\n| `maxListeners` | Warn when one event exceeds this active-listener count. |\n| `middleware` | Synchronous dispatch middleware. |\n| `name` | Display name in debug logs and disposal errors. |\n| `onError` | Handles listener and validation errors instead of rethrowing. |\n| `validatePayload` | Runs before middleware and listeners. |\n\n---\n\n### `Bus`\n\n```ts\ninterface Bus<T extends EventMap> {\n [Symbol.dispose](): void;\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n emit<K extends EventKey<T>>(event: K, ...args: T[K] extends void ? [] : [payload: T[K]]): number;\n eventNames(): EventKey<T>[];\n events<K extends EventKey<T>>(event: K, options?: { maxBuffer?: number; signal?: AbortSignal }): EventStream<T[K]>;\n listenerCount(event?: EventKey<T>): number;\n on<K extends EventKey<T>>(event: K, listener: Listener<T[K]>, options?: SubscribeOptions): Unsubscribe;\n onAny(listener: (event: EventKey<T>, payload: unknown) => void, options?: SubscribeOptions): Unsubscribe;\n once<K extends EventKey<T>>(event: K, listener: Listener<T[K]>, options?: { signal?: AbortSignal }): Unsubscribe;\n wait<K extends EventKey<T>>(event: K, options?: { signal?: AbortSignal }): Promise<T[K]>;\n waitAny<const K extends readonly [EventKey<T>, EventKey<T>, ...EventKey<T>[]]>(\n events: K,\n options?: { signal?: AbortSignal },\n ): Promise<WaitAnyResult<T, K>>;\n wildcardCount(): number;\n}\n```\n\n`emit()` returns listener count or `0` after disposal, blocked middleware, or handled validation rejection.\n\n---\n\n### `BusLogger`, `Listener`, `SubscribeOptions`, and `Unsubscribe`\n\n```ts\ntype BusLogger = {\n debug?: (message: string) => void;\n warn?: (message: string) => void;\n};\n\ntype Listener<T> = (payload: T) => void;\ntype SubscribeOptions = { once?: boolean; signal?: AbortSignal };\ntype Unsubscribe = () => void;\n```\n\n---\n\n### `EmissionErrorContext` and `Middleware`\n\n```ts\ntype EmissionErrorContext<T extends EventMap> = {\n err: unknown;\n event: EventKey<T>;\n payload: unknown;\n timestamp: number;\n};\n\ntype Middleware<T extends EventMap> = (\n event: EventKey<T>,\n payload: unknown,\n next: () => void,\n) => void;\n```\n\nCall middleware `next()` synchronously at most once. Omit it to block dispatch.\n\n---\n\n### `EventStream` and `WaitAnyResult`\n\n```ts\ntype EventStream<T> = AsyncGenerator<T> & AsyncDisposable;\n\ntype WaitAnyResult<T extends EventMap, K extends readonly EventKey<T>[]> = {\n [I in keyof K]: K[I] extends EventKey<T> ? { event: K[I]; payload: T[K[I]] } : never;\n}[number];\n```\n\n---\n\n### `PipeableKey`, `RenamedPipeEntry`, and `PipeEntry`\n\n```ts\ntype PipeableKey<S extends EventMap, T extends EventMap> = {\n [K in EventKey<S> & EventKey<T>]: S[K] extends T[K] ? K : never;\n}[EventKey<S> & EventKey<T>];\n\ntype RenamedPipeEntry<S extends EventMap, T extends EventMap> = {\n [From in EventKey<S>]: {\n [To in EventKey<T>]: S[From] extends T[To] ? { from: From; to: To } : never;\n }[EventKey<T>];\n}[EventKey<S>];\n\ntype PipeEntry<S extends EventMap, T extends EventMap> =\n | PipeableKey<S, T>\n | RenamedPipeEntry<S, T>;\n```\n\n## Testing and Devtools\n\n### `createTestBus()`\n\n```ts\nfunction createTestBus<T extends EventMap = Record<string, unknown>>(\n options?: BusOptions<T>,\n): TestBus<T>;\n```\n\nCreates a bus that records dispatched payloads.\n\n**Returns:** `TestBus<T>`.\n\n### `TestBus`\n\n```ts\ntype TestBus<T extends EventMap> = Bus<T> & {\n allEmitted(): { [K in EventKey<T>]?: T[K][] };\n emitted<K extends EventKey<T>>(event: K): T[K][];\n emittedCount<K extends EventKey<T>>(event: K): number;\n reset(): void;\n};\n```\n\n### `debugBus()`\n\n```ts\nfunction debugBus<T extends EventMap>(\n options?: Omit<BusOptions<T>, 'logger'> & { logger?: { warn?: BusLogger['warn'] } },\n): Bus<T>;\n```\n\nCreates a bus with `console.debug` logging. Import from `@vielzeug/herald/devtools`.\n\n## Errors\n\n| Error | Trigger | Notable properties |\n| --- | --- | --- |\n| `BusDisposedError` | `wait()` or `waitAny()` interrupted by disposal | Bus name appears when configured. |\n| `HeraldConfigError` | Invalid stream buffer, empty pipe entries, or fewer than two `waitAny()` events | — |\n| `HeraldError` | Base class for Herald-originated errors | `HeraldError.is(error)` narrows subclasses. |\n",
6
+ "usage": "---\ntitle: Herald — Usage Guide\ndescription: Typed event maps, lifecycle-owned subscriptions, waits, streams, pipes, and testing.\n---\n\n[[toc]]\n\n## Basic Usage\n\nUse interface or type alias event maps. Events model facts that happened; use Ripple for current state.\n\n```ts\nimport { createBus } from '@vielzeug/herald';\n\ninterface AppEvents {\n 'cart:updated': { count: number };\n 'user:logout': void;\n}\n\nconst bus = createBus<AppEvents>();\nconst stop = bus.on('cart:updated', ({ count }) => console.log(count));\n\nbus.emit('cart:updated', { count: 1 });\nstop();\nbus.dispose();\n```\n\n## Subscriptions\n\nUse `once()` for one event and `{ signal }` for owned subscription lifetime.\n\n```ts\nconst controller = new AbortController();\n\nbus.on('cart:updated', renderCart, { signal: controller.signal });\nbus.once('user:logout', clearSession);\ncontroller.abort();\n```\n\n## Middleware and Validation\n\nMiddleware is synchronous. Call `next()` once to continue; omit it to block dispatch.\n\n```ts\nconst bus = createBus<AppEvents>({\n middleware: [\n (event, payload, next) => {\n audit(event, payload);\n next();\n },\n ],\n validatePayload: (event, payload) => {\n if (event === 'cart:updated' && payload.count < 0) throw new RangeError('count must be non-negative');\n },\n});\n```\n\n## Awaiting Events\n\n```ts\nconst cart = await bus.wait('cart:updated', { signal: AbortSignal.timeout(5_000) });\nconst winner = await bus.waitAny(['cart:updated', 'user:logout'], { signal: AbortSignal.timeout(5_000) });\n```\n\n## Streaming Events\n\n`events()` subscribes eagerly. Bound buffers for producers faster than consumers.\n\n```ts\nawait using stream = bus.events('cart:updated', { maxBuffer: 100 });\n\nfor await (const cart of stream) {\n renderCart(cart);\n}\n```\n\n## Piping Events\n\n`pipeEvents()` only accepts compatible payloads. Stop explicitly or tie pipe to signal.\n\n```ts\nconst stopPipe = pipeEvents(sourceBus, auditBus, ['cart:updated'], { signal: pageSignal });\nstopPipe();\n```\n\n## Testing\n\n`createTestBus()` records dispatched payloads without mocks.\n\n```ts\nimport { createTestBus } from '@vielzeug/herald/testing';\n\nconst bus = createTestBus<AppEvents>();\nbus.emit('cart:updated', { count: 2 });\nexpect(bus.emitted('cart:updated')).toEqual([{ count: 2 }]);\nbus.dispose();\n```\n\n## Debugging\n\n```ts\nimport { debugBus } from '@vielzeug/herald/devtools';\n\nconst bus = debugBus<AppEvents>({ name: 'cart' });\n```\n\n## Working with Other Vielzeug Libraries\n\nUse Herald for temporal events. Use Ripple for retained reactive state. Use Familiar or Courier completion handlers to emit application events.\n\n## Best Practices\n\n- Define one explicit event map per boundary.\n- Emit facts, not mutable application state.\n- Keep middleware synchronous and call `next()` once.\n- Pass AbortSignals for component/request scoped work.\n- Set `maxBuffer` for long-lived streams.\n- Use `wait()` only for one-off coordination.\n- Use unsubscribe handles instead of global listener removal.\n- Dispose owner-scoped buses.\n",
7
7
  "examples": "---\ntitle: Herald — Examples\ndescription: Practical examples and recipes for herald.\n---\n\n## Examples\n\n- [Standalone Entry](./examples/standalone-entry.md)\n- [Module Level Bus](./examples/module-level-bus.md)\n- [Awaiting A One Time Event](./examples/awaiting-a-one-time-event.md)\n- [Inspecting Listener Counts](./examples/inspecting-listener-counts.md)\n- [Custom Error Boundary](./examples/custom-error-boundary.md)\n- [Handling Disposal In Async Code](./examples/handling-disposal-in-async-code.md)\n- [Request Scoping](./examples/request-scoping.md)\n- [Streaming With Events](./examples/streaming-with-events.md)\n- [Bus Bridging With pipeEvents](./examples/bus-bridging-with-pipeevents.md)\n- [Testing With Createtestbus](./examples/testing-with-createtestbus.md)\n"
8
8
  },
9
9
  "examples": [
@@ -27,16 +27,6 @@
27
27
  "code": "import { createBus } from '@vielzeug/herald'\n\n// Typed pub/sub with on(), emit(), and once()\nconst bus = createBus()\n\nconst unsub = bus.on('user:login', ({ userId, email }) => {\n console.log('login:', userId, email)\n})\n\nbus.once('user:logout', () => {\n console.log('logged out (fires once)')\n})\n\nbus.emit('user:login', { userId: '1', email: 'alice@example.com' })\nbus.emit('user:logout')\nbus.emit('user:logout') // once() already removed; no output\n\nunsub()\nbus.emit('user:login', { userId: '2', email: 'bob@example.com' }) // no output — unsubscribed\n\nbus.dispose()",
28
28
  "name": "Basic Bus"
29
29
  },
30
- {
31
- "id": "behavior-bus",
32
- "code": "import { createBehaviorBus } from '@vielzeug/herald'\n\n// BehaviorBus replays the last value to new subscribers — like BehaviorSubject\nconst bus = createBehaviorBus({ theme: 'light', zoom: 1 })\n\n// First subscriber receives current values immediately\nbus.on('theme', (t) => console.log('A sees theme:', t))\nbus.on('zoom', (z) => console.log('A sees zoom:', z))\n\nbus.emit('theme', 'dark')\n\n// Late subscriber gets the current value on registration\nbus.on('theme', (t) => console.log('B sees theme:', t))\n\n// Read current value without subscribing\nconsole.log('current theme:', bus.current('theme'))\nconsole.log('current zoom:', bus.current('zoom'))\n\n// snapshot() returns all buffered values at once\nconsole.log('snapshot:', bus.snapshot()) // { theme: 'dark', zoom: 1 }\n\n// reset() clears the buffer for one or all events\nbus.reset('zoom')\nconsole.log('after reset, snapshot:', bus.snapshot()) // { theme: 'dark' }\n\nbus.dispose()",
33
- "name": "Behavior Bus"
34
- },
35
- {
36
- "id": "behavior-snapshot",
37
- "code": "import { createBehaviorBus } from '@vielzeug/herald'\n\n// snapshot() reads all buffered values at once — useful for serialization\n// and hydrating another bus or UI from the current state\n\nconst bus = createBehaviorBus({ theme: 'light', zoom: 1, sidebar: true })\n\nconsole.log('initial snapshot:', bus.snapshot())\n// { theme: 'light', zoom: 1, sidebar: true }\n\nbus.emit('theme', 'dark')\nbus.emit('zoom', 2)\n\nconsole.log('after emits:', bus.snapshot())\n// { theme: 'dark', zoom: 2, sidebar: true }\n\n// Hydrate a new bus from the snapshot\nconst saved = bus.snapshot()\nconst restored = createBehaviorBus(saved)\n\nconsole.log('restored theme:', restored.current('theme')) // 'dark'\nconsole.log('restored zoom:', restored.current('zoom')) // 2\n\n// reset() only clears entries you choose\nbus.reset('zoom')\nconsole.log('after reset zoom:', bus.snapshot())\n// { theme: 'dark', sidebar: true } — zoom is gone\n\nbus.dispose()\nrestored.dispose()",
38
- "name": "BehaviorBus snapshot()"
39
- },
40
30
  {
41
31
  "id": "bus-basics",
42
32
  "code": "import { createBus } from '@vielzeug/herald'\n\n// on() returns an unsubscribe handle; emit() delivers to all active listeners\nconst bus = createBus()\n\nconst unsubLogin = bus.on('user:login', (payload) => {\n console.log('login:', payload.name, '(' + payload.userId + ')')\n})\n\nbus.on('user:logout', () => console.log('logged out'))\nbus.on('notification', (msg) => console.log('notification:', msg))\n\nbus.emit('user:login', { userId: '123', name: 'Alice' })\nbus.emit('notification', 'welcome back!')\n\nunsubLogin()\n\nbus.emit('user:login', { userId: '456', name: 'Bob' }) // no output — unsubscribed\nconsole.log('active listeners:', bus.listenerCount())\n\nbus.dispose()",
@@ -79,7 +69,7 @@
79
69
  },
80
70
  {
81
71
  "id": "test-bus",
82
- "code": "import { createTestBus } from '@vielzeug/herald/testing'\n\n// TestBus wraps a normal bus with emission recording — no mocking required\nconst bus = createTestBus()\n\nbus.on('cart:updated', (cart) => console.log('handler saw total:', cart.total))\n\nbus.emit('cart:updated', { items: 1, total: 19.99 })\nbus.emit('cart:updated', { items: 2, total: 39.98 })\nbus.emit('user:logout')\n\nconsole.log('emitted count:', bus.emittedCount('cart:updated')) // 2\nconsole.log('all payloads:', bus.emitted('cart:updated'))\nconsole.log('every recorded event:', bus.allEmitted())\n\n// removeAllListeners() stops delivery but keeps the historical records\nbus.removeAllListeners('cart:updated')\nbus.emit('cart:updated', { items: 3, total: 59.97 }) // no handler output, still recorded\n\nconsole.log('after removeAllListeners:', bus.emitted('cart:updated'))\n\nbus.dispose()",
72
+ "code": "import { createTestBus } from '@vielzeug/herald/testing'\n\n// TestBus wraps a normal bus with emission recording — no mocking required\nconst bus = createTestBus()\n\nconst stop = bus.on('cart:updated', (cart) => console.log('handler saw total:', cart.total))\n\nbus.emit('cart:updated', { items: 1, total: 19.99 })\nbus.emit('cart:updated', { items: 2, total: 39.98 })\nbus.emit('user:logout')\n\nconsole.log('emitted count:', bus.emittedCount('cart:updated')) // 2\nconsole.log('all payloads:', bus.emitted('cart:updated'))\nconsole.log('every recorded event:', bus.allEmitted())\n\nstop()\nbus.emit('cart:updated', { items: 3, total: 59.97 }) // still recorded\n\nconsole.log('after unsubscribe:', bus.emitted('cart:updated'))\n\nbus.dispose()",
83
73
  "name": "createTestBus()"
84
74
  },
85
75
  {
@@ -94,29 +84,25 @@
94
84
  }
95
85
  ],
96
86
  "typeSignatures": {
97
- "createBehaviorBus": "export { createBehaviorBus } from './behavior-bus';",
98
87
  "combineSignals": "export { combineSignals, createBus } from './bus';",
99
88
  "createBus": "export { combineSignals, createBus } from './bus';",
100
89
  "BusDisposedError": "export { BusDisposedError, HeraldConfigError, HeraldError } from './errors';",
101
90
  "HeraldConfigError": "export { BusDisposedError, HeraldConfigError, HeraldError } from './errors';",
102
91
  "HeraldError": "export { BusDisposedError, HeraldConfigError, HeraldError } from './errors';",
103
92
  "pipeEvents": "export { pipeEvents } from './pipe';",
104
- "BehaviorBus": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
105
- "BehaviorBusOptions": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
106
- "BehaviorInitial": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
107
- "Bus": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
108
- "BusLogger": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
109
- "BusOptions": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
110
- "EmissionErrorContext": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
111
- "EventKey": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
112
- "EventMap": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
113
- "EventStream": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
114
- "Listener": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
115
- "Middleware": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
116
- "PipeableKey": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
117
- "PipeEntry": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
118
- "SubscribeOptions": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
119
- "Unsubscribe": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
120
- "WaitAnyResult": "export type {\n BehaviorBus,\n BehaviorBusOptions,\n BehaviorInitial,\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';"
93
+ "Bus": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
94
+ "BusLogger": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
95
+ "BusOptions": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
96
+ "EmissionErrorContext": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
97
+ "EventKey": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
98
+ "EventMap": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
99
+ "EventStream": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
100
+ "Listener": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
101
+ "Middleware": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
102
+ "PipeableKey": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
103
+ "PipeEntry": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
104
+ "SubscribeOptions": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
105
+ "Unsubscribe": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';",
106
+ "WaitAnyResult": "export type {\n Bus,\n BusLogger,\n BusOptions,\n EmissionErrorContext,\n EventKey,\n EventMap,\n EventStream,\n Listener,\n Middleware,\n PipeableKey,\n PipeEntry,\n SubscribeOptions,\n Unsubscribe,\n WaitAnyResult,\n} from './types';"
121
107
  }
122
108
  }