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,89 @@
# `bun-ws-engine`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`createBunWsEngine` is the package's headline factory. It composes every other module in the package — registry, bus, instruments, backpressure guard, heartbeat controller, reconnect token issuer, reconnect buffer, principal-expiry watcher — into a single `BunWsEngine` value that the host wires into `Bun.serve`'s `websocket` option. The factory is also the entry the conformance harness drives via `makeTestConnection`, so the unit-level conformance run exercises the same code paths a production handshake takes.
The engine owns connection lifetime: it freezes the principal at handshake, holds per-connection backpressure and heartbeat state, fires `principal_expired` listeners when the underlying token lapses, and emits the OTel surface from design §11.7 against every state transition.
## Features
- One-line composition of the full §11 connection lifecycle.
- Drop-in `(bus, registry, broker)` triple for `runWsEngineConformance`.
- `engine.attach({ ... })` runs handshake validation, enforces the per-tenant cap, instantiates the backpressure / heartbeat / principal-expiry machinery, and wires the sink onto the resulting `ManagedConnection`.
- `engine.broadcast(caller, topic, opts)` records to `ws.broadcasts_total`, fans out via the bus, and persists the broadcast into the reconnect-event buffer in one call.
- `engine.onPrincipalExpired(listener)` registers a host-level listener for the `connection.principal_expired` event.
- `engine.dispose()` closes every open connection and tears down the reconnect buffer.
## Usage: Basic
```ts
import { createBunWsEngine } from "@suaveplan/api-ws-bun";
const engine = createBunWsEngine({
reconnectTokenSecret: new Uint8Array(32).fill(7),
});
const conn = engine.attach({
id: "c1",
principal,
tenantId,
capabilities: new Set(),
tokenExpiresAt: Date.now() + 60_000,
sink: { send: () => undefined, close: () => undefined },
});
conn.subscribe("orders");
engine.broadcast(
{ tenant: tenantId, capabilities: new Set() },
"orders",
{ tenant: tenantId, payload: { ok: true } }
);
```
## Usage: Advanced
```ts
import { createInMemoryWsBroker } from "@suaveplan/api-ws-broker-in-memory";
import { createBunWsEngine, WS_BUN_CODES } from "@suaveplan/api-ws-bun";
const broker = createInMemoryWsBroker();
const engine = createBunWsEngine({
reconnectTokenSecret: new Uint8Array(32).fill(11),
broker,
heartbeatMs: 10_000,
missedPongLimit: 3,
maxConnectionsPerTenant: 5_000,
reconnectEventBufferStrategy: "auto",
pluginId: "demo-plugin",
});
engine.onPrincipalExpired((evt) => log.warn(evt));
try {
engine.attach({ id: "c-overflow", principal, tenantId, capabilities: new Set(), tokenExpiresAt: Date.now() + 60_000, sink });
} catch (err) {
if ((err as { code: string }).code === WS_BUN_CODES.TENANT_CAP_EXCEEDED) {
// surface 429
}
}
await engine.dispose();
```
## API Reference
- `CreateBunWsEngineInput` — `BunWsEngineOptions & { broker?, clock? }`.
- `BunWsEngine<Payload>` — `{ bus, registry, broker, instruments, options, reconnectTokens, reconnectBuffer, attach, recordInbound, broadcast, onPrincipalExpired, dispose }`.
- `AttachConnectionInput` — args to `engine.attach`.
- `ManagedConnection extends WsConnection` — exposes the per-connection guards for assertions.
- `BunWsSocketSink` — `{ send, close, ping? }`.
- `createBunWsEngine(input): BunWsEngine` — factory.
- `makeTestConnection(engine, { id, tenant })` — conformance harness helper.
## Implementation Notes
The engine deliberately keeps the sink abstract (`BunWsSocketSink`) rather than depending on Bun's `ServerWebSocket` type. In production the host adapts a `ServerWebSocket` into a sink with three methods; in tests the sink is a closure capturing sent frames. This split is what lets the unit test suite drive the engine without spinning up Bun.serve, while the integration test (`tests/integration/end-to-end.test.ts`) drives the full Bun listener path against the same sink contract.
The `lastEventId` and `state` fields on `ManagedConnection` use getters so the engine can mutate the underlying closures without breaking `WsConnection`'s readonly contract. The contract is a structural type, so this is fine.
The principal-expiry callback flips `state` to `"closing"` but does not eagerly close the socket; the engine waits for the next handshake or send to drain the connection. This matches the spec wording — "host emits `connection.principal_expired`; plugin's `onClose` MUST then be invokable" — which puts the actual close decision on the plugin, not the engine.