Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,77 @@
# envelope
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the `TransportEnvelope` wire format and provides `buildEnvelope` / `parseEnvelope` helpers for `@suaveplan/events-transport`. The envelope is the serialisable container that crosses context boundaries (worker threads, browser tabs). Every envelope carries a version tag, event name, structured-clone-safe payload, a dedup nonce, sender origin, timestamp, and optional W3C trace context headers.
The Zod schema validates all incoming envelopes at the receive boundary, throwing `EventsTransportError(EVENTS_TRANSPORT.ENVELOPE_INVALID)` for malformed data. This prevents processing corrupt or malicious messages from untrusted channels.
## Features
- `TransportEnvelope` — wire format type with v1 literal version tag
- `TransportEnvelopeSchema` — Zod schema for validation and coercion
- `buildEnvelope(event, payload, options)` — creates a typed envelope
- `parseEnvelope(raw)` — validates and returns `TransportEnvelope` or throws
## Usage: Basic Example
```ts
import { buildEnvelope, parseEnvelope } from "@suaveplan/events-transport";
const env = buildEnvelope("order.placed", { orderId: "o-1" }, {
nonce: crypto.randomUUID(),
origin: "tab-main",
ts: Date.now(),
});
const parsed = parseEnvelope(JSON.parse(JSON.stringify(env)));
console.log(parsed.event); // "order.placed"
```
## Usage: Advanced Example
```ts
import { buildEnvelope, parseEnvelope, TransportEnvelopeSchema } from "@suaveplan/events-transport";
// Build with trace context
const env = buildEnvelope("user.created", { id: "u-1" }, {
nonce: "abc-123",
origin: "worker-1",
ts: Date.now(),
traceContext: { traceparent: "00-abc...123-01" },
});
// Validate manually
const result = TransportEnvelopeSchema.safeParse(env);
if (!result.success) {
console.error("invalid envelope");
}
```
## API Reference
### `buildEnvelope(event, payload, options): TransportEnvelope`
| Param | Type | Description |
|---|---|---|
| `event` | `string` | Event name |
| `payload` | `unknown` | Structured-clone-safe payload |
| `options.nonce` | `string` | Unique dedup key |
| `options.origin` | `string` | Sender identifier |
| `options.ts` | `number` | Sender timestamp (ms) |
| `options.traceContext` | `Record<string,string>?` | W3C trace context headers |
### `parseEnvelope(raw): TransportEnvelope`
**Throws**: `EventsTransportError(EVENTS_TRANSPORT.ENVELOPE_INVALID)` if validation fails.
### `TransportEnvelopeSchema`
Zod schema for `TransportEnvelope`. Use directly when integrating with other Zod schemas.
## Implementation Notes
`payload: z.unknown()` in the schema means the payload is not validated structurally — that is left to the event schema validation layer (`@suaveplan/events-schema`). The envelope only validates the envelope _frame_ (version, nonce, origin, ts), not the payload content. This separation keeps the transport layer payload-agnostic.

View File

@@ -0,0 +1,66 @@
# nonce-window
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides `NonceWindow`, a sliding-window LRU set for deduplicating transport envelopes by nonce. Both `MessageChannelTransport` and `BroadcastChannelTransport` use it to silently drop duplicate envelopes that arrive within the dedup window, which prevents double-processing under at-least-once delivery (retries) or network-level message duplication.
The window size is configurable — larger windows catch duplicates over longer time horizons at the cost of memory; smaller windows are cheaper but may miss duplicates that arrive after the window has evicted them.
## Features
- `NonceWindow(windowSize?)` — LRU sliding window; default size 1000
- `check(nonce): boolean` — returns `true` (duplicate) or `false` (first-seen); records the nonce if not a duplicate
- `size` getter — current number of tracked nonces
- `clear()` — resets the window
## Usage: Basic Example
```ts
import { NonceWindow } from "@suaveplan/events-transport";
const window = new NonceWindow(100);
window.check("nonce-1"); // false (new)
window.check("nonce-1"); // true (duplicate)
window.check("nonce-2"); // false (new)
```
## Usage: Advanced Example
```ts
import { NonceWindow } from "@suaveplan/events-transport";
// Fill to capacity and verify LRU eviction
const window = new NonceWindow(2);
window.check("a"); // false
window.check("b"); // false
window.check("c"); // false — evicts "a"
window.check("a"); // false — "a" was evicted, treated as new
window.check("b"); // true — "b" still in window
```
## API Reference
### `new NonceWindow(windowSize?: number)`
| Param | Type | Description |
|---|---|---|
| `windowSize` | `number?` | Max nonces to track (default: 1000) |
### `check(nonce: string): boolean`
Records `nonce` if not seen. Returns `true` if already in the window (duplicate), `false` if new.
### `size: number`
Current count of tracked nonces.
### `clear(): void`
Empties the window; all subsequent nonces will be treated as new.
## Implementation Notes
The implementation uses a `string[]` ring-buffer and a `Set<string>` for O(1) lookup. On eviction, the oldest entry is `shift()`ed from the array and deleted from the set. This gives O(1) amortized `check` for the common case. A more memory-efficient LRU cache could use a doubly-linked list + Map for O(1) worst-case, but for window sizes of 1000 the array approach is faster in practice due to cache locality.

