Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,242 @@
# OpenSpec Change: enterprise-events-transport
- **Change ID**: enterprise-events-transport
- **Package**: `@suaveplan/events-transport`
- **Category**: universal
- **Target path**: `universal/events/events-transport`
- **Status**: PROPOSED
- **Date**: 2026-04-24
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/events-transport.md`
---
## Summary
Deliver `@suaveplan/events-transport` on the genesis branch from scratch at enterprise quality. The package provides cross-context event bridges for `@suaveplan/events`: bridges dispatches across `MessageChannel` (worker ↔ main thread) and `BroadcastChannel` (tab ↔ tab), with structured-clone-safe payload validation, W3C trace context propagation in envelopes, documented delivery guarantees per transport, idempotency via nonce dedup windows, and `AbortSignal` cancellation. Attaches to the event bus via the public interceptor API.
---
## Current-state audit (genesis)
The package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data.
---
## Available workspace deps on genesis
| Package | Role |
|---|---|
| `@suaveplan/events` (`workspace:^`) | Event bus and interceptor API |
| `@suaveplan/error` (`workspace:^`) | Base class for `EventsTransportError`; `defineCodes` for `EVENTS_TRANSPORT.*` codes |
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `Rng`, `IdGenerator` injection interfaces |
| `zod` (`catalog:forms`) | Zod schemas for transport envelope and option types |
| `@opentelemetry/api` (`catalog:observability`) | Cross-boundary span linking, context propagation |
| `tslib` (`catalog:build`) | Runtime helpers |
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`).
---
## Public API surface
### Transport interface
```ts
interface EventTransport {
readonly name: string
readonly deliveryGuarantee: DeliveryGuarantee
connect(bus: EventBusLike, options?: ConnectOptions): TransportHandle
}
type DeliveryGuarantee = "at-most-once" | "at-least-once"
interface ConnectOptions {
eventFilter?: (name: string) => boolean
signal?: AbortSignal
logger?: LoggerLike
clock?: Clock
rng?: Rng
idGenerator?: IdGenerator
}
interface TransportHandle {
disconnect(): void
readonly stats: TransportStats
}
interface TransportStats {
sent: number
received: number
dropped: number
duplicatesFiltered: number
}
```
### MessageChannel transport
```ts
// at-least-once delivery with ack protocol
function createMessageChannelTransport(options: MessageChannelTransportOptions): EventTransport
interface MessageChannelTransportOptions {
channel: MessageChannel
ackTimeoutMs?: number // default: 5000
maxRetries?: number // default: 3
nonceWindowSize?: number // dedup window for received nonces; default: 1000
}
```
### BroadcastChannel transport
```ts
// at-most-once delivery (no ack)
function createBroadcastChannelTransport(options: BroadcastChannelTransportOptions): EventTransport
interface BroadcastChannelTransportOptions {
channelName: string
nonceWindowSize?: number // dedup window for received nonces; default: 1000
origin?: string // filter by origin; default: "*"
}
```
### Transport envelope (wire format)
```ts
interface TransportEnvelope {
v: 1 // envelope version
event: string // event name
payload: unknown // structured-clone-safe payload
nonce: string // unique per-send dedup key
origin: string // sender identifier
ts: number // sender timestamp (ms since epoch)
traceContext?: Record<string, string> // W3C trace context headers
}
const TransportEnvelopeSchema: ZodSchema
```
### Structured-clone safety check
```ts
// Returns true if the value can be transmitted via structured clone
function isStructuredCloneable(value: unknown): boolean
// Throws EventsTransportError if value is not structured-clone-safe
function assertStructuredCloneable(value: unknown): void
```
### Error handling
```ts
class EventsTransportError extends SuaveplanError<EventsTransportErrorCode>
type EventsTransportErrorCode =
| "EVENTS_TRANSPORT_NOT_CLONEABLE"
| "EVENTS_TRANSPORT_ACK_TIMEOUT"
| "EVENTS_TRANSPORT_CONNECT_FAILED"
| "EVENTS_TRANSPORT_DISCONNECTED"
| "EVENTS_TRANSPORT_ENVELOPE_INVALID"
function registerEventsTransportErrorCodes(): void
```
### Zod schemas
```ts
const MessageChannelTransportOptionsSchema: ZodSchema
const BroadcastChannelTransportOptionsSchema: ZodSchema
const ConnectOptionsSchema: ZodSchema
```
---
## New additions for genesis
### 1. W3C Trace Context in envelopes
When `@suaveplan/events-otel` is in use on the sending side, `serializeTraceContext()` is called and the result placed in `envelope.traceContext`. On the receiving side, `deserializeTraceContext(envelope.traceContext)` restores the context before re-dispatching, linking the spans across the transport boundary.
### 2. Nonce dedup window
Both transports maintain a sliding window of received nonces (LRU, configurable `nonceWindowSize`). Duplicate nonces within the window are silently dropped and counted in `TransportStats.duplicatesFiltered`.
### 3. Ack protocol for `MessageChannel`
The `MessageChannelTransport` sender waits for an `{ack: nonce}` reply message before incrementing the retry counter. If the ack doesn't arrive within `ackTimeoutMs`, the envelope is re-sent (up to `maxRetries`). The receiver sends the ack immediately after re-dispatching.
### 4. OTel span linking
On the sending side, a span `events_transport.send` is created linked to the current active span. On the receiving side, a span `events_transport.receive` is created with a `SpanLink` back to the sender span (via the propagated trace context), enabling cross-process Gantt chart visualisation.
### 5. Property-based tests (fast-check)
- Envelope round-trip: any structured-clone-safe payload survives `JSON.stringify` → `JSON.parse` round-trip unchanged
- Nonce dedup: the same nonce delivered N times to a receiver results in exactly one re-dispatch
- `BroadcastChannel` at-most-once: a dropped message is never delivered after disconnect
- `MessageChannel` retry: message delivered after first ack timeout still reaches receiver exactly once
---
## File layout
```text
universal/events/events-transport/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/
index.ts
types/
common.ts
common.md
core/
transport-interface/
transport-interface.ts
transport-interface.md
transport-interface.types.ts
index.ts
envelope/
envelope.ts
envelope.md
envelope.types.ts
envelope.test.ts
index.ts
nonce-window/
nonce-window.ts
nonce-window.md
nonce-window.test.ts
index.ts
transports/
message-channel/
message-channel-transport.ts
message-channel-transport.md
message-channel-transport.types.ts
message-channel-transport.test.ts
index.ts
broadcast-channel/
broadcast-channel-transport.ts
broadcast-channel-transport.md
broadcast-channel-transport.types.ts
broadcast-channel-transport.test.ts
index.ts
utils/
structured-clone-check.ts
structured-clone-check.md
structured-clone-check.test.ts
tests/
integration.test.ts
property.test.ts
```
---
## Impact
**Downstream packages**: any application using Web Workers or multi-tab coordination via `BroadcastChannel` that needs `@suaveplan/events` to cross the context boundary.
**Workspace dep prerequisites**: `@suaveplan/events`, `@suaveplan/error`, `@suaveplan/types` must be published to Verdaccio. `@suaveplan/events-otel` is an optional peer for trace context injection.
**Breaking changes**: none (new package).

