first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
# @suaveplan/drive-transport-https - HTTPS relay transport for the drive mesh. Implements TransportLike<MessageEnvelope> over a relay endpoint with bearer auth, long-poll receive, exponential backoff, and AbortSignal cancellation.
## Summary
HTTPS-relay-backed `TransportLike<MessageEnvelope>` for the Suaveplan drive mesh. Implements long-poll receive, stateless POST send, exponential backoff with equal-jitter, and Zod-validated wire format. Browser-friendly: the relay is the only HTTP server in the loop, so peers never need to host an inbound endpoint.
```ts
import { HttpsTransport } from "@suaveplan/drive-transport-https";
const transport = new HttpsTransport({
relayUrl: "https://relay.example.com",
ourPeerId: "ab12...",
authToken: process.env.RELAY_TOKEN,
});
transport.start();
const off = transport.onMessage((envelope) => console.log(envelope.id));
await transport.connect("peer-b", "ignored");
await transport.send("peer-b", envelope);
off();
await transport.stop();
```
See `src/transport/https-transport.md` for full semantics, error-handling guidance, and the relay contract.
<!-- begin:auto-docs -->
## Documentation tree
- [schemas](./src/schemas/transport.schema.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [transport.schema](./src/schemas/transport.schema.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [defaults](./src/transport/defaults.md) — Centralises default values for `HttpsTransport` so every part of the package agrees on the same numbers. Living in its…
- [https-transport](./src/transport/https-transport.md) — Implements `HttpsTransport`, the HTTPS-relay-backed `TransportLike<MessageEnvelope>` for `@suaveplan/drive-mesh` and an…
- [error-codes](./src/error-codes.md) — Defines the structured error registry for `@suaveplan/drive-transport-https`. Every failure mode the HTTPS relay transp…
<!-- end:auto-docs --> - [transport.schema](./src/schemas/transport.schema.md) — <!-- begin:auto-docs --> _(no submodules)_- [defaults](./src/transport/defaults.md) — Centralises default values for `HttpsTransport` so every part of the package agrees on the same numbers. Living in its…
- [https-transport](./src/transport/https-transport.md) — Implements `HttpsTransport`, the HTTPS-relay-backed `TransportLike<MessageEnvelope>` for `@suaveplan/drive-mesh` and an…
- [error-codes](./src/error-codes.md) — Defines the structured error registry for `@suaveplan/drive-transport-https`. Every failure mode the HTTPS relay transp…
- [transport.schema](./src/schemas/transport.schema.md) — <!-- begin:auto-docs --> _(no submodules)_- [defaults](./src/transport/defaults.md) — Centralises default values for `HttpsTransport` so every part of the package agrees on the same numbers. Living in its…
- [https-transport](./src/transport/https-transport.md) — Implements `HttpsTransport`, the HTTPS-relay-backed `TransportLike<MessageEnvelope>` for `@suaveplan/drive-mesh` and an…
- [error-codes](./src/error-codes.md) — Defines the structured error registry for `@suaveplan/drive-transport-https`. Every failure mode the HTTPS relay transp…
- [transport.schema](./src/schemas/transport.schema.md) — <!-- begin:auto-docs --> _(no submodules)_- [defaults](./src/transport/defaults.md) — Centralises default values for `HttpsTransport` so every part of the package agrees on the same numbers. Living in its…
- [https-transport](./src/transport/https-transport.md) — Implements `HttpsTransport`, the HTTPS-relay-backed `TransportLike<MessageEnvelope>` for `@suaveplan/drive-mesh` and an…
- [error-codes](./src/error-codes.md) — Defines the structured error registry for `@suaveplan/drive-transport-https`. Every failure mode the HTTPS relay transp…
- [transport.schema](./src/schemas/transport.schema.md) — <!-- begin:auto-docs --> _(no submodules)_- [defaults](./src/transport/defaults.md) — Centralises default values for `HttpsTransport` so every part of the package agrees on the same numbers. Living in its…
- [https-transport](./src/transport/https-transport.md) — Implements `HttpsTransport`, the HTTPS-relay-backed `TransportLike<MessageEnvelope>` for `@suaveplan/drive-mesh` and an…
- [error-codes](./src/error-codes.md) — Defines the structured error registry for `@suaveplan/drive-transport-https`. Every failure mode the HTTPS relay transp…

View File

@@ -0,0 +1,61 @@
# error-codes
## Purpose
Defines the structured error registry for `@suaveplan/drive-transport-https`. Every failure mode the HTTPS relay transport can produce is registered through `defineCodes("HTTPS_TRANSPORT", [...])` so consumers can branch on stable string codes (`"HTTPS_TRANSPORT.SEND_FAILED"`, etc.) without parsing English-language error messages. The companion `HttpsTransportError` class extends the project-wide `SuaveplanError` base so transport errors compose with cause-chain traversal, redaction, and OTel span-event adapters defined in `@suaveplan/error`.
## Features
- Frozen `HTTPS_TRANSPORT_CODES` constant covering nine failure classes spanning lifecycle, send, receive, authentication, and abort paths.
- `HttpsTransportErrorCode` literal union derived from the registry so call sites stay type-safe.
- `HttpsTransportError` class with the `SuaveplanError` constructor signature widened to public, accepting `(code, message, options?)` plus the standard `cause` field.
- Side-effect-safe registration via `defineCodes` so subpath imports automatically see codes registered.
## Usage: Basic Example
```ts
import {
HTTPS_TRANSPORT_CODES,
HttpsTransportError,
} from "@suaveplan/drive-transport-https";
throw new HttpsTransportError(
HTTPS_TRANSPORT_CODES.SEND_FAILED,
"relay returned 503"
);
```
## Usage: Advanced Example
```ts
import {
HTTPS_TRANSPORT_CODES,
HttpsTransportError,
} from "@suaveplan/drive-transport-https";
try {
await transport.send(peerId, envelope);
} catch (err) {
if (err instanceof HttpsTransportError) {
switch (err.code) {
case HTTPS_TRANSPORT_CODES.AUTH_FAILED:
return refreshTokenAndRetry();
case HTTPS_TRANSPORT_CODES.RELAY_UNAVAILABLE:
return queueLocalAndBackoff(err);
default:
throw err;
}
}
throw err;
}
```
## API Reference
- `HTTPS_TRANSPORT_CODES` — `Readonly<Record<string, string>>` of `"HTTPS_TRANSPORT.<NAME>"` strings.
- `HttpsTransportErrorCode` — string-literal union of every value in `HTTPS_TRANSPORT_CODES`.
- `HttpsTransportError` — `class HttpsTransportError extends SuaveplanError<HttpsTransportErrorCode>` with constructor `(code, message, options?)`. Inherits the structured-redaction surface from `SuaveplanError`.
## Implementation Notes
The nine codes split cleanly into lifecycle (`NOT_CONNECTED` / `CLOSED` / `ABORTED` / `PEER_UNKNOWN`), wire (`SEND_FAILED` / `RECV_FAILED` / `MALFORMED_RESPONSE`), and policy (`AUTH_FAILED` / `RELAY_UNAVAILABLE`) groups so monitoring dashboards can attach SLOs per group without redacting cardinality. `RELAY_UNAVAILABLE` is reserved for transient 5xx classes; `SEND_FAILED` and `RECV_FAILED` are retained for non-retryable 4xx classes (other than `401` / `403` which become `AUTH_FAILED`). `MALFORMED_RESPONSE` fires when the relay returns 200 with a body that fails Zod boundary validation — typically signals a relay-version mismatch.

View File

@@ -0,0 +1,55 @@
# transport.schema
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Boundary-only Zod schemas for `@suaveplan/drive-transport-https`. These schemas validate every external input the transport accepts: constructor options, relay `POST /send` request and response bodies, and relay `GET /recv` response bodies. Internal cross-module calls trust their inputs because every entry point at the package boundary either parses-or-throws via these schemas, so once a value is accepted it conforms to its declared type.
## Features
- `HttpsTransportOptionsSchema` — validates the constructor option bag (`relayUrl` must be a URL, `ourPeerId` non-empty, `backoffBaseMs ≤ backoffMaxMs`).
- `SendBodySchema` — validates `{ to, envelope }` request bodies before they hit the relay so a typo cannot leak through to wire format.
- `SendResponseSchema` — `{ ok, code? }` shape that the transport interprets as accepted vs policy-rejected.
- `RecvResponseSchema` — `{ envelopes, nextCursor }`; ensures every envelope returned by the relay matches the structural shape of `MessageEnvelope` before being dispatched to subscribers.
- `MessageEnvelope` shape mirrored verbatim from `@suaveplan/types`.
## Usage: Basic Example
```ts
import {
HttpsTransportOptionsSchema,
} from "@suaveplan/drive-transport-https/schemas";
const validated = HttpsTransportOptionsSchema.parse(rawOptions);
```
## Usage: Advanced Example
```ts
import {
RecvResponseSchema,
SendBodySchema,
} from "@suaveplan/drive-transport-https/schemas";
// On a custom relay-tester harness:
const body = SendBodySchema.parse(JSON.parse(rawRequestBody));
const recvJson = RecvResponseSchema.parse(await response.json());
for (const envelope of recvJson.envelopes) {
// envelope is a fully-typed MessageEnvelope here
}
```
## API Reference
- `HttpsTransportOptionsSchema` — accepts `HttpsTransportOptions`.
- `SendBodySchema` — accepts `SendBody`.
- `SendResponseSchema` — accepts `SendResponse`.
- `RecvResponseSchema` — accepts `RecvResponse`.
Each schema exposes Zod's standard surface: `.parse(value)` (throws on invalid) and `.safeParse(value)` (returns a discriminated `{ success, data | error }`).
## Implementation Notes
The schemas live in their own subpath export so consumers writing custom relay servers can import them without pulling in the runtime transport class. `relayUrl` requires a fully-qualified URL (file paths and relative URLs are rejected). The `backoffBaseMs ≤ backoffMaxMs` invariant is enforced via Zod's `refine` so the constructor cannot accept a config that would produce ever-shrinking backoffs. Nothing in this module loads at module evaluation time except Zod's schema graph, which is pure.

View File

@@ -0,0 +1,52 @@
# defaults
## Purpose
Centralises default values for `HttpsTransport` so every part of the package agrees on the same numbers. Living in its own module makes the constants discoverable and lets the typecheck-time schema cap derived values (e.g. backoffs) without circular imports.
## Features
- `DEFAULT_LONG_POLL_TIMEOUT_MS = 30_000` — relays hold the long-poll for up to thirty seconds when no messages are pending.
- `DEFAULT_POLL_IDLE_DELAY_MS = 0` — re-poll immediately after an empty long-poll. Tests pass a small non-zero value to synchronise loops with `FakeClock`.
- `DEFAULT_BACKOFF_BASE_MS = 250` — first retry waits ~250 ms; doubles each failure.
- `DEFAULT_BACKOFF_MAX_MS = 30_000` — maximum backoff between retries.
- `RECV_BATCH_CAP = 256` — receive-loop yields between dispatched batches above this size.
## Usage: Basic Example
```ts
import { DEFAULT_LONG_POLL_TIMEOUT_MS } from "@suaveplan/drive-transport-https";
console.log(`relays hold up to ${DEFAULT_LONG_POLL_TIMEOUT_MS} ms`);
```
## Usage: Advanced Example
```ts
import {
DEFAULT_BACKOFF_BASE_MS,
DEFAULT_BACKOFF_MAX_MS,
RECV_BATCH_CAP,
} from "@suaveplan/drive-transport-https";
const transport = new HttpsTransport({
relayUrl: "https://relay.example.com",
ourPeerId: "ab12...",
// Halve the defaults for a low-latency intranet:
backoffBaseMs: DEFAULT_BACKOFF_BASE_MS / 2,
backoffMaxMs: DEFAULT_BACKOFF_MAX_MS / 2,
});
console.log(`receive loop yields above ${RECV_BATCH_CAP} envelopes`);
```
## API Reference
- `DEFAULT_LONG_POLL_TIMEOUT_MS: number` — 30 000.
- `DEFAULT_POLL_IDLE_DELAY_MS: number` — 0.
- `DEFAULT_BACKOFF_BASE_MS: number` — 250.
- `DEFAULT_BACKOFF_MAX_MS: number` — 30 000.
- `RECV_BATCH_CAP: number` — 256.
## Implementation Notes
The defaults are deliberately conservative: thirty-second long-polls match the typical browser idle-tab timeout, so a tab that has been backgrounded for the full window naturally gets a single fresh poll on the next visibility transition rather than starving connection slots. The 250 ms backoff base keeps fast-flapping relays from hammering the receiver while still re-establishing the loop quickly under jitter. `RECV_BATCH_CAP` exists so a slow subscriber chain (logging, telemetry, etc.) cannot starve the next poll cycle on a noisy relay.

View File

@@ -0,0 +1,96 @@
# https-transport
## Purpose
Implements `HttpsTransport`, the HTTPS-relay-backed `TransportLike<MessageEnvelope>` for `@suaveplan/drive-mesh` and any other consumer that wants a browser-friendly transport. Sending is a stateless `POST /send` to the relay; receiving is a long-poll `GET /recv` loop that runs in the background and dispatches every returned envelope to every subscribed handler. The transport is fully `AbortSignal`-aware, exponential-backoff-aware on transient failures, and Zod-validated at every wire boundary so a misconfigured relay produces a typed `HttpsTransportError` instead of a silently-mangled envelope.
## Features
- Implements `TransportLike<MessageEnvelope>` from `@suaveplan/types/transport` exactly — `connect` / `disconnect` / `send` / `onMessage` / `getConnectedPeers` plus extra `start` / `stop` / `cursor` / `state` lifecycle helpers.
- Long-poll receive loop with configurable `longPollTimeoutMs`, `pollIdleDelayMs`, and exponential backoff (`backoffBaseMs` / `backoffMaxMs` with equal-jitter).
- Zod-validated wire format: `SendBodySchema`, `SendResponseSchema`, `RecvResponseSchema` enforce the relay contract; mismatched bodies raise `HTTPS_TRANSPORT.MALFORMED_RESPONSE`.
- Distinct error codes per failure class: `NOT_CONNECTED` / `CLOSED` / `SEND_FAILED` / `RECV_FAILED` / `AUTH_FAILED` / `RELAY_UNAVAILABLE` / `MALFORMED_RESPONSE` / `ABORTED` / `PEER_UNKNOWN`.
- Auth-failure short-circuit: a `401`/`403` from `/recv` stops the loop instead of looping forever — back-pressure belongs to the consumer.
- Subscribers are observers: a handler that throws is swallowed so one bad subscriber cannot poison the loop.
- Receive-loop yield: dispatches above `RECV_BATCH_CAP` envelopes yield via `await Promise.resolve()` between batches so a noisy relay cannot starve the next poll cycle.
- Injectable `fetchFn`, `Clock`, `Rng` — every random / time / network choice is testable without `vi.useFakeTimers()`.
## Usage: Basic Example
```ts
import { HttpsTransport } from "@suaveplan/drive-transport-https";
const transport = new HttpsTransport({
relayUrl: "https://relay.example.com",
ourPeerId: "ab12...",
authToken: process.env.RELAY_TOKEN,
});
transport.start();
const off = transport.onMessage((envelope) => {
console.log("received", envelope.id);
});
await transport.connect("peer-b", "ignored");
await transport.send("peer-b", {
id: "msg-1",
type: "ping",
senderId: "ab12...",
targetId: "peer-b",
payload: new Uint8Array([1, 2, 3]),
timestamp: Date.now(),
headers: {},
});
off();
await transport.stop();
```
## Usage: Advanced Example
```ts
import {
HTTPS_TRANSPORT_CODES,
HttpsTransport,
HttpsTransportError,
} from "@suaveplan/drive-transport-https";
const transport = new HttpsTransport({
relayUrl: "https://relay.example.com",
ourPeerId: "ab12...",
authToken: refreshToken(),
longPollTimeoutMs: 10_000,
backoffBaseMs: 100,
backoffMaxMs: 2_000,
fetchFn: instrumentedFetch,
});
transport.start();
try {
await transport.send(remotePeerId, envelope);
} catch (err) {
if (
err instanceof HttpsTransportError &&
err.code === HTTPS_TRANSPORT_CODES.AUTH_FAILED
) {
await refreshAndRestart(transport);
} else {
throw err;
}
}
```
## API Reference
- `new HttpsTransport(options: HttpsTransportOptions)` — constructor; validates `options` against `HttpsTransportOptionsSchema`.
- `transport.start(): void` — begin the long-poll loop. Throws `HTTPS_TRANSPORT.CLOSED` if previously stopped. Idempotent while running.
- `transport.stop(): Promise<void>` — abort the loop; idempotent. After stop, the transport cannot be restarted.
- `transport.connect(peerId, address): Promise<void>` — register a peer. The HTTPS relay model is stateless so `address` is recorded but unused.
- `transport.disconnect(peerId): Promise<void>` — remove a peer.
- `transport.send(peerId, envelope): Promise<void>` — `POST /send`. Throws on auth, relay-unavailable, send-failed, malformed-response.
- `transport.onMessage(handler): Unsubscribe` — subscribe.
- `transport.getConnectedPeers(): readonly PeerId[]`.
- `transport.cursor: string` — the relay's last `nextCursor` (diagnostic only).
- `transport.state: "idle" | "running" | "stopped"`.
## Implementation Notes
The receive loop is a single async function that runs until the abort signal fires. On a transient failure (`RELAY_UNAVAILABLE` / `RECV_FAILED` / `MALFORMED_RESPONSE`) it backs off via `exp(failures) / 2 + jitter` clamped to `backoffMaxMs` and retries; on `AUTH_FAILED` it terminates the loop and propagates the error. Send is independent of the loop — even if the loop is in an extended backoff window, `send()` can still issue a `POST /send` because it shares only the `AbortController`'s signal with the loop, not the loop's retry state machine.
`stop()` aborts the controller and awaits the loop's promise (catching any propagated error so `stop()` itself remains a clean no-throw). Subsequent `start()` calls throw `HTTPS_TRANSPORT.CLOSED` to make double-lifecycle errors loud rather than silent. Auth failures inside `send()` propagate immediately (no backoff) because back-pressure for credential refresh belongs in the consumer.

View File

@@ -0,0 +1,68 @@
# transport.types
## Purpose
Shared public types for `@suaveplan/drive-transport-https`. Defines the constructor option bag (`HttpsTransportOptions`), the relay request / response shapes (`SendBody` / `SendResponse` / `RecvResponse`), the `FetchFn` injection point that makes the whole package testable without a network, and the `Unsubscribe` handle type returned by `onMessage`. Living in `types/` keeps the runtime modules focused on behaviour and leaves a single, documented surface for downstream consumers (drive-mesh, browser apps) to import.
## Features
- `HttpsTransportOptions` — every knob the transport exposes: `relayUrl`, `ourPeerId`, `authToken?`, `fetchFn?`, `longPollTimeoutMs?`, `pollIdleDelayMs?`, `backoffBaseMs?`, `backoffMaxMs?`, `clock?`, `rng?`.
- `FetchFn` — a `fetch`-shaped injection point; production defaults to `globalThis.fetch` and tests pass a deterministic stub.
- `SendBody` / `SendResponse` — the relay's `POST /send` contract with `{ ok, code? }` policy responses.
- `RecvResponse` — the relay's `GET /recv` response with `envelopes` plus a `nextCursor` for at-least-once delivery semantics.
- `Unsubscribe` — alias for `() => void` returned by the subscription API.
## Usage: Basic Example
```ts
import type {
HttpsTransportOptions,
} from "@suaveplan/drive-transport-https/types";
const options: HttpsTransportOptions = {
relayUrl: "https://relay.example.com",
ourPeerId: "ab12...",
authToken: process.env.RELAY_TOKEN,
};
```
## Usage: Advanced Example
```ts
import type {
FetchFn,
HttpsTransportOptions,
RecvResponse,
} from "@suaveplan/drive-transport-https/types";
const fakeFetch: FetchFn = async (input) => {
if (String(input).includes("/recv")) {
const body: RecvResponse = { envelopes: [], nextCursor: "0" };
return new Response(JSON.stringify(body), { status: 200 });
}
return new Response(JSON.stringify({ ok: true }), { status: 200 });
};
const options: HttpsTransportOptions = {
relayUrl: "https://relay.test",
ourPeerId: "deadbeef",
fetchFn: fakeFetch,
longPollTimeoutMs: 1_000,
pollIdleDelayMs: 25,
backoffBaseMs: 50,
backoffMaxMs: 500,
};
```
## API Reference
- `interface HttpsTransportOptions` — see "Features" for fields. All optional fields default through `defaults.ts`.
- `type FetchFn = (input: string | URL, init?: RequestInit) => Promise<Response>`.
- `interface RecvResponse { envelopes: ReadonlyArray<MessageEnvelope>; nextCursor: string }`.
- `interface SendBody { to: PeerId; envelope: MessageEnvelope }`.
- `interface SendResponse { ok: boolean; code?: string }`.
- `type Unsubscribe = () => void`.
## Implementation Notes
`HttpsTransportOptions` is `readonly` end-to-end so the transport can capture the option bag once at construction without copying. Defaults are applied internally; consumers are not required to set anything beyond `relayUrl` and `ourPeerId`. The clock and RNG injection points exist purely so deterministic tests do not need `vi.useFakeTimers()` — `FakeClock` from `@suaveplan/testing` is the canonical pattern.