first commit
This commit is contained in:
86
docs/api-stack/api/api-ws-broker-in-memory/README.md
Normal file
86
docs/api-stack/api/api-ws-broker-in-memory/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# @suaveplan/api-ws-broker-in-memory - In-memory single-host WebSocket broker for @suaveplan/api-ws. Implements WsBroker via direct in-process fan-out — publish iterates a topic->handlers map and invokes each subscriber's handler. Supports fire-and-forget and await-all delivery modes, idempotent dispose, Zod-validated options, and the WS_BROKER_IN_MEMORY.* error registry (INVALID_OPTIONS, BROKER_DISPOSED). Production-grade for single-host deployments; tests-grade for any plugin manifesting WebSocket endpoints. For multi-host fan-out use @suaveplan/api-ws-broker-redis (Stage 7).
|
||||
|
||||
Single-host in-process implementation of the `@suaveplan/api-ws`
|
||||
`WsBroker` contract. Suitable for production single-host deployments
|
||||
AND as the default broker wired into `createTestHost` ($2
|
||||
kit). For multi-host fan-out use `@suaveplan/api-ws-broker-redis`
|
||||
(Stage 7).
|
||||
|
||||
The broker maintains a per-topic `Set<handler>` registry and dispatches
|
||||
every `publish(topic, envelope)` to every handler currently subscribed
|
||||
to that topic. There is no network, no message bus, and no persistence —
|
||||
"in-memory" describes the storage substrate, not the maturity level.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [broker](./src/broker/broker.md) — This module implements the `WsBroker` contract from `@suaveplan/api-ws` by maintaining an in-process `Map<topic, Set<ha…
|
||||
- [broker](./src/broker/broker.md) — This module implements the `WsBroker` contract from `@suaveplan/api-ws` by maintaining an in-process `Map<topic, Set<ha…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — This module defines the stable, namespaced error-code registry for the in-memory WebSocket broker. Every public failure…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — This module defines the stable, namespaced error-code registry for the in-memory WebSocket broker. Every public failure…
|
||||
- [options](./src/options/options.md) — This module owns the Zod-validated option shape consumed by `createInMemoryWsBroker`. Keeping it in a dedicated feature…
|
||||
- [options](./src/options/options.md) — This module owns the Zod-validated option shape consumed by `createInMemoryWsBroker`. Keeping it in a dedicated feature…
|
||||
- [testing](./src/testing/testing.md) — This module exposes a small, opinionated test harness around the in-memory broker. It removes the boilerplate of wiring…
|
||||
- [testing](./src/testing/testing.md) — This module exposes a small, opinionated test harness around the in-memory broker. It removes the boilerplate of wiring…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## What ships
|
||||
|
||||
| Subpath | Module | Doc |
|
||||
|---|---|---|
|
||||
| `./broker` | `createInMemoryWsBroker(options?)` factory | [`src/broker/broker.md`](src/broker/broker.md) |
|
||||
| `./error-codes` | `WS_BROKER_IN_MEMORY_CODES` + `WsBrokerInMemoryError` | [`src/error-codes/error-codes.md`](src/error-codes/error-codes.md) |
|
||||
| `./testing` | `createInMemoryBrokerHarness(options?)` for tests | [`src/testing/testing.md`](src/testing/testing.md) |
|
||||
|
||||
## Surface
|
||||
|
||||
- `createInMemoryWsBroker(options?)` — factory returning an
|
||||
`InMemoryWsBroker` (the `WsBroker` contract plus
|
||||
`subscriberCount(topic)` and `isDisposed()` introspection helpers).
|
||||
- `InMemoryWsBrokerOptions { deliveryMode? }` — Zod-validated strict
|
||||
option shape. `deliveryMode` is `"fire-and-forget"` (default) or
|
||||
`"await-all"`.
|
||||
- `WS_BROKER_IN_MEMORY_CODES` — closed set of two stable codes:
|
||||
`INVALID_OPTIONS` and `BROKER_DISPOSED`.
|
||||
- `createInMemoryBrokerHarness(options?)` — opinionated test harness
|
||||
wrapping the broker with a captured-envelope observer + idempotent
|
||||
teardown.
|
||||
|
||||
## Quickstart
|
||||
|
||||
```ts
|
||||
import { createInMemoryWsBroker } from "@suaveplan/api-ws-broker-in-memory";
|
||||
import type { TenantId } from "@suaveplan/api-types/tenant";
|
||||
|
||||
const broker = createInMemoryWsBroker<{ id: number }>();
|
||||
const sub = broker.subscribe("orders", (envelope) => {
|
||||
console.log("delivered", envelope.payload.id);
|
||||
});
|
||||
|
||||
await broker.publish("orders", {
|
||||
topic: "orders",
|
||||
tenant: "t1" as unknown as TenantId,
|
||||
payload: { id: 1 },
|
||||
});
|
||||
|
||||
await sub.unsubscribe();
|
||||
broker.dispose();
|
||||
```
|
||||
|
||||
## Single-host vs multi-host
|
||||
|
||||
This package is **single-host only**. Two host replicas instantiated
|
||||
with separate `createInMemoryWsBroker()` calls each see only their own
|
||||
publishes; there is no shared substrate. For multi-host topologies use
|
||||
`@suaveplan/api-ws-broker-redis` (Stage 7), which implements the same
|
||||
`WsBroker` contract over Redis Pub/Sub.
|
||||
|
||||
## Quality bars
|
||||
|
||||
- 100% line / branch / function / statement coverage (Istanbul).
|
||||
- 100% docs coverage — every implementation `.ts` has a sibling `.md`
|
||||
with the required headings.
|
||||
- Zero direct imports of `vitest` / `bun:test` — all test files import
|
||||
from `@suaveplan/testing/runner`.
|
||||
- Stable, namespaced error codes (`WS_BROKER_IN_MEMORY.*`).
|
||||
|
||||
123
docs/api-stack/api/api-ws-broker-in-memory/src/broker/broker.md
Normal file
123
docs/api-stack/api/api-ws-broker-in-memory/src/broker/broker.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# `broker` — `createInMemoryWsBroker`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module implements the `WsBroker` contract from
|
||||
`@suaveplan/api-ws` by maintaining an in-process `Map<topic, Set<handler>>`
|
||||
table and fanning out every `publish(topic, envelope)` call to every
|
||||
subscriber currently registered for that topic. The broker is the
|
||||
single-host reference for the multi-host `api-ws-broker-redis` adapter
|
||||
and the default broker wired into the API testing kit.
|
||||
|
||||
"In-memory" describes the storage substrate, not the maturity level —
|
||||
this broker is production-grade for any deployment that fits on a
|
||||
single host (one HTTP server process, one WS engine). Multi-host
|
||||
topologies need the Redis adapter because in-memory has no
|
||||
cross-process visibility by definition.
|
||||
|
||||
## Features
|
||||
|
||||
- Direct in-process subscription map keyed by topic; O(1) subscribe,
|
||||
unsubscribe, and lookup.
|
||||
- Snapshot-before-dispatch so a handler that unsubscribes mid-fan-out
|
||||
does not skip its peers.
|
||||
- Per-subscriber error isolation — a rejecting handler never aborts
|
||||
sibling deliveries nor surfaces as a rejected `publish` promise.
|
||||
- Two delivery modes via `options.deliveryMode`: `"fire-and-forget"`
|
||||
(default; publish resolves on next microtask) and `"await-all"`
|
||||
(publish awaits every handler).
|
||||
- Idempotent `dispose()` that clears the registry and refuses
|
||||
subsequent `publish` / `subscribe` calls with a typed
|
||||
`WS_BROKER_IN_MEMORY.BROKER_DISPOSED` error.
|
||||
- `subscriberCount(topic)` and `isDisposed()` introspection helpers
|
||||
used by tests, observability adapters, and the testing harness in
|
||||
`./testing`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createInMemoryWsBroker } from "@suaveplan/api-ws-broker-in-memory/broker";
|
||||
import type { TenantId } from "@suaveplan/api-types/tenant";
|
||||
|
||||
const broker = createInMemoryWsBroker();
|
||||
const sub = broker.subscribe("orders", (envelope) => {
|
||||
console.log("received", envelope.payload);
|
||||
});
|
||||
|
||||
await broker.publish("orders", {
|
||||
topic: "orders",
|
||||
tenant: "t1" as unknown as TenantId,
|
||||
payload: { id: 1 },
|
||||
});
|
||||
|
||||
await sub.unsubscribe();
|
||||
await broker.dispose();
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { createInMemoryWsBroker } from "@suaveplan/api-ws-broker-in-memory/broker";
|
||||
import type { TenantId } from "@suaveplan/api-types/tenant";
|
||||
|
||||
// "await-all" mode: publish waits for every handler to settle, even
|
||||
// async ones. Useful in test scenarios that assert side-effects
|
||||
// immediately after publish resolves.
|
||||
const broker = createInMemoryWsBroker<{ id: number }>({
|
||||
deliveryMode: "await-all",
|
||||
});
|
||||
|
||||
const observed: number[] = [];
|
||||
broker.subscribe("orders", async (env) => {
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
observed.push(env.payload.id);
|
||||
});
|
||||
broker.subscribe("orders", async (env) => {
|
||||
observed.push(-env.payload.id);
|
||||
});
|
||||
|
||||
await broker.publish("orders", {
|
||||
topic: "orders",
|
||||
tenant: "t1" as unknown as TenantId,
|
||||
payload: { id: 42 },
|
||||
});
|
||||
// observed contains 42 and -42 in some order; both are present
|
||||
// because publish awaited both handlers before resolving.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createInMemoryWsBroker<Payload = unknown>(options?): InMemoryWsBroker<Payload>`
|
||||
— factory.
|
||||
- `interface InMemoryWsBroker<Payload> extends WsBroker<Payload>`
|
||||
- `publish(topic, envelope): Promise<void>`
|
||||
- `subscribe(topic, handler): BrokerSubscription`
|
||||
- `dispose(): void`
|
||||
- `subscriberCount(topic): number` — adapter-specific introspection.
|
||||
- `isDisposed(): boolean` — adapter-specific lifecycle query.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The registry is a `Map<string, Set<RegistryEntry>>`. Topics with
|
||||
zero subscribers are removed from the map by `unsubscribe` so the
|
||||
outer Map does not accumulate dead entries.
|
||||
- Each subscription gets a unique `RegistryEntry` reference even if
|
||||
the same `handler` callback is registered twice — duplicate
|
||||
registrations are allowed and each is independently dispatched and
|
||||
independently disposed.
|
||||
- `publish` snapshots the subscriber set into an array before
|
||||
invoking handlers. Without the snapshot, an unsubscribing handler
|
||||
could shrink the live `Set` mid-iteration and skip its peers.
|
||||
- `dispose()` clears the topic map and flips a `disposed` flag.
|
||||
Subsequent `publish` / `subscribe` throw
|
||||
`WS_BROKER_IN_MEMORY.BROKER_DISPOSED`. `dispose()` itself is a
|
||||
no-op on second call so cleanup helpers can call it
|
||||
defensively.
|
||||
- Handler rejections are individually caught (`.catch(() => {})`) so
|
||||
a misbehaving subscriber cannot poison the outer publish. This
|
||||
matches the multi-host broker's semantics: brokers are fan-out
|
||||
pipes, not error reporters.
|
||||
@@ -0,0 +1,94 @@
|
||||
# `error-codes` — `WS_BROKER_IN_MEMORY.*` registry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module defines the stable, namespaced error-code registry for the
|
||||
in-memory WebSocket broker. Every public failure path the broker can
|
||||
raise carries a `WS_BROKER_IN_MEMORY.*` code, allowing consumers (test
|
||||
harnesses, host wiring, observability layers) to branch on a small
|
||||
finite set of string literals rather than parsing free-form messages.
|
||||
|
||||
The registry is intentionally tiny — the broker's surface is small and
|
||||
deterministic, so only two failure modes are reachable: misconfiguration
|
||||
at the constructor boundary, and use-after-dispose. Multi-host failure
|
||||
modes (network unreachable, retry exhausted) live in
|
||||
`@suaveplan/api-ws-broker-redis`, not here.
|
||||
|
||||
## Features
|
||||
|
||||
- `WS_BROKER_IN_MEMORY_CODES` — frozen object produced by
|
||||
`defineCodes("WS_BROKER_IN_MEMORY", [...])`. Each value is the fully
|
||||
qualified literal (e.g. `"WS_BROKER_IN_MEMORY.BROKER_DISPOSED"`).
|
||||
- `WsBrokerInMemoryErrorCode` — discriminated string-literal union of
|
||||
every entry in the registry, suitable for exhaustive `switch`
|
||||
narrowing.
|
||||
- `WsBrokerInMemoryError` — concrete subclass of `SuaveplanError`,
|
||||
exposing the broker-specific code as both `error.code` (from the base
|
||||
class) and `error.brokerCode` for type-narrowed access.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
WS_BROKER_IN_MEMORY_CODES,
|
||||
WsBrokerInMemoryError,
|
||||
} from "@suaveplan/api-ws-broker-in-memory/error-codes";
|
||||
|
||||
throw new WsBrokerInMemoryError(
|
||||
WS_BROKER_IN_MEMORY_CODES.BROKER_DISPOSED,
|
||||
"broker disposed; construct a new one"
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import type { WsBrokerInMemoryErrorCode } from "@suaveplan/api-ws-broker-in-memory/error-codes";
|
||||
import {
|
||||
WS_BROKER_IN_MEMORY_CODES,
|
||||
WsBrokerInMemoryError,
|
||||
} from "@suaveplan/api-ws-broker-in-memory/error-codes";
|
||||
|
||||
function handle(err: unknown): void {
|
||||
if (!(err instanceof WsBrokerInMemoryError)) throw err;
|
||||
const code: WsBrokerInMemoryErrorCode = err.brokerCode;
|
||||
switch (code) {
|
||||
case WS_BROKER_IN_MEMORY_CODES.INVALID_OPTIONS:
|
||||
// Caller bug — surface as a config error to the operator.
|
||||
break;
|
||||
case WS_BROKER_IN_MEMORY_CODES.BROKER_DISPOSED:
|
||||
// Lifecycle bug — reconstruct.
|
||||
break;
|
||||
default: {
|
||||
const _exhaustive: never = code;
|
||||
void _exhaustive;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `WS_BROKER_IN_MEMORY_CODES` — `{ INVALID_OPTIONS, BROKER_DISPOSED }`
|
||||
with fully namespaced string values.
|
||||
- `WsBrokerInMemoryErrorCode` — `(typeof WS_BROKER_IN_MEMORY_CODES)[keyof ...]`.
|
||||
- `class WsBrokerInMemoryError extends SuaveplanError<WsBrokerInMemoryErrorCode>`
|
||||
— constructor `(code, message, options?)`; exposes `.brokerCode`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The registry is closed: adding a new failure mode requires an
|
||||
OpenSpec change. This is deliberate — a stable registry is what
|
||||
makes the broker safe to depend on from observability and host
|
||||
wiring.
|
||||
- Errors raised by the upstream `@suaveplan/api-ws` contract (for
|
||||
example `WS.BROKER_UNAVAILABLE`) are intentionally NOT re-thrown
|
||||
from this package: the in-memory broker never has an unavailable
|
||||
upstream, so that code is structurally unreachable here.
|
||||
- The class stores `brokerCode` separately from the inherited `code`
|
||||
field so call sites that destructure can do so without re-asserting
|
||||
the narrowed type at every use.
|
||||
@@ -0,0 +1,74 @@
|
||||
# `options` — `createInMemoryWsBroker` option surface
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module owns the Zod-validated option shape consumed by
|
||||
`createInMemoryWsBroker`. Keeping it in a dedicated feature folder lets
|
||||
the constructor stay focused on lifecycle wiring and lets the option
|
||||
defaulting logic be tested in isolation without standing up a broker
|
||||
instance.
|
||||
|
||||
The option set is deliberately small: an in-memory broker has no
|
||||
network knobs, no retry tuning, and no persistence story. The two
|
||||
levers that matter — delivery semantics and a defensive subscriber cap
|
||||
— are exposed here.
|
||||
|
||||
## Features
|
||||
|
||||
- `InMemoryWsBrokerOptions` — the public, all-optional input shape.
|
||||
- `InMemoryWsBrokerDeliveryMode` — `"fire-and-forget"` (default) or
|
||||
`"await-all"`.
|
||||
- `inMemoryWsBrokerOptionsSchema` — strict Zod schema; unknown keys
|
||||
are rejected so option typos fail at the constructor boundary.
|
||||
- `resolveInMemoryWsBrokerOptions(opts)` — applies defaults and yields
|
||||
the `ResolvedInMemoryWsBrokerOptions` shape used internally.
|
||||
- `ResolvedInMemoryWsBrokerOptions` — every field required.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { inMemoryWsBrokerOptionsSchema } from "@suaveplan/api-ws-broker-in-memory/options";
|
||||
|
||||
const parsed = inMemoryWsBrokerOptionsSchema.parse({
|
||||
deliveryMode: "await-all",
|
||||
});
|
||||
// parsed.deliveryMode === "await-all"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
inMemoryWsBrokerOptionsSchema,
|
||||
resolveInMemoryWsBrokerOptions,
|
||||
} from "@suaveplan/api-ws-broker-in-memory/options";
|
||||
|
||||
const raw = { deliveryMode: "await-all" as const };
|
||||
const safe = inMemoryWsBrokerOptionsSchema.parse(raw);
|
||||
const resolved = resolveInMemoryWsBrokerOptions(safe);
|
||||
// resolved === { deliveryMode: "await-all" }
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface InMemoryWsBrokerOptions { deliveryMode?; }`
|
||||
- `type InMemoryWsBrokerDeliveryMode = "fire-and-forget" | "await-all"`
|
||||
- `inMemoryWsBrokerOptionsSchema: z.ZodType<InMemoryWsBrokerOptions>`
|
||||
- `function resolveInMemoryWsBrokerOptions(opts): ResolvedInMemoryWsBrokerOptions`
|
||||
- `interface ResolvedInMemoryWsBrokerOptions { deliveryMode; }`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The schema is `.strict()` because typos in option keys are a common
|
||||
ops failure mode and silently ignoring them is worse than throwing.
|
||||
- The split between public `Options` (all optional) and internal
|
||||
`ResolvedOptions` (all required) lets the broker implementation skip
|
||||
per-call defaulting work and keeps every field statically narrowed
|
||||
inside the hot path.
|
||||
- Future broker tuning knobs (e.g. publish concurrency, slow-handler
|
||||
detection thresholds) should land here as additional optional
|
||||
fields, never as global module state.
|
||||
@@ -0,0 +1,113 @@
|
||||
# `testing` — `createInMemoryBrokerHarness`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module exposes a small, opinionated test harness around the
|
||||
in-memory broker. It removes the boilerplate of wiring an observer
|
||||
subscription and a teardown helper, so most tests collapse to "build
|
||||
harness, publish, assert on `received`, dispose". The harness deliberately
|
||||
shares the production broker — there is no separate "fake" broker hiding
|
||||
underneath — which means every assertion exercises the real fan-out
|
||||
pipeline and every regression caught in tests is one that would also
|
||||
manifest in production single-host deployments.
|
||||
|
||||
The harness lives under `./testing` (not the main entry) so consumer
|
||||
production bundles never pay for the helper machinery; only test bundles
|
||||
import this subpath.
|
||||
|
||||
## Features
|
||||
|
||||
- `createInMemoryBrokerHarness(opts?)` factory that returns a
|
||||
ready-to-use bundle of `{ broker, received, observerSubscription,
|
||||
publishOne, dispose }`.
|
||||
- A pre-registered observer subscription on the harness's default topic
|
||||
(`"test.events"`) plus an extensible `extraTopics` list so tests that
|
||||
probe the registry shape don't need to subscribe manually.
|
||||
- `publishOne(payload, opts?)` convenience that materialises a full
|
||||
`BrokerEnvelope` from a payload, defaulting `topic` and `tenant` from
|
||||
the harness configuration.
|
||||
- `dispose()` is idempotent and tolerant of partial teardown — it can
|
||||
be called from `afterEach` even if the test itself disposed the broker
|
||||
directly.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { createInMemoryBrokerHarness } from "@suaveplan/api-ws-broker-in-memory/testing";
|
||||
|
||||
describe("broker harness", () => {
|
||||
it("captures every published envelope", { timeout: 5000 }, async () => {
|
||||
const harness = createInMemoryBrokerHarness<{ id: number }>();
|
||||
try {
|
||||
await harness.publishOne({ id: 1 });
|
||||
await harness.publishOne({ id: 2 });
|
||||
expect(harness.received.map((e) => e.payload.id)).toEqual([1, 2]);
|
||||
} finally {
|
||||
harness.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import type { TenantId } from "@suaveplan/api-types/tenant";
|
||||
import { createInMemoryBrokerHarness } from "@suaveplan/api-ws-broker-in-memory/testing";
|
||||
|
||||
describe("broker harness — await-all + multi-topic", () => {
|
||||
it("delivers across additional topics", { timeout: 5000 }, async () => {
|
||||
const harness = createInMemoryBrokerHarness<string>({
|
||||
broker: { deliveryMode: "await-all" },
|
||||
topic: "orders",
|
||||
tenant: "t1" as unknown as TenantId,
|
||||
extraTopics: ["audit", "billing"],
|
||||
});
|
||||
try {
|
||||
await harness.publishOne("ok", { topic: "orders" });
|
||||
// The probe subscribers on audit/billing keep them
|
||||
// registered for `subscriberCount` introspection.
|
||||
expect(harness.broker.subscriberCount("orders")).toBe(1);
|
||||
expect(harness.broker.subscriberCount("audit")).toBe(1);
|
||||
expect(harness.received).toHaveLength(1);
|
||||
expect(harness.received[0]?.payload).toBe("ok");
|
||||
} finally {
|
||||
harness.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createInMemoryBrokerHarness<Payload>(opts?): InMemoryBrokerHarness<Payload>`
|
||||
- `interface CreateInMemoryBrokerHarnessOptions<Payload>` —
|
||||
`{ broker?, topic?, tenant?, extraTopics?, initialPayload? }`.
|
||||
- `interface InMemoryBrokerHarness<Payload>` —
|
||||
`{ broker, received, observerSubscription, publishOne, dispose }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The harness is deliberately tiny — it intentionally does NOT
|
||||
attempt to drive the `runWsEngineConformance` harness, because that
|
||||
one expects a full `(bus, registry, broker, makeConnection)` tuple
|
||||
while this package only owns the broker leg. The package's
|
||||
conformance test in `tests/conformance/` runs broker-shaped type
|
||||
assertions instead, which is the right scope for a broker-only
|
||||
package.
|
||||
- `extraTopics` register a no-op subscriber so the topic survives in
|
||||
the registry for introspection (`subscriberCount(topic) === 1`).
|
||||
Without a probe subscriber the in-memory broker reaps zero-size
|
||||
topic entries from its `Map` and `subscriberCount` would report 0,
|
||||
which is the correct production behaviour but inconvenient when a
|
||||
test wants to assert "this topic exists right now".
|
||||
- The harness wraps the observer unsubscribe and broker dispose in
|
||||
separate `try`/`catch` blocks so an out-of-order teardown (test
|
||||
disposes the broker, then `afterEach` calls `harness.dispose()`)
|
||||
does not surface stale `BROKER_DISPOSED` errors.
|
||||
Reference in New Issue
Block a user