View File

@@ -0,0 +1,79 @@
# transport-interface
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the core `EventTransport` interface and supporting types (`ConnectOptions`, `TransportHandle`, `TransportStats`, `DeliveryGuarantee`) that all transport implementations in `@suaveplan/events-transport` must satisfy. This interface layer is pure types — no runtime code — enabling consumers to depend on it for typing without pulling in any implementation.
The design follows a connect-and-disconnect pattern: `transport.connect(bus, options?)` returns a `TransportHandle` with `disconnect()` and a `stats` snapshot. The `deliveryGuarantee` property documents the transport's contract so callers can make informed decisions about idempotency and retry at the application layer.
## Features
- `EventTransport` interface with `name`, `deliveryGuarantee`, and `connect`
- `TransportHandle` with `disconnect()` and `stats` snapshot
- `TransportStats` with `sent`, `received`, `dropped`, `duplicatesFiltered` counters
- `DeliveryGuarantee` — `"at-most-once"` or `"at-least-once"`
- `ConnectOptions` — `eventFilter`, `signal`, `logger`, `clock`, `rng`, `idGenerator`
## Usage: Basic Example
```ts
import type { EventTransport, TransportHandle } from "@suaveplan/events-transport";
function withTransport(bus: EventBus, transport: EventTransport): () => void {
const handle: TransportHandle = transport.connect(bus);
console.log("guarantee:", transport.deliveryGuarantee);
return () => handle.disconnect();
}
```
## Usage: Advanced Example
```ts
import type { EventTransport, ConnectOptions } from "@suaveplan/events-transport";
import { EventBus } from "@suaveplan/events";
const bus = new EventBus();
const transport: EventTransport = createMessageChannelTransport({ channel: new MessageChannel() });
const handle = transport.connect(bus, {
eventFilter: (name) => name.startsWith("order."),
signal: AbortSignal.timeout(60_000),
});
console.log(handle.stats.sent); // 0 initially
console.log(handle.stats.received); // 0 initially
handle.disconnect();
```
## API Reference
### `EventTransport`
| Member | Type | Description |
|---|---|---|
| `name` | `string` | Transport identifier |
| `deliveryGuarantee` | `DeliveryGuarantee` | Delivery contract |
| `connect(bus, options?)` | `TransportHandle` | Attach transport to the bus |
### `TransportHandle`
| Member | Type | Description |
|---|---|---|
| `disconnect()` | `void` | Detach from the bus |
| `stats` | `TransportStats` | Live statistics |
### `TransportStats`
| Field | Type | Description |
|---|---|---|
| `sent` | `number` | Envelopes sent |
| `received` | `number` | Envelopes received |
| `dropped` | `number` | Envelopes dropped (non-cloneable, etc.) |
| `duplicatesFiltered` | `number` | Duplicate nonces filtered |
## Implementation Notes
The `EventTransport.connect` method accepts an `EventBus<EventMap>` (the widest type) rather than a specific generic type. This avoids TypeScript covariance issues between different `EventBus<TMap>` types at the cost of losing per-event-type safety at the transport boundary. Type safety is enforced by the event bus itself when re-dispatching received payloads.

