95 lines
6.3 KiB
Markdown
95 lines
6.3 KiB
Markdown
# Add @suaveplan/broadcast-channel
|
|
|
|
**Package**: `@suaveplan/broadcast-channel`
|
|
**Status**: PROPOSED
|
|
**Type**: new-package
|
|
**Design**: ./design.md
|
|
**Spec delta**: ./specs/broadcast-channel/spec.md
|
|
|
|
## Why
|
|
|
|
Browser apps need a typed cross-tab messaging primitive built on the `BroadcastChannel` Web API:
|
|
multi-tab sync, leader election, tab presence and heartbeats. Upstream `../suaveplan/packages/browser/broadcast-channel/`
|
|
shipped at v1.0.5 with a clean public surface but four documented enterprise gaps (`enterprise/broadcast-channel.md`
|
|
§2 smells / §3 required improvements): no Zod boundary on inbound frames, no OTel observability, an untyped
|
|
`(event: any)` listener, and no fallback for browsers without `BroadcastChannel` (Safari < 15.4 and any
|
|
extension/test sandbox without the global). Genesis ships the migration at full parity plus those four fixes.
|
|
|
|
## What changes
|
|
|
|
- ADDED: `TypedBroadcastChannel` — typed cross-tab channel with leader election, tab presence, heartbeats, message-type handler registry, and `EventBus<BroadcastChannelEventMap>` lifecycle events.
|
|
- ADDED: `ChannelFactory` seam — caller injects native or polyfill `ChannelLike`.
|
|
- ADDED: `nativeChannelFactory` — wraps the global `BroadcastChannel`.
|
|
- ADDED: `createLocalStorageChannelFactory` — `StorageLike`-backed fallback for environments where `BroadcastChannel` is unavailable.
|
|
- ADDED: `ChannelMessageSchema` — Zod boundary validator; malformed inbound frames are dropped and surface `BROADCAST_CHANNEL.MESSAGE_MALFORMED`.
|
|
- ADDED: OTel instruments under meter `@suaveplan/broadcast-channel` — `channel.message.sent`, `channel.message.received`, `channel.message.dropped`, `channel.tab.joined`, `channel.tab.left`, `channel.leader.elected`.
|
|
- ADDED: Typed listener — `ChannelLike.addEventListener(type, listener: (event: MessageEvent) => void)`. Upstream `any` removed.
|
|
- ADDED: `BROADCAST_CHANNEL_CODES` registry — `CHANNEL_CLOSED`, `MESSAGE_MALFORMED`, `PAYLOAD_TOO_LARGE`, `STORAGE_FAILED`.
|
|
- ADDED: Payload-size guard — `maxMessageBytes` (default 1 MiB); over-budget `send()` throws `BroadcastChannelPayloadTooLargeError`.
|
|
- ADDED: Injectable `Clock` / `Rng` / `LoggerLike` for deterministic tests.
|
|
|
|
## Out of scope
|
|
|
|
- `ask()` request/response helper — 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.
|
|
- Leader-election strategies (round-robin, active-tab-wins) — upstream parity is lowest-tab-ID; alternative strategies are §4 feature-rich extensions not required by the four gaps.
|
|
- Message replay buffer, CRDT adapter, dev overlay, encryption hook, namespaced channels, presence hooks (`onTabFocus` / `onTabBlur`), compression — all §4 enhancements deferred to dedicated changes; each is a meaningful feature with its own design and test surface.
|
|
|
|
## Source mapping
|
|
|
|
This change derives from `../suaveplan/enterprise/broadcast-channel.md` and
|
|
`../suaveplan/packages/browser/broadcast-channel/src/`.
|
|
|
|
| Requirement | Source lines / symbol | Notes |
|
|
|---|---|---|
|
|
| Typed channel with leader election | enterprise.md L9-L11; `channel/channel.ts::TypedBroadcastChannel` | verbatim parity |
|
|
| Tab presence + heartbeats | enterprise.md L9-L11; `channel/channel.ts::startHeartbeat` | verbatim parity |
|
|
| Per-type handler registry | enterprise.md L9-L11; `channel/channel.ts::on` | verbatim parity |
|
|
| `ChannelFactory` injection seam | enterprise.md L11; `channel/channel.ts::ChannelFactory` | verbatim parity |
|
|
| `BroadcastChannelEventMap` lifecycle events | enterprise.md L11; `types/broadcast-channel.types.ts` | parity, adapted to genesis `EventBus<TMap>` (single-payload map) |
|
|
| `DEFAULT_CHANNEL_CONFIG` | `types/broadcast-channel.types.ts::DEFAULT_CHANNEL_CONFIG` | verbatim parity |
|
|
| Zod boundary on inbound frames | enterprise.md L31, L38, L56, L79 | gap fix |
|
|
| `localStorage` fallback transport | enterprise.md L29, L38, L51, L64-L65, L80, L96 | gap fix |
|
|
| Typed `MessageEvent` listener | enterprise.md L27, L39, L78 | gap fix |
|
|
| OTel counters for sent / received / tabs / leader | enterprise.md L43, L83 | gap fix |
|
|
| Payload-size guard | enterprise.md L33, L42, L57, L99 | gap fix, paired with `PAYLOAD_TOO_LARGE` code |
|
|
| `defineCodes("BROADCAST_CHANNEL", [...])` registry | enterprise.md L57 | extension into genesis error system |
|
|
| Injectable `Clock` / `Rng` / `LoggerLike` | enterprise.md L46-L58 (cross-cutting); CLAUDE.md rule 2 | genesis cross-cutting |
|
|
|
|
### Requirements with no source line
|
|
|
|
| Requirement | Justification |
|
|
|---|---|
|
|
| 100% Istanbul coverage threshold | Genesis-wide policy (`AGENTS.md` §7) |
|
|
| `@suaveplan/testing/runner` test imports | Genesis-wide rule 11 / CLAUDE.md |
|
|
| Bun catalog refs for external deps | Genesis-wide rule (`AGENTS.md` §1) |
|
|
|
|
### Excluded from this migration
|
|
|
|
| Upstream §4 enhancement | Reason |
|
|
|---|---|
|
|
| `ask()` request/response | Not in the four mandated gap fixes; deferred to a dedicated change with its own correlation-ID + timeout tests |
|
|
| Round-robin / active-tab-wins leader strategies | Upstream parity is lowest-tab-ID; alternative strategies are §4 enhancements |
|
|
| Message replay buffer (ring) | §4 enhancement; needs its own bounded-buffer design |
|
|
| CRDT adapter, encryption hook, compression, presence hooks, namespacing, dev overlay | §4 enhancements; each is a meaningful feature with its own design |
|
|
|
|
## Verification approach
|
|
|
|
- `src/channel/channel.test.ts` — `TypedBroadcastChannel` parity scenarios, leader election, heartbeat, stale cleanup, payload-size guard.
|
|
- `src/transport/native.test.ts` — `nativeChannelFactory` round-trip via real `BroadcastChannel` in happy-dom.
|
|
- `src/transport/local-storage.test.ts` — `createLocalStorageChannelFactory` round-trip via real `StorageLike` fallback.
|
|
- `src/schemas/messages.test.ts` — Zod boundary acceptance / rejection.
|
|
- `src/otel/metrics.test.ts` — every instrument asserted against real in-memory `MeterProvider`.
|
|
- `src/id/id.test.ts` — `generateId(rng)` determinism + format.
|
|
- `src/errors/codes.test.ts` — registry frozen-shape + every code thrown by ≥1 production path.
|
|
|
|
## Impact
|
|
|
|
### Affected dependents
|
|
|
|
No dependents on genesis yet (verified by `bunx turbo ls --filter=...@suaveplan/broadcast-channel` — package is new).
|
|
This is a leaf browser-tier package.
|
|
|
|
## Migration impact
|
|
|
|
None — additive, new package.
|