# Capability Spec Delta: api-ws-broker-in-memory (api-ws-broker-in-memory) **Package**: `@suaveplan/api-ws-broker-in-memory` **Status**: PROPOSED ## ADDED Requirements ### Requirement: Fan-out to every subscriber on publish The broker SHALL deliver `publish(topic, message)` to every connection currently subscribed to `topic`. Delivery order across subscribers is unspecified; per-subscriber message order is FIFO. #### Scenario: every subscriber receives every message - **WHEN** three connections subscribe to `"orders.created"` AND two messages are published - **THEN** each connection receives both messages in publish order **Verified by:** `src/factory/create-in-memory-ws-broker.test.ts::"every subscriber receives every published message in FIFO order"` --- ### Requirement: Subscription lifecycle The broker SHALL expose `subscribe(connectionId, topic)`, `unsubscribe(connectionId, topic)`, and `cleanupConnection(connectionId)`. `cleanupConnection` removes the connection from every topic it was subscribed to. #### Scenario: unsubscribe stops delivery - **WHEN** a connection subscribes, then unsubscribes, then a message is published - **THEN** the connection does NOT receive the message #### Scenario: cleanupConnection removes from every topic - **WHEN** a connection is subscribed to `["a", "b", "c"]` AND `cleanupConnection(id)` is called - **THEN** `subscribersOf("a")`, `subscribersOf("b")`, `subscribersOf("c")` no longer include `id` #### Scenario: cleanup is idempotent - **WHEN** `cleanupConnection(id)` is called twice - **THEN** the second call is a no-op AND no error is thrown **Verified by:** `src/factory/create-in-memory-ws-broker.test.ts::"unsubscribe stops delivery"` AND `src/factory/create-in-memory-ws-broker.test.ts::"cleanupConnection removes from every topic"` AND `src/factory/create-in-memory-ws-broker.test.ts::"cleanupConnection is idempotent"` --- ### Requirement: maxSubscribersPerTopic enforced when set When `options.maxSubscribersPerTopic` is set AND a new `subscribe` would exceed it, the broker SHALL throw `WS_BROKER_IN_MEMORY_MAX_SUBSCRIBERS_EXCEEDED`. #### Scenario: subscribe rejected at cap - **WHEN** `maxSubscribersPerTopic: 2` AND two connections subscribe AND a third tries to subscribe - **THEN** the third call throws `WS_BROKER_IN_MEMORY_MAX_SUBSCRIBERS_EXCEEDED` **Verified by:** `src/factory/create-in-memory-ws-broker.test.ts::"subscribe throws when maxSubscribersPerTopic is exceeded"` --- ### Requirement: Delivery mode semantics `deliveryMode: "await-all"` SHALL await every subscriber's handler before `publish` resolves. `"fire-and-forget"` SHALL return immediately after dispatch. #### Scenario: await-all awaits every handler - **WHEN** `deliveryMode: "await-all"` AND two subscribers each return a 50ms-delayed promise - **THEN** `publish` resolves only after both promises settle (measurable timing) #### Scenario: fire-and-forget returns synchronously - **WHEN** `deliveryMode: "fire-and-forget"` - **THEN** `publish` resolves before any subscriber's delayed promise has settled **Verified by:** `src/delivery/delivery-mode.test.ts::"await-all blocks publish until every handler settles"` AND `src/delivery/delivery-mode.test.ts::"fire-and-forget resolves publish synchronously"` --- ### Requirement: Conforms to WsBroker contract #### Scenario: conformance suite passes - **WHEN** `runWsBrokerConformance("in-memory-direct", () => createInMemoryWsBroker(...))` runs - **THEN** every invariant passes **Verified by:** `src/factory/create-in-memory-ws-broker.spec-conformance.test.ts::"in-memory-direct conforms to WsBroker contract"` --- ### Requirement: Registered in `api-host/testing` adapter table #### Scenario: createTestHost resolves the sentinel - **WHEN** `createTestHost({ adapters: { "ws.broker": "in-memory" } })` - **THEN** the resulting `host.ws.broker` is an instance produced by this factory **Verified by:** `tests/integration/integration.test.ts::"createTestHost resolves ws.broker:\"in-memory\" to this adapter"`