Initial Commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# Add @suaveplan/broadcast-channel `ask()` request/response
|
||||
|
||||
**Package**: `@suaveplan/broadcast-channel`
|
||||
**Status**: PROPOSED
|
||||
**Type**: capability-extension
|
||||
**Spec delta**: ./specs/broadcast-channel/spec.md
|
||||
|
||||
## Why
|
||||
|
||||
The initial `@suaveplan/broadcast-channel` ship (`openspec/archive/2026-05-19-add-broadcast-channel/`)
|
||||
explicitly deferred the `ask()` request/response helper under "Out of scope":
|
||||
|
||||
> `ask()` request/response — not in the four mandated gap fixes; consumers can layer correlation-ID
|
||||
> on top of `send`/`on`. Will land in a follow-up change with `BROADCAST_CHANNEL.ASK_TIMEOUT` code.
|
||||
|
||||
Consumers (cross-tab leader queries, per-tab capability probes, "who-has-feature-X?" fan-outs)
|
||||
currently have to re-implement correlation-ID + timeout plumbing on top of `send` / `on`. That is
|
||||
exactly the duplication the package exists to eliminate, and it is the one §4 enhancement that is
|
||||
small, self-contained, and trivially testable with deterministic injectables. This change ships
|
||||
ONLY `ask()` + `onAsk()`. Every other §4 item (leader-election strategies, replay buffer, CRDT,
|
||||
encryption, compression, namespacing, dev overlay, presence hooks) remains deferred to its own
|
||||
dedicated change.
|
||||
|
||||
## What changes
|
||||
|
||||
- ADDED: `TypedBroadcastChannel.ask<TReq, TResp>(handlerKey, payload, opts?) → Promise<TResp>` with
|
||||
injectable timeout, correlation IDs, and optional `requireResponder` guard.
|
||||
- ADDED: `TypedBroadcastChannel.onAsk(handlerKey, handler)` — single-handler-per-key registration.
|
||||
- ADDED: Two new wire-frame variants — `ask` (carrying `correlationId`, `handlerKey`, `payload`,
|
||||
`expiresAt`) and `ask-response` (carrying `responseTo`, `handlerKey`, `outcome: "ok" | "error"`,
|
||||
`payload`).
|
||||
- ADDED: Frame schema extended via discriminated union — `ChannelMessageSchema` is widened, not
|
||||
replaced; all existing `send` / `on` / heartbeat paths remain bit-compatible.
|
||||
- ADDED: Three new error codes — `BROADCAST_CHANNEL.ASK_TIMEOUT`,
|
||||
`BROADCAST_CHANNEL.ASK_NO_RESPONDER`, `BROADCAST_CHANNEL.ASK_HANDLER_DUPLICATE` — each thrown by
|
||||
≥1 production path.
|
||||
- ADDED: Three new OTel counters — `channel.ask.sent{key}`, `channel.ask.responded{key,outcome}`,
|
||||
`channel.ask.timeout{key}` — under the existing `@suaveplan/broadcast-channel` meter.
|
||||
- ADDED: Injectable `TimersLike` on `TypedBroadcastChannelOptions` (default backed by global
|
||||
`setTimeout` / `clearTimeout`) so timeout tests run deterministically against a FakeClock-driven
|
||||
timer pair.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Multi-responder fan-out collection (`askMany()` / `askAll()`).
|
||||
- Streaming / progressive responses (responses are one-shot per `correlationId`).
|
||||
- Leader-election strategies (round-robin, active-tab-wins).
|
||||
- Replay buffer, CRDT adapter, encryption hook, compression, namespacing, dev overlay,
|
||||
`onTabFocus` / `onTabBlur` hooks.
|
||||
|
||||
## Source mapping
|
||||
|
||||
This change derives from `../suaveplan/enterprise/broadcast-channel.md` §4.1 and the initial
|
||||
ship's "Out of scope" deferral list.
|
||||
|
||||
| Requirement | Source lines / symbol | Notes |
|
||||
|---|---|---|
|
||||
| `ask()` request/response | enterprise.md §4.1 ("Request/response (`ask`) primitive with correlation ID, timeout, and typed response.") | §4 enhancement #1, deferred from initial ship |
|
||||
| `ASK_TIMEOUT` code | initial proposal "Out of scope" line referencing `BROADCAST_CHANNEL.ASK_TIMEOUT` | explicit follow-up |
|
||||
| Single-handler `onAsk` registration | genesis design choice — multi-responder is its own future change | genesis-original |
|
||||
| Three OTel counters | genesis cross-cutting (rule 6 of CLAUDE.md — every hot path gets a counter) | genesis-original |
|
||||
| Injectable `TimersLike` | `@suaveplan/types::TimersLike`; CLAUDE.md rule 2 (no real timers in tests) | genesis cross-cutting |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| `requireResponder` config flag → `ASK_NO_RESPONDER` | Genesis safety knob: callers that *must* have a peer answer (e.g. leader-only queries) need a distinct error code from a generic timeout to drive retry policy. |
|
||||
| 100% Istanbul coverage on touched modules | Genesis-wide policy (`AGENTS.md` §7) |
|
||||
| `@suaveplan/testing/runner` test imports | Genesis-wide rule 11 / CLAUDE.md |
|
||||
|
||||
## Verification approach
|
||||
|
||||
- `src/ask/ask.test.ts` — `ask()` happy path round-trip with two `TypedBroadcastChannel`s sharing a
|
||||
fake transport pair; correlation-ID isolation under concurrent calls; timeout via FakeClock-driven
|
||||
`TimersLike`; `requireResponder` flag toggling `ASK_NO_RESPONDER` vs. `ASK_TIMEOUT`; handler-thrown
|
||||
errors propagate as `ask-response` `outcome: "error"` and reject the caller; cleanup on
|
||||
`close()` rejects all pending asks.
|
||||
- `src/ask/responder.test.ts` — `onAsk` registration; duplicate registration throws
|
||||
`ASK_HANDLER_DUPLICATE`; unregister via returned disposer; non-matching `ask` frames are dropped
|
||||
silently.
|
||||
- `src/schemas/messages.test.ts` — widened union accepts both legacy frames and new `ask` /
|
||||
`ask-response` variants; malformed `ask` (missing `correlationId`) rejected.
|
||||
- `src/otel/metrics.test.ts` — three new counter names observed via in-memory `MeterProvider`.
|
||||
- `src/errors/codes.test.ts` — registry size + 7-code shape; every new code thrown by a real path.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
Run `bunx turbo ls --filter=...@suaveplan/broadcast-channel` to enumerate. The package is browser-tier
|
||||
and has no internal dependents on genesis at the time of this change.
|
||||
|
||||
- No dependents (verified by `bunx turbo ls --filter=...@suaveplan/broadcast-channel` — package is
|
||||
a leaf browser-tier addition that landed 2026-05-19; nothing else has wired against it yet).
|
||||
|
||||
## Migration impact
|
||||
|
||||
None — additive. Existing `send` / `on` / leader / heartbeat call sites are byte-compatible: the
|
||||
frame schema widens via discriminated union, the `BROADCAST_CHANNEL_CODES` registry grows by three
|
||||
codes (existing codes unchanged), and the OTel meter gains three counters (existing six unchanged).
|
||||
No public types are renamed or removed.
|
||||
Reference in New Issue
Block a user