View File

@@ -0,0 +1,92 @@
# Tasks: enterprise-events-transport
- **Change ID**: enterprise-events-transport
- **Package**: `@suaveplan/events-transport`
- **Date**: 2026-04-24
## Prerequisites (must be published to Verdaccio first)
- [x] `@suaveplan/events`
- [x] `@suaveplan/error`
- [x] `@suaveplan/types`
## Optional peer (for trace context injection)
- [x] `@suaveplan/events-otel` — if present, `serializeTraceContext`/`deserializeTraceContext` are used automatically
## Tasks
### 1. Scaffold package
- [x] Create `universal/events/events-transport/` directory tree per file layout in `proposal.md`
- [x] Write `package.json` (name `@suaveplan/events-transport`, version `0.1.0`, `sideEffects: false`, correct workspace deps and `@opentelemetry/api` catalog dep)
- [x] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config`
### 2. Error codes (`src/error-codes.ts`)
- [x] Define `EventsTransportErrorCode` union type
- [x] Call `defineCodes` to register all `EVENTS_TRANSPORT.*` codes
- [x] Export `registerEventsTransportErrorCodes()` and `EventsTransportError`
### 3. Types (`src/types/common.ts`)
- [x] Define and export `EventTransport`, `DeliveryGuarantee`, `ConnectOptions`, `TransportHandle`, `TransportStats`
- [x] Write `common.md`
### 4. Transport envelope (`src/core/envelope/`)
- [x] Define `TransportEnvelope` interface and `TransportEnvelopeSchema` Zod schema
- [x] Export stable envelope version constant `ENVELOPE_VERSION = 1`
- [x] Write `.md`, `.types.ts`, `.test.ts`, `index.ts`
### 5. Nonce dedup window (`src/core/nonce-window/`)
- [x] Implement LRU sliding window of received nonces with configurable `windowSize`
- [x] `has(nonce)` and `add(nonce)` methods
- [x] Write `.md`, `.test.ts`, `index.ts`
### 6. Structured-clone safety check (`src/utils/structured-clone-check.ts`)
- [x] Implement `isStructuredCloneable(value)` — attempt `structuredClone(value)` and return false on error
- [x] Implement `assertStructuredCloneable(value)` — throws `EventsTransportError("EVENTS_TRANSPORT_NOT_CLONEABLE")`
- [x] Write `.md` and `.test.ts`
### 7. MessageChannel transport (`src/transports/message-channel/`)
- [x] Implement at-least-once delivery: send envelope, wait for ack within `ackTimeoutMs`, retry up to `maxRetries`
- [x] Receiver sends ack immediately after re-dispatching
- [x] Nonce dedup window prevents duplicate re-dispatch
- [x] OTel span linking: `events_transport.send` and `events_transport.receive` linked via trace context in envelope
- [x] Write `.md`, `.types.ts`, `.test.ts`, `index.ts`
### 8. BroadcastChannel transport (`src/transports/broadcast-channel/`)
- [x] Implement at-most-once delivery: fire-and-forget with nonce dedup on receive
- [x] Optional origin filter
- [x] Write `.md`, `.types.ts`, `.test.ts`, `index.ts`
### 9. Tests
- [x] Unit tests co-located (100% coverage target)
- [x] `tests/integration.test.ts` — real MessageChannel and BroadcastChannel in Bun worker context; assert delivery and dedup behaviour
- [x] `tests/property.test.ts` — fast-check properties per `proposal.md` §5
### 10. Docs
- [x] Every `.ts` implementation file has a sibling `.md`
- [x] Root `README.md`
### 11. Pre-publish gate
- [x] `bunx turbo lint typecheck test --filter=@suaveplan/events-transport` green
- [x] `bunx turbo test:coverage --filter=@suaveplan/events-transport` ≥ 100% on touched modules
### 12. Publish
- [x] `bun scripts/publish.ts --package @suaveplan/events-transport`
### 13. Archive
- [x] Fold spec delta into `openspec/specs/events/spec.md`
- [x] `mv openspec/changes/enterprise-events-transport openspec/archive/2026-04-24-enterprise-events-transport`