View File

@@ -0,0 +1,124 @@
# broadcast-channel-transport
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements an `EventTransport` over the browser `BroadcastChannel` API, enabling
**at-most-once** event fan-out to every browser context (tab, worker, iframe) that has opened
a `BroadcastChannel` with the same channel name. Unlike the `MessageChannel` transport, no
acknowledgement is exchanged — delivery is fire-and-forget. This makes it ideal for cache
invalidation, UI refresh signals, and other scenarios where occasional loss is acceptable and
simplicity is preferred over reliability.
The transport filters out messages it sent itself (`origin === "local"`) to prevent event
loops, and uses a `NonceWindow` ring buffer to deduplicate retransmissions from other
transports bridged to the same bus.
## Features
- At-most-once delivery — no ack/retry overhead
- Same-origin loop prevention via `origin` tag in envelope
- Nonce-based duplicate filtering (configurable sliding-window)
- Payload safety check — non-structured-cloneable values are dropped rather than throwing
- AbortSignal support for clean shutdown
- Injectable clock, ID generator, and event filter via `ConnectOptions`
- Per-transport statistics: `sent`, `received`, `dropped`, `duplicatesFiltered`
## Usage: Basic Example
```ts
import { createBroadcastChannelTransport } from "@suaveplan/events-transport/transports/broadcast-channel/broadcast-channel-transport";
import { EventBus } from "@suaveplan/events";
const channel = new BroadcastChannel("app-events");
const bus = new EventBus<{ "cache.invalidated": { key: string } }>();
const transport = createBroadcastChannelTransport({ channel });
const handle = transport.connect(bus);
bus.on("cache.invalidated", (p) => console.log("invalidate", p.key));
await bus.emitAsync("cache.invalidated", { key: "user:42" });
// Event is broadcast to all other contexts on the same channel name
handle.disconnect();
channel.close();
```
## Usage: Advanced Example
```ts
import { createBroadcastChannelTransport } from "@suaveplan/events-transport/transports/broadcast-channel/broadcast-channel-transport";
import { EventBus } from "@suaveplan/events";
const channel = new BroadcastChannel("notifications");
const bus = new EventBus<{ "notification.show": { msg: string } }>();
const ac = new AbortController();
const transport = createBroadcastChannelTransport({
channel,
nonceWindowSize: 200,
});
const handle = transport.connect(bus, {
signal: ac.signal,
eventFilter: (name) => name.startsWith("notification."),
clock: { now: () => performance.now() },
idGenerator: { generate: () => crypto.randomUUID() },
});
bus.on("notification.show", (p) => console.log(p.msg));
await bus.emitAsync("notification.show", { msg: "Hello from another tab!" });
console.log(handle.stats.sent); // 1
ac.abort();
handle.disconnect();
channel.close();
```
## API Reference
### `createBroadcastChannelTransport(options): EventTransport`
| Param | Type | Description |
|---|---|---|
| `options.channel` | `BroadcastChannel` | The broadcast channel to use |
| `options.nonceWindowSize` | `number?` | Dedup window size. Default: `1000` |
**Returns**: `EventTransport` with `deliveryGuarantee: "at-most-once"`
### `EventTransport.connect(bus, options?): TransportHandle`
Connects the transport to an `EventBus`. Installs a `bus.use()` interceptor to capture
outbound dispatches and a `channel.onmessage` listener for inbound envelopes.
| Param | Type | Description |
|---|---|---|
| `bus` | `EventBus` | The bus to bridge |
| `options.eventFilter` | `(name: string) => boolean` | Filter events. Default: all events |
| `options.signal` | `AbortSignal?` | Cancellation |
| `options.clock` | `Clock?` | Injectable clock |
| `options.idGenerator` | `IdGenerator?` | Injectable nonce generator |
### `TransportHandle`
| Member | Type | Description |
|---|---|---|
| `disconnect()` | `() => void` | Uninstall interceptor and listener |
| `stats` | `TransportStats` | Snapshot of sent/received/dropped/duplicatesFiltered counts |
## Implementation Notes
Because `BroadcastChannel` delivers messages to every context **except** the sender, this
transport does not need to track pending acks. However, the `origin: "local"` tag is still
set on outbound envelopes so that if two tabs have the same logical bus connected via the
same `BroadcastChannel`, a message sent from tab A will not be re-dispatched by tab A's
own `onMessage` listener (which would cause a local loop if the same channel name were
somehow looped back).
The dedup window guards against scenarios where multiple transports are bridged together —
for example, a `MessageChannel` transport forwarding into a `BroadcastChannel` transport —
where the same nonce could arrive from multiple paths.

View File

@@ -0,0 +1,127 @@
# message-channel-transport
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements an `EventTransport` over the browser and Node.js `MessageChannel` API, providing
bidirectional, structured-cloneable event forwarding between two `MessagePort` endpoints with
**at-least-once delivery** semantics. Every outbound event envelope is posted to `port1` and
the sender waits for an explicit acknowledgement from the receiver on `port2` before recording
the send as successful. If the ack does not arrive within the configured timeout, the message
is retried up to `maxRetries` times; unacked messages are tracked as dropped in the transport
statistics.
## Features
- At-least-once delivery via ack/retry protocol
- Nonce-based duplicate filtering on the receive side (configurable sliding-window size)
- Payload safety check — non-structured-cloneable values are dropped rather than throwing
- Per-transport statistics: `sent`, `received`, `dropped`, `duplicatesFiltered`
- AbortSignal support: stops outbound sends and inbound processing after abort
- Injectable clock, ID generator, and event filter via `ConnectOptions`
- Clean disconnect with pending ack cleanup
## Usage: Basic Example
```ts
import { createMessageChannelTransport } from "@suaveplan/events-transport/transports/message-channel/message-channel-transport";
import { EventBus } from "@suaveplan/events";
const channel = new MessageChannel();
const bus = new EventBus<{ "user.created": { id: string } }>();
const transport = createMessageChannelTransport({ channel });
const handle = transport.connect(bus);
bus.on("user.created", (p) => console.log("received", p.id));
await bus.emitAsync("user.created", { id: "u-1" });
console.log(handle.stats.sent); // 1
handle.disconnect();
```
## Usage: Advanced Example
```ts
import { createMessageChannelTransport } from "@suaveplan/events-transport/transports/message-channel/message-channel-transport";
import { EventBus } from "@suaveplan/events";
const channel = new MessageChannel();
const busA = new EventBus<{ ping: { seq: number } }>();
const busB = new EventBus<{ ping: { seq: number } }>();
const ac = new AbortController();
// Wire busA's port1 to busB's port2 via a shared channel
const transportA = createMessageChannelTransport({
channel,
ackTimeoutMs: 2000,
maxRetries: 5,
nonceWindowSize: 500,
});
const handleA = transportA.connect(busA, {
signal: ac.signal,
eventFilter: (name) => name.startsWith("ping"),
});
// busB receives events
busB.on("ping", (p) => console.log("busB got ping", p.seq));
// busA emits, arrives on busB
await busA.emitAsync("ping", { seq: 1 });
console.log(handleA.stats); // { sent: 1, received: 0, dropped: 0, duplicatesFiltered: 0 }
ac.abort();
handleA.disconnect();
```
## API Reference
### `createMessageChannelTransport(options): EventTransport`
| Param | Type | Description |
|---|---|---|
| `options.channel` | `MessageChannel` | The channel to communicate over |
| `options.ackTimeoutMs` | `number?` | Ack wait timeout in ms. Default: `5000` |
| `options.maxRetries` | `number?` | Max retries before drop. Default: `3` |
| `options.nonceWindowSize` | `number?` | Dedup window size. Default: `1000` |
**Returns**: `EventTransport` with `deliveryGuarantee: "at-least-once"`
### `EventTransport.connect(bus, options?): TransportHandle`
Connects the transport to an `EventBus`. Installs a `bus.use()` interceptor to capture outbound
dispatches and a `port2` message listener to receive inbound envelopes.
| Param | Type | Description |
|---|---|---|
| `bus` | `EventBus` | The bus to bridge |
| `options.eventFilter` | `(name: string) => boolean` | Filter events. Default: all events |
| `options.signal` | `AbortSignal?` | Cancellation |
| `options.clock` | `Clock?` | Injectable clock |
| `options.idGenerator` | `IdGenerator?` | Injectable nonce generator |
### `TransportHandle`
| Member | Type | Description |
|---|---|---|
| `disconnect()` | `() => void` | Uninstall interceptor, stop listener, clear pending acks |
| `stats` | `TransportStats` | Snapshot of sent/received/dropped/duplicatesFiltered counts |
## Implementation Notes
The ack protocol uses a `Map<string, () => void>` of pending ack resolvers keyed by nonce.
When `port1.postMessage(envelope)` is sent, a `Promise<boolean>` is created that resolves
`true` when the matching `{ ack: nonce }` reply arrives on `port2`, or `false` when the
timeout fires. The retry loop retries the full postMessage on each `false` result.
Non-structured-cloneable payloads are silently dropped (incrementing `stats.dropped`) rather
than throwing because the interceptor runs after `next()` — the local dispatch has already
succeeded. Throwing here would be misleading since the event was processed locally.
Duplicate filtering on receive uses a `NonceWindow` ring buffer so the sender can safely retry
without the receiver processing the same event twice.

View File

@@ -0,0 +1,64 @@
# structured-clone-check
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides `isStructuredCloneable` and `assertStructuredCloneable` utilities for `@suaveplan/events-transport`. These functions validate that an event payload can be safely transmitted across context boundaries via the structured clone algorithm used by `MessageChannel` and `BroadcastChannel`. Payloads that fail the check are dropped before transmission to prevent runtime `DataCloneError` exceptions.
The check is conservative — it allows all primitives, plain objects, arrays, and well-known cloneable builtins (Date, RegExp, Map, Set, ArrayBuffer, TypedArrays, Error), and rejects functions, symbols, WeakMap, WeakSet, and class instances with custom prototypes.
## Features
- `isStructuredCloneable(value)` — recursive check for clone-safety
- `assertStructuredCloneable(value)` — throws `EventsTransportError(NOT_CLONEABLE)` on failure
- Handles nested objects and arrays
- Allows Date, RegExp, Map, Set, ArrayBuffer, TypedArray, Error
## Usage: Basic Example
```ts
import { isStructuredCloneable } from "@suaveplan/events-transport";
isStructuredCloneable({ id: "x", count: 1 }); // true
isStructuredCloneable(() => console.log("fn")); // false
isStructuredCloneable(Symbol("sym")); // false
isStructuredCloneable(new Date()); // true
isStructuredCloneable(new Map([["k", "v"]])); // true
```
## Usage: Advanced Example
```ts
import { assertStructuredCloneable, isStructuredCloneable } from "@suaveplan/events-transport";
import { EventsTransportError } from "@suaveplan/events-transport";
// Assert before transmission
try {
assertStructuredCloneable({ fn: () => {} });
} catch (err) {
if (err instanceof EventsTransportError) {
console.log(err.code); // "EVENTS_TRANSPORT.NOT_CLONEABLE"
}
}
// Safe payloads
isStructuredCloneable(new Uint8Array([1, 2, 3])); // true (TypedArray)
isStructuredCloneable(new Set([1, 2, 3])); // true (Set)
isStructuredCloneable(new WeakMap()); // false
```
## API Reference
### `isStructuredCloneable(value: unknown): boolean`
Returns `true` if the value can be transmitted via structured clone.
### `assertStructuredCloneable(value: unknown): void`
**Throws**: `EventsTransportError(EVENTS_TRANSPORT.NOT_CLONEABLE)` if value is not cloneable.
## Implementation Notes
The check uses `instanceof` for known builtins and `Object.getPrototypeOf` to detect class instances. It is recursive for arrays and plain objects. Custom class instances (those with a non-Object, non-null prototype that are not whitelisted) are rejected as non-cloneable. This is a conservative heuristic — some custom classes may be cloneable if they implement the `[Symbol.for("nodejs.util.inspect.custom")]` or structuredClone algorithm, but the safe default is to reject them. The actual `structuredClone()` call is not used for the check to avoid the cost of deep-cloning on every dispatch.