104 lines
4.4 KiB
Markdown
104 lines
4.4 KiB
Markdown
# Design — add-broadcast-channel
|
|
|
|
## Module layout
|
|
|
|
```text
|
|
packages/events/broadcast-channel/
|
|
src/
|
|
channel/
|
|
channel.ts # TypedBroadcastChannel core (295 lines, under 300 budget)
|
|
heartbeat.ts # HeartbeatRunner helper extracted to fit budget
|
|
channel.test.ts
|
|
heartbeat.test.ts
|
|
channel.md
|
|
heartbeat.md
|
|
index.ts
|
|
errors/
|
|
codes.ts # defineCodes("BROADCAST_CHANNEL", [...]) + 4 subclasses
|
|
codes.test.ts
|
|
codes.md
|
|
index.ts
|
|
id/
|
|
id.ts # generateId(rng) + defaultRng
|
|
id.test.ts
|
|
id.md
|
|
index.ts
|
|
otel/
|
|
metrics.ts # 6 counters + refreshBroadcastChannelMeters
|
|
metrics.test.ts
|
|
metrics.md
|
|
index.ts
|
|
schemas/
|
|
messages.ts # ChannelMessageSchema + ChannelConfigSchema (Zod)
|
|
messages.test.ts
|
|
messages.md
|
|
index.ts
|
|
transport/
|
|
native.ts # nativeChannelFactory + isNativeBroadcastChannelAvailable
|
|
local-storage.ts # StorageLike-backed fallback
|
|
transport.types.ts # ChannelLike (typed MessageEvent) + ChannelFactory
|
|
native.test.ts
|
|
local-storage.test.ts
|
|
native.md
|
|
local-storage.md
|
|
index.ts
|
|
types/
|
|
broadcast-channel.types.ts
|
|
index.ts
|
|
index.ts # named re-exports only
|
|
tests/setup/polyfills.ts # in-memory MeterProvider for OTel tests
|
|
README.md
|
|
package.json
|
|
vite.config.ts
|
|
vitest.config.ts
|
|
tsconfig.json
|
|
tsconfig.lib.json
|
|
```
|
|
|
|
## Key design choices
|
|
|
|
- **Single-payload event map**: genesis `EventBus<TMap>` uses
|
|
`Record<string, unknown>` (one payload per emit). Upstream used a tuple-args
|
|
shape (`EventMap = Record<string, unknown[]>`). The migrated event map
|
|
carries structured records (`ChannelOpenedEvent`, `TabJoinedEvent`, …) so
|
|
every callback sees one object with named fields. Net public-surface impact:
|
|
consumers register `on("tab:joined", e => e.tabId)` instead of
|
|
`on("tab:joined", tabId => ...)`. This is documented in `proposal.md` →
|
|
`BroadcastChannelEventMap`.
|
|
- **`ChannelLike` listener fixed to `MessageEvent`**: upstream had
|
|
`(event: any) => void` — the migration narrows to `(event: MessageEvent) => void`
|
|
on the `ChannelLike` interface and propagates the change through both
|
|
transports. This is the genesis-mandated gap fix.
|
|
- **`StorageLike` rather than raw `window.localStorage`**: the fallback
|
|
transport takes any `StorageLike` from `@suaveplan/types`. This generalises
|
|
the polyfill beyond Safari — the same factory works on top of OPFS,
|
|
IndexedDB, Garage S3, or an in-memory test double — and removes the
|
|
package's dependency on a specific browser global.
|
|
- **Storage failures logged, not thrown**: the polling / write paths fire on
|
|
intervals; an unhandled rejection would crash the host. Failures wrap into
|
|
`BroadcastChannelStorageFailedError`, surface through the injected
|
|
`LoggerLike.warn`, and the loop continues. The code is still reachable from
|
|
tests because the wrapping happens synchronously after the rejection.
|
|
- **Six counters, no histograms**: every lifecycle path emits a counter. No
|
|
meaningful latency axis exists (dispatch is in-process, `send` is sync) so a
|
|
histogram would be empty noise.
|
|
- **Payload guard via JSON byte length**: the canonical structured-clone size
|
|
of a frame is unknowable without serialising. JSON is the only encoding the
|
|
underlying transports universally support, so the guard runs the JSON
|
|
encoder once, measures via `TextEncoder.encode`, and rejects above
|
|
`maxMessageBytes`. The same encoded buffer is not reused for the native
|
|
transport (which clones internally) but the cost is one `JSON.stringify`
|
|
per `send` — well under any meaningful budget.
|
|
|
|
## Out-of-scope features and why
|
|
|
|
The upstream §4 list ("Feature-rich enhancements") catalogues ten further
|
|
ideas. The migration explicitly defers nine of them — see
|
|
`### Excluded from this migration` in `proposal.md`. The genesis rule "No
|
|
deferrals when migrating/creating packages" applies to **legitimate parity**
|
|
features that were in upstream's shipping surface; §4 enhancements are
|
|
documented as future work in the audit, not as upstream-shipped surface, and
|
|
each carries its own design surface and test contract that does not fit a
|
|
4-file migration. Future changes (`add-broadcast-channel-ask`,
|
|
`add-broadcast-channel-leader-strategies`, etc.) will land them individually.
|