durablews 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,41 +1,40 @@
1
1
  # DurableWS
2
2
 
3
- > The WebSocket client that survives the real world automatic reconnection,
3
+ > The WebSocket client that survives the real world, automatic reconnection,
4
4
  > bounded queueing, and typed messages. Zero dependencies, every modern
5
5
  > runtime, durable by default.
6
6
 
7
- > ⚠️ **v2 is in alpha** (`npm install durablews@alpha`). The features below
8
- > are built and tested (unit, integration, real-browser e2e); the API may
9
- > still shift before 2.0. The
7
+ > The features below are built and tested (unit, integration, real-browser
8
+ > e2e). The
10
9
  > [architecture RFC](https://github.com/imnsco/DurableWS/blob/main/rfcs/0001-v2-architecture.md)
11
- > tracks design and status. The `1.x` release predates this rewrite don't
10
+ > tracks design and status. The `1.x` line predates the v2 rewrite, don't
12
11
  > use it.
13
12
 
14
13
  ## What you get
15
14
 
16
- - **Automatic reconnection, on by default** full-jitter exponential
15
+ - **Automatic reconnection, on by default**: full-jitter exponential
17
16
  backoff, unlimited retries, a `shouldReconnect` veto, and `reconnecting`
18
17
  events for your UI.
19
- - **Message queueing while disconnected, on by default** bounded,
18
+ - **Message queueing while disconnected, on by default**: bounded,
20
19
  drop-oldest, flushed in order on open. Every dropped message fires a `drop`
21
20
  event; nothing is silently lost.
22
- - **Opt-in heartbeat / idle detection** quietly-dead links are closed (code
21
+ - **Opt-in heartbeat / idle detection**: quietly-dead links are closed (code
23
22
  `4408`) and recovered through the normal reconnect machinery.
24
- - **Typed + validated messages** pass any
23
+ - **Typed + validated messages**: pass any
25
24
  [Standard Schema](https://standardschema.dev) (zod, valibot, arktype, …)
26
25
  and inbound messages are type-inferred *and* runtime-validated.
27
- - **Middleware, inbound and outbound** an onion pipeline with async-safe,
26
+ - **Middleware, inbound and outbound**: an onion pipeline with async-safe,
28
27
  ordered outbound execution (auth/token-refresh ready).
29
- - **Pluggable codec** JSON by default; swap in msgpack or anything else.
30
- - **Vue & React bindings in the box** `durablews/vue` (composable) and
28
+ - **Pluggable codec**: JSON by default; swap in msgpack or anything else.
29
+ - **Vue & React bindings in the box**: `durablews/vue` (composable) and
31
30
  `durablews/react` (hook); the frameworks are optional peers.
32
- - **A drop-in `WebSocket` class** `durablews/compat` for one-line migration
31
+ - **A drop-in `WebSocket` class**: `durablews/compat` for one-line migration
33
32
  or `webSocketImpl` injection, with a published known-deviations table.
34
33
  - **Zero runtime dependencies**, built on the standard global `WebSocket`.
35
34
 
36
35
  ## Requirements
37
36
 
38
- - **Node.js ≥ 22** the first release where the global `WebSocket` is
37
+ - **Node.js ≥ 22**: the first release where the global `WebSocket` is
39
38
  available unflagged. (There is no `ws` dependency, by design.)
40
39
  - Any modern runtime with a global `WebSocket`: current browsers, Deno, Bun,
41
40
  and Cloudflare Workers.
@@ -43,7 +42,7 @@
43
42
  ## Installation
44
43
 
45
44
  ```bash
46
- npm install durablews@alpha
45
+ npm install durablews
47
46
  ```
48
47
 
49
48
  ## Quick start
@@ -73,19 +72,19 @@ const Message = z.object({ type: z.string(), body: z.string() });
73
72
  const client = defineClient({ url, schema: Message });
74
73
 
75
74
  client.on("message", (msg) => {
76
- // msg: { type: string; body: string } validated at runtime
75
+ // msg: { type: string; body: string }, validated at runtime
77
76
  });
78
77
  ```
79
78
 
80
79
  ## Documentation
81
80
 
82
- **[durablews.imns.co](https://durablews.imns.co)** getting started, guides
81
+ **[durablews.imns.co](https://durablews.imns.co)**: getting started, guides
83
82
  (durability tuning, middleware, codecs, drop-in compat), framework pages, the
84
83
  API reference, and an honest comparison with the alternatives.
85
84
 
86
85
  ## Contributing
87
86
 
88
- Contributions are welcome see [CONTRIBUTING](https://github.com/imnsco/DurableWS/blob/main/CONTRIBUTING.md).
87
+ Contributions are welcome, see [CONTRIBUTING](https://github.com/imnsco/DurableWS/blob/main/CONTRIBUTING.md).
89
88
 
90
89
  ## License
91
90
 
@@ -1,4 +1,4 @@
1
- import { a as WebSocketClientConfig, W as WebSocketClient } from './types-BDvztGcG.js';
1
+ import { a as WebSocketClientConfig, W as WebSocketClient } from './types-BgbiWEXo.js';
2
2
 
3
3
  /**
4
4
  * What a framework binding accepts: either a config (the binding creates and
@@ -1,4 +1,4 @@
1
- import { a as WebSocketClientConfig, W as WebSocketClient } from './types-BDvztGcG.cjs';
1
+ import { a as WebSocketClientConfig, W as WebSocketClient } from './types-BgbiWEXo.cjs';
2
2
 
3
3
  /**
4
4
  * What a framework binding accepts: either a config (the binding creates and
@@ -0,0 +1,12 @@
1
+ // src/middleware/pingpong.ts
2
+ var pingpong = (ctx, next) => {
3
+ if (ctx.data === "ping") {
4
+ ctx.client.send("pong");
5
+ return;
6
+ }
7
+ return next();
8
+ };
9
+
10
+ export { pingpong };
11
+ //# sourceMappingURL=chunk-5MUMKGJL.js.map
12
+ //# sourceMappingURL=chunk-5MUMKGJL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/pingpong.ts"],"names":[],"mappings":";AAcO,IAAM,QAAA,GAAuB,CAAC,GAAA,EAAK,IAAA,KAAS;AAC/C,EAAA,IAAI,GAAA,CAAI,SAAS,MAAA,EAAQ;AACrB,IAAA,GAAA,CAAI,MAAA,CAAO,KAAK,MAAM,CAAA;AACtB,IAAA;AAAA,EACJ;AACA,EAAA,OAAO,IAAA,EAAK;AAChB","file":"chunk-5MUMKGJL.js","sourcesContent":["import type { Middleware } from \"@/types\";\n\n/**\n * Replies to a textual `\"ping\"` with `\"pong\"` and stops the message there\n * (it is not emitted as a `message` event). A common keepalive convention.\n *\n * Opt in explicitly, it is not registered by default:\n *\n * ```typescript\n * import { defineClient, pingpong } from \"durablews\";\n *\n * const ws = defineClient({ url }).use(pingpong);\n * ```\n */\nexport const pingpong: Middleware = (ctx, next) => {\n if (ctx.data === \"ping\") {\n ctx.client.send(\"pong\");\n return;\n }\n return next();\n};\n"]}
package/dist/compat.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { W as WebSocketClient, a as WebSocketClientConfig } from './types-BDvztGcG.cjs';
1
+ import { W as WebSocketClient, a as WebSocketClientConfig } from './types-BgbiWEXo.cjs';
2
2
 
3
3
  /** The durablews options accepted as the constructor's third argument. */
4
4
  type DurableWebSocketOptions = Omit<WebSocketClientConfig, "url" | "protocols" | "codec" | "schema">;
package/dist/compat.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { W as WebSocketClient, a as WebSocketClientConfig } from './types-BDvztGcG.js';
1
+ import { W as WebSocketClient, a as WebSocketClientConfig } from './types-BgbiWEXo.js';
2
2
 
3
3
  /** The durablews options accepted as the constructor's third argument. */
4
4
  type DurableWebSocketOptions = Omit<WebSocketClientConfig, "url" | "protocols" | "codec" | "schema">;
package/dist/index.cjs CHANGED
@@ -587,7 +587,7 @@ function toError(value) {
587
587
  return value instanceof Error ? value : new Error(String(value));
588
588
  }
589
589
 
590
- // src/middleware.ts
590
+ // src/middleware/pingpong.ts
591
591
  var pingpong = (ctx, next) => {
592
592
  if (ctx.data === "ping") {
593
593
  ctx.client.send("pong");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/backoff.ts","../src/codec.ts","../src/fsm.ts","../src/heartbeat.ts","../src/helpers/event-bus.ts","../src/pipeline.ts","../src/queue.ts","../src/schema.ts","../src/client.ts","../src/middleware.ts","../src/index.ts"],"names":["promise"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ReconnectOptions } from \"@/types\";\n\n/** `ReconnectOptions` with every field filled in. */\nexport type ResolvedReconnect = Required<ReconnectOptions>;\n\n/**\n * The durable-by-default reconnection policy: full-jitter exponential backoff,\n * never give up. See the RFC's M3 decisions for the rationale behind each value.\n */\nexport const RECONNECT_DEFAULTS: ResolvedReconnect = {\n baseDelay: 500,\n factor: 2,\n maxDelay: 30_000,\n jitter: true,\n maxRetries: Number.POSITIVE_INFINITY,\n shouldReconnect: () => true\n};\n\n/**\n * Resolve the user's `reconnect` config: `false` disables reconnection\n * entirely (`null`), anything else is merged over the defaults.\n */\nexport function resolveReconnect(\n option: false | ReconnectOptions | undefined\n): ResolvedReconnect | null {\n if (option === false) {\n return null;\n }\n return { ...RECONNECT_DEFAULTS, ...option };\n}\n\n/**\n * Compute the backoff delay for a retry.\n *\n * The exponential delay is `baseDelay × factorᵃᵗᵗᵉᵐᵖᵗ`, capped at `maxDelay`.\n * With jitter (the default), the actual delay is drawn uniformly from\n * `[0, exponential]` — \"full jitter\", which spreads a fleet of simultaneously\n * dropped clients across the whole window instead of letting them retry in\n * synchronized waves.\n *\n * @param attempt - 0-based: the first retry is attempt `0`.\n * @param random - injectable for deterministic tests; defaults to `Math.random`.\n */\nexport function computeDelay(\n attempt: number,\n options: ResolvedReconnect,\n random: () => number = Math.random\n): number {\n const exponential = Math.min(\n options.maxDelay,\n options.baseDelay * options.factor ** attempt\n );\n return options.jitter ? random() * exponential : exponential;\n}\n","import type { Codec } from \"@/types\";\n\n/**\n * Parse a string as JSON, falling back to the raw string when it isn't valid\n * JSON. Lets text protocols that mix JSON and plain strings work unchanged.\n */\nfunction safeJSONParse(data: string): unknown {\n try {\n return JSON.parse(data);\n } catch {\n return data;\n }\n}\n\n/**\n * Whether a value is already a WebSocket-sendable binary frame and should be\n * passed through rather than JSON-encoded (`ArrayBuffer`, any typed array or\n * `DataView`, or a `Blob`).\n *\n * The predicate claims `BufferSource` (what `WebSocket.send` accepts), which\n * asserts the view is not backed by a `SharedArrayBuffer` — `send` rejects\n * those at runtime, so they were never sendable anyway.\n */\nfunction isBinary(data: unknown): data is BufferSource | Blob {\n return (\n data instanceof ArrayBuffer ||\n ArrayBuffer.isView(data) ||\n (typeof Blob !== \"undefined\" && data instanceof Blob)\n );\n}\n\n/**\n * The default codec: JSON over text frames, binary passed through.\n *\n * - `encode` — strings and binary frames (`ArrayBuffer`/typed arrays/`Blob`)\n * are sent verbatim; everything else is `JSON.stringify`d.\n * - `decode` — text frames are JSON-parsed (falling back to the raw string);\n * binary frames are passed through untouched.\n */\nexport const jsonCodec: Codec = {\n encode(data: unknown) {\n if (typeof data === \"string\" || isBinary(data)) {\n return data;\n }\n return JSON.stringify(data);\n },\n decode(data: unknown) {\n return typeof data === \"string\" ? safeJSONParse(data) : data;\n }\n};\n","import type { ConnectionEvent, ConnectionState } from \"@/types\";\n\n/**\n * The connection lifecycle as an explicit finite state machine.\n *\n * A connection has a small, fixed set of legal states and transitions. Modelling\n * it as a table — rather than a free-form reducer — means an illegal transition\n * (e.g. a `CLOSED` event arriving while already `closed`) is *representable as the\n * absence of an entry* and can be rejected, instead of silently doing nothing.\n * That silent no-op is exactly what allowed the original close/error bug.\n *\n * `ERROR` is deliberately absent from the table: a transport error does not by\n * itself change the connection state (the browser/runtime fires `error` and then\n * `close`), so it is handled out-of-band by the client (record + emit) and the\n * following `CLOSED` event performs the actual transition.\n */\nconst TRANSITIONS: Record<\n ConnectionState,\n Partial<Record<ConnectionEvent, ConnectionState>>\n> = {\n idle: { CONNECT: \"connecting\" },\n connecting: {\n OPEN: \"open\",\n CLOSE_REQUESTED: \"closing\",\n CLOSED: \"closed\",\n RETRY: \"reconnecting\"\n },\n open: {\n CLOSE_REQUESTED: \"closing\",\n CLOSED: \"closed\",\n RETRY: \"reconnecting\"\n },\n closing: { CLOSED: \"closed\" },\n // `reconnecting` = waiting out the backoff delay; no socket exists, so\n // CLOSE_REQUESTED goes straight to `closed` (there is nothing to wait for)\n // and OPEN/CLOSED cannot legally occur.\n reconnecting: {\n CONNECT: \"connecting\",\n CLOSE_REQUESTED: \"closed\"\n },\n closed: { CONNECT: \"connecting\" }\n};\n\n/**\n * Resolve the next state for an `(state, event)` pair.\n *\n * @returns the destination state, or `null` if the transition is illegal.\n */\nexport function nextState(\n state: ConnectionState,\n event: ConnectionEvent\n): ConnectionState | null {\n return TRANSITIONS[state][event] ?? null;\n}\n\n/**\n * Whether `(state, event)` is a legal transition.\n */\nexport function canTransition(\n state: ConnectionState,\n event: ConnectionEvent\n): boolean {\n return nextState(state, event) !== null;\n}\n","import type { HeartbeatOptions } from \"@/types\";\n\n/** `HeartbeatOptions` with every field filled in. */\nexport interface ResolvedHeartbeat {\n readonly interval: number;\n readonly message: unknown;\n readonly timeout: number;\n}\n\n/**\n * The close code used when the heartbeat declares the link dead (app-reserved\n * 4xxx range, mnemonic for HTTP 408 Request Timeout). Useful in a custom\n * `shouldReconnect` to distinguish heartbeat closes from server closes.\n */\nexport const HEARTBEAT_TIMEOUT_CODE = 4408;\n\n/**\n * Resolve the user's `heartbeat` config. Heartbeat is **opt-in**: absence means\n * off (`null`) — a naive \"no traffic → reconnect\" would kill legitimately\n * quiet-but-healthy connections, and any ping depends on app-level semantics\n * the library can't assume (see the RFC's M3 decisions).\n */\nexport function resolveHeartbeat(\n option: HeartbeatOptions | undefined\n): ResolvedHeartbeat | null {\n if (option === undefined) {\n return null;\n }\n return {\n interval: option.interval,\n message: option.message ?? \"ping\",\n timeout: option.timeout ?? option.interval\n };\n}\n","import type { EventBus } from \"@/types\";\n\n/**\n * Creates an event bus for managing event subscriptions and emissions.\n * Provides a simple pub/sub pattern with type-safe event handling.\n *\n * @returns EventBus instance with on, off, emit, and once methods\n *\n * @example\n * ```typescript\n * const eventBus = defineEventBus();\n *\n * // Subscribe to events\n * eventBus.on('user-login', (user) => {\n * console.log('User logged in:', user);\n * });\n *\n * // Emit events\n * eventBus.emit('user-login', { id: 1, name: 'John' });\n *\n * // One-time subscription\n * eventBus.once('app-ready', () => {\n * console.log('App is ready!');\n * });\n * ```\n */\nexport function defineEventBus(): EventBus {\n const listeners = new Map<string, Array<(payload: unknown) => void>>();\n\n /**\n * Subscribes to an event with a handler function.\n *\n * @template T - The type of the event payload\n * @param eventName - The name of the event to listen for\n * @param handler - Function to call when the event is emitted\n */\n function on<T = unknown>(\n eventName: string,\n handler: (payload: T) => void\n ): void {\n const handlers = listeners.get(eventName) ?? [];\n handlers.push(handler as (payload: unknown) => void);\n listeners.set(eventName, handlers);\n }\n\n /**\n * Unsubscribes a handler from an event.\n *\n * @template T - The type of the event payload\n * @param eventName - The name of the event to stop listening for\n * @param handler - The handler function to remove\n */\n function off<T = unknown>(\n eventName: string,\n handler: (payload: T) => void\n ) {\n const handlers = listeners.get(eventName);\n if (!handlers) {\n return;\n }\n listeners.set(\n eventName,\n handlers.filter((h) => h !== handler)\n );\n }\n\n /**\n * Subscribes to an event with a handler that will only be called once.\n * The handler is automatically removed after the first emission.\n *\n * @template T - The type of the event payload\n * @param eventName - The name of the event to listen for\n * @param handler - Function to call when the event is emitted (only once)\n */\n function once<T = unknown>(\n eventName: string,\n handler: (payload: T) => void\n ): void {\n const onceHandler = (payload: T) => {\n // Remove the handler after first invocation\n off(eventName, onceHandler as (payload: unknown) => void);\n // Call the original handler\n handler(payload);\n };\n\n // Add the wrapper handler\n on(eventName, onceHandler);\n }\n\n /**\n * Emits an event to all subscribed handlers.\n *\n * @template T - The type of the event payload\n * @param eventName - The name of the event to emit\n * @param payload - The data to pass to event handlers\n */\n function emit<T = unknown>(eventName: string, payload?: T) {\n const handlers = listeners.get(eventName);\n handlers?.forEach((fn) => {\n fn(payload);\n });\n }\n\n return { on, off, emit, once };\n}\n","/**\n * The functional shape shared by inbound and outbound middleware: receive a\n * context and a `next`, optionally async. The context type is the only thing\n * that differs per direction.\n */\ntype PipelineMiddleware<TContext> = (\n ctx: TContext,\n next: () => void | Promise<void>\n) => void | Promise<void>;\n\n/**\n * Runs a message through a middleware chain (onion model).\n *\n * Each middleware receives the shared context and a `next` it may call to pass\n * control onward. When the chain runs to completion, `terminal` is invoked —\n * for inbound messages that is where the client emits `message`; for outbound,\n * where it encodes and writes to the socket. A middleware that returns without\n * calling `next()` short-circuits the chain (and `terminal`).\n *\n * Mirrors the guard from the old store pipeline: calling `next()` more than once\n * in a single middleware is a bug and throws.\n *\n * @returns `void`, or a `Promise` if any middleware in the chain is async.\n */\nexport function runPipeline<TContext>(\n middlewares: readonly PipelineMiddleware<TContext>[],\n ctx: TContext,\n terminal: () => void\n): void | Promise<void> {\n let invoked = -1;\n\n function dispatch(i: number): void | Promise<void> {\n if (i <= invoked) {\n throw new Error(\"next() called multiple times\");\n }\n invoked = i;\n\n const middleware = middlewares[i];\n if (!middleware) {\n return terminal();\n }\n return middleware(ctx, () => dispatch(i + 1));\n }\n\n return dispatch(0);\n}\n","import type { QueueOptions } from \"@/types\";\n\n/** `QueueOptions` with every field filled in. */\nexport type ResolvedQueue = Required<QueueOptions>;\n\n/**\n * The default outbound-queue policy: bounded at 256 messages, drop-oldest.\n * Never silently unbounded (a memory leak), never silently lossy (every drop\n * emits a `drop` event). See the RFC's M3 decisions.\n */\nexport const QUEUE_DEFAULTS: ResolvedQueue = {\n maxSize: 256\n};\n\n/**\n * Resolve the user's `queue` config: `false` disables queueing entirely\n * (`null`), anything else is merged over the defaults.\n */\nexport function resolveQueue(\n option: false | QueueOptions | undefined\n): ResolvedQueue | null {\n if (option === false) {\n return null;\n }\n return { ...QUEUE_DEFAULTS, ...option };\n}\n","/**\n * The Standard Schema v1 interface (https://standardschema.dev), vendored as\n * types-only per the spec's guidance — implementing libraries (zod, valibot,\n * arktype, …) conform to this shape, so DurableWS can accept any of them\n * without depending on any of them. Keeps core zero-dependency.\n */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly \"~standard\": StandardSchemaV1.Props<Input, Output>;\n}\n\nexport declare namespace StandardSchemaV1 {\n export interface Props<Input = unknown, Output = Input> {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (\n value: unknown\n ) => Result<Output> | Promise<Result<Output>>;\n readonly types?: Types<Input, Output> | undefined;\n }\n\n export type Result<Output> = SuccessResult<Output> | FailureResult;\n\n export interface SuccessResult<Output> {\n readonly value: Output;\n readonly issues?: undefined;\n }\n\n export interface FailureResult {\n readonly issues: ReadonlyArray<Issue>;\n }\n\n export interface Issue {\n readonly message: string;\n readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;\n }\n\n export interface PathSegment {\n readonly key: PropertyKey;\n }\n\n export interface Types<Input = unknown, Output = Input> {\n readonly input: Input;\n readonly output: Output;\n }\n\n export type InferInput<Schema extends StandardSchemaV1> = NonNullable<\n Schema[\"~standard\"][\"types\"]\n >[\"input\"];\n\n export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<\n Schema[\"~standard\"][\"types\"]\n >[\"output\"];\n}\n\n/**\n * Thrown (as an `error` event payload) when an inbound message fails the\n * configured schema. The message is **not** emitted — handlers only ever see\n * data that passed validation.\n */\nexport class SchemaValidationError extends Error {\n readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;\n\n constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n super(\n `Inbound message failed schema validation: ${issues\n .map((issue) => issue.message)\n .join(\"; \")}`\n );\n this.name = \"SchemaValidationError\";\n this.issues = issues;\n }\n}\n","import { computeDelay, resolveReconnect } from \"@/backoff\";\nimport { jsonCodec } from \"@/codec\";\nimport { nextState } from \"@/fsm\";\nimport { HEARTBEAT_TIMEOUT_CODE, resolveHeartbeat } from \"@/heartbeat\";\nimport { defineEventBus } from \"@/helpers/event-bus\";\nimport { runPipeline } from \"@/pipeline\";\nimport { resolveQueue } from \"@/queue\";\nimport { SchemaValidationError, type StandardSchemaV1 } from \"@/schema\";\nimport type {\n ClientEventMap,\n ClientState,\n ConnectionEvent,\n ConnectionState,\n DirectionalMiddleware,\n Middleware,\n OutboundMiddleware,\n WebSocketClient,\n WebSocketClientConfig\n} from \"@/types\";\n\n/**\n * Creates a WebSocket client driven by an explicit connection FSM.\n *\n * Durable by default: an unexpected disconnect schedules a reconnect with\n * full-jitter exponential backoff (see `backoff.ts`); pass `reconnect: false`\n * to opt out.\n *\n * @param config - Connection configuration (at minimum, a `url`).\n *\n * @example\n * ```typescript\n * const ws = client({ url: \"wss://example.com/socket\" });\n *\n * ws.on(\"message\", (data) => console.log(\"received:\", data));\n * ws.on(\"reconnecting\", ({ attempt, delay }) => {\n * console.log(`retry #${attempt} in ${Math.round(delay)}ms`);\n * });\n *\n * await ws.connect();\n * ws.send({ type: \"hello\", message: \"world\" });\n *\n * ws.close();\n * ```\n */\nexport function client(config: WebSocketClientConfig): WebSocketClient {\n const bus = defineEventBus();\n const codec = config.codec ?? jsonCodec;\n const reconnect = resolveReconnect(config.reconnect);\n const queue = resolveQueue(config.queue);\n const heartbeat = resolveHeartbeat(config.heartbeat);\n const middlewares: Middleware[] = [];\n const outboundMiddlewares: OutboundMiddleware[] = [];\n\n // Outbound messages awaiting an open socket, in send() order. Original\n // (un-encoded) values: encoding happens at flush, so a drop event can hand\n // the user back exactly what they passed to send().\n const queued: unknown[] = [];\n\n let socket: WebSocket | null = null;\n let state: ConnectionState = \"idle\";\n let lastError: Event | Error | null = null;\n\n // Heartbeat bookkeeping: when the last inbound frame (of any kind)\n // arrived, the ping interval, and the per-ping liveness deadline.\n let lastInboundAt = 0;\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n let heartbeatDeadline: ReturnType<typeof setTimeout> | null = null;\n\n // The serialized outbound path (in play only when outbound middleware is\n // registered). `outboundTail` is the tail of the in-flight chain: while\n // set, every new message chains behind it, so socket writes keep send()\n // order even when a middleware awaits. `requeueIndex` keeps messages that\n // were mid-pipeline when the connection dropped ahead of newer queued\n // sends; it resets on every socket close.\n let outboundTail: Promise<void> | null = null;\n let requeueIndex = 0;\n\n // True between a user close() and the next connect(); suppresses\n // reconnection so \"the user hung up\" is never retried.\n let closeRequested = false;\n // Retries used in the current disconnection episode (see ClientState).\n let retryAttempt = 0;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n // subscribe() listeners plus the cached getState() snapshot. The cache\n // makes snapshots referentially stable between changes — required by\n // React's useSyncExternalStore (a fresh object per getSnapshot() call\n // would loop the renderer).\n const subscribers = new Set<() => void>();\n let snapshot: ClientState | null = null;\n\n // The in-flight connect() promise and its settlers. A single promise is\n // shared across concurrent/repeat connect() calls so the method is\n // idempotent, and it survives failed attempts while reconnection is\n // active — it settles only on first open or terminal failure.\n let pending: {\n readonly promise: Promise<void>;\n readonly resolve: () => void;\n readonly reject: (reason: Error) => void;\n } | null = null;\n\n /**\n * Applies an FSM event. Legal transitions update the state and emit\n * `statechange`; illegal ones are ignored (the table is the guard).\n *\n * @returns the new state if a transition occurred, otherwise `null`.\n */\n /**\n * Invalidates the cached snapshot and fires subscribe() listeners. Called\n * after every mutation of the observable state, before the corresponding\n * bus event, so any observer reading getState() sees fresh data.\n */\n function notify() {\n snapshot = null;\n for (const listener of [...subscribers]) {\n listener();\n }\n }\n\n function transition(event: ConnectionEvent): ConnectionState | null {\n const next = nextState(state, event);\n if (next === null || next === state) {\n return null;\n }\n\n const previous = state;\n state = next;\n notify();\n bus.emit(\"statechange\", { previous, current: next });\n return next;\n }\n\n function settleConnected() {\n pending?.resolve();\n pending = null;\n }\n\n function settleFailed(reason: Error) {\n pending?.reject(reason);\n pending = null;\n }\n\n function clearRetryTimer() {\n if (retryTimer !== null) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n }\n\n function stopHeartbeat() {\n if (heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n if (heartbeatDeadline !== null) {\n clearTimeout(heartbeatDeadline);\n heartbeatDeadline = null;\n }\n }\n\n /**\n * While open, sends the heartbeat message every `interval` ms and arms a\n * liveness deadline after each ping. Any inbound frame before the deadline\n * proves the link; none means it's dead — force-close with code 4408,\n * which flows into the normal reconnect machinery (the close is not\n * user-initiated, so it is retryable).\n */\n function startHeartbeat() {\n if (heartbeat === null) {\n return;\n }\n stopHeartbeat();\n heartbeatTimer = setInterval(() => {\n if (state !== \"open\" || !socket) {\n return;\n }\n const pingSentAt = Date.now();\n try {\n socket.send(codec.encode(heartbeat.message));\n } catch (error) {\n bus.emit(\"error\", toError(error));\n }\n if (heartbeatDeadline !== null) {\n clearTimeout(heartbeatDeadline);\n }\n heartbeatDeadline = setTimeout(() => {\n heartbeatDeadline = null;\n if (state === \"open\" && socket && lastInboundAt < pingSentAt) {\n const failure = new Error(\n `Heartbeat timeout: no inbound traffic within ${heartbeat.timeout}ms of a ping`\n );\n lastError = failure;\n notify();\n bus.emit(\"error\", failure);\n socket.close(HEARTBEAT_TIMEOUT_CODE, \"heartbeat timeout\");\n }\n }, heartbeat.timeout);\n }, heartbeat.interval);\n }\n\n /**\n * Final stage of the outbound pipeline: encode the (possibly transformed)\n * message and write it to the socket. The connection may have changed\n * while middleware awaited, so re-check here: if a retry is underway, the\n * *original* value is re-queued ahead of newer sends (middleware re-runs\n * at the next transmission, keeping e.g. tokens fresh); on a dead-end\n * state it surfaces as a `drop` — never silently lost.\n */\n function wire(transformed: unknown, original: unknown) {\n if (socket && state === \"open\") {\n socket.send(codec.encode(transformed));\n return;\n }\n if (\n queue !== null &&\n (state === \"connecting\" || state === \"reconnecting\")\n ) {\n if (queued.length >= queue.maxSize) {\n bus.emit(\"drop\", {\n data: queued.shift(),\n reason: \"overflow\"\n });\n if (requeueIndex > 0) {\n requeueIndex -= 1;\n }\n }\n queued.splice(requeueIndex, 0, original);\n requeueIndex += 1;\n notify();\n return;\n }\n bus.emit(\"drop\", { data: original, reason: \"close\" });\n }\n\n /**\n * Runs one message through the outbound middleware chain. Errors are\n * per-message: a throw/rejection surfaces as `error` and only this\n * message is skipped.\n */\n function runOutbound(data: unknown): void | Promise<void> {\n const ctx = { data, client: api };\n try {\n const result = runPipeline(outboundMiddlewares, ctx, () => {\n wire(ctx.data, data);\n });\n if (result instanceof Promise) {\n return result.catch((error: unknown) => {\n bus.emit(\"error\", toError(error));\n });\n }\n } catch (error) {\n bus.emit(\"error\", toError(error));\n }\n }\n\n /**\n * Sends a message through the outbound middleware pipeline, preserving\n * send() order: while any message is in flight (async middleware), newer\n * messages chain behind it. With no middleware in flight and an all-sync\n * chain, the path stays fully synchronous.\n */\n function transmit(data: unknown) {\n if (outboundTail === null) {\n const result = runOutbound(data);\n if (result instanceof Promise) {\n setOutboundTail(result);\n }\n return;\n }\n setOutboundTail(outboundTail.then(() => runOutbound(data)));\n }\n\n function setOutboundTail(run: Promise<void>) {\n const tail = run.then(() => {\n // Chain drained: restore the synchronous fast path.\n if (outboundTail === tail) {\n outboundTail = null;\n }\n });\n outboundTail = tail;\n }\n\n /**\n * Sends every queued message in order. Called once the socket opens,\n * *before* the `open` event, so the backlog (sent earlier in time)\n * precedes anything an `open` handler sends — under async outbound\n * middleware \"precedes\" means pipeline order; socket writes follow it.\n * A per-message encode/send failure surfaces as an `error` event and\n * flushing continues.\n */\n function flushQueue() {\n const hadQueued = queued.length > 0;\n requeueIndex = 0;\n while (queued.length > 0 && socket && state === \"open\") {\n const data = queued.shift();\n if (outboundMiddlewares.length === 0) {\n try {\n socket.send(codec.encode(data));\n } catch (error) {\n bus.emit(\"error\", toError(error));\n }\n } else {\n transmit(data);\n }\n }\n if (hadQueued) {\n notify();\n }\n }\n\n /**\n * Empties the queue as `drop` events — these messages will never be sent\n * (user `close()` or terminal failure). Never silently lossy.\n */\n function dropQueued() {\n const hadQueued = queued.length > 0;\n while (queued.length > 0) {\n bus.emit(\"drop\", { data: queued.shift(), reason: \"close\" });\n }\n if (hadQueued) {\n notify();\n }\n }\n\n /**\n * Whether an unexpected close should be retried: reconnection enabled, not\n * a user `close()`, retries left, and no `shouldReconnect` veto.\n */\n function isRetryable(event: CloseEvent): boolean {\n return (\n reconnect !== null &&\n !closeRequested &&\n retryAttempt < reconnect.maxRetries &&\n reconnect.shouldReconnect(event)\n );\n }\n\n function openSocket() {\n if (lastError !== null) {\n lastError = null;\n notify();\n }\n socket = config.protocols\n ? new WebSocket(config.url, config.protocols)\n : new WebSocket(config.url);\n if (config.binaryType) {\n socket.binaryType = config.binaryType;\n }\n\n socket.onopen = () => {\n retryAttempt = 0;\n transition(\"OPEN\");\n flushQueue();\n startHeartbeat();\n bus.emit(\"open\");\n settleConnected();\n };\n\n socket.onmessage = (event: MessageEvent) => {\n lastInboundAt = Date.now();\n deliver(event.data);\n };\n\n socket.onerror = (event: Event) => {\n lastError = event;\n notify();\n bus.emit(\"error\", event);\n };\n\n socket.onclose = (event: CloseEvent) => {\n const wasConnecting = state === \"connecting\";\n socket = null;\n stopHeartbeat();\n // A new disconnected period begins: in-flight outbound messages\n // re-queue from the front (see wire()).\n requeueIndex = 0;\n\n if (reconnect !== null && isRetryable(event)) {\n // Event order is deliberate: statechange (state already moved\n // to `reconnecting`), then the close that caused it, then the\n // retry announcement with attempt + delay.\n retryAttempt += 1;\n const delay = computeDelay(retryAttempt - 1, reconnect);\n transition(\"RETRY\");\n bus.emit(\"close\", event);\n bus.emit(\"reconnecting\", { attempt: retryAttempt, delay });\n retryTimer = setTimeout(() => {\n retryTimer = null;\n if (transition(\"CONNECT\") !== null) {\n openSocket();\n }\n }, delay);\n return; // pending connect() survives the retry\n }\n\n transition(\"CLOSED\");\n dropQueued();\n bus.emit(\"close\", event);\n // Terminal for any in-flight connect(): closed before first open\n // with no (further) retries coming.\n if (wasConnecting || pending) {\n settleFailed(\n new Error(\n retryAttempt > 0\n ? `WebSocket reconnect gave up after ${retryAttempt} attempt(s) (code ${event.code})`\n : `WebSocket closed before opening (code ${event.code}${\n event.reason ? `: ${event.reason}` : \"\"\n })`\n )\n );\n }\n };\n }\n\n /**\n * Runs a decoded — and, if a schema is configured, validated — inbound\n * message through the middleware chain. If the chain completes, the\n * message is emitted as `message`; a middleware that throws or rejects\n * surfaces as an `error`. Validation precedes middleware, so middleware\n * only ever sees trusted data; an invalid message surfaces as an `error`\n * (`SchemaValidationError`) and never reaches middleware or `message`.\n */\n function deliver(raw: unknown) {\n const decoded = codec.decode(raw);\n if (config.schema === undefined) {\n dispatchMessage(decoded);\n return;\n }\n let result: ReturnType<StandardSchemaV1[\"~standard\"][\"validate\"]>;\n try {\n result = config.schema[\"~standard\"].validate(decoded);\n } catch (error) {\n bus.emit(\"error\", toError(error));\n return;\n }\n if (result instanceof Promise) {\n result.then(handleValidated, (error: unknown) => {\n bus.emit(\"error\", toError(error));\n });\n return;\n }\n handleValidated(result);\n }\n\n function handleValidated(result: StandardSchemaV1.Result<unknown>) {\n if (result.issues) {\n bus.emit(\"error\", new SchemaValidationError(result.issues));\n return;\n }\n dispatchMessage(result.value);\n }\n\n function dispatchMessage(data: unknown) {\n const ctx = { data, client: api };\n const emit = () => {\n bus.emit(\"message\", ctx.data);\n };\n try {\n const result = runPipeline(middlewares, ctx, emit);\n if (result instanceof Promise) {\n result.catch((error: unknown) => {\n bus.emit(\"error\", toError(error));\n });\n }\n } catch (error) {\n bus.emit(\"error\", toError(error));\n }\n }\n\n /** Lazily create (or reuse) the shared connect() promise. */\n function ensurePending(): Promise<void> {\n if (pending) {\n return pending.promise;\n }\n let resolve!: () => void;\n let reject!: (reason: Error) => void;\n const promise = new Promise<void>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n pending = { promise, resolve, reject };\n return promise;\n }\n\n const api: WebSocketClient = {\n get state() {\n return state;\n },\n\n connect() {\n if (state === \"open\") {\n return Promise.resolve();\n }\n if (state === \"connecting\") {\n return ensurePending();\n }\n if (state === \"closing\") {\n return Promise.reject(\n new Error(\n \"Cannot connect() while the connection is closing\"\n )\n );\n }\n if (state === \"reconnecting\") {\n // Skip the rest of the backoff wait and attempt now.\n clearRetryTimer();\n const promise = ensurePending();\n transition(\"CONNECT\");\n openSocket();\n return promise;\n }\n\n // idle or closed → start a fresh episode.\n closeRequested = false;\n retryAttempt = 0;\n if (transition(\"CONNECT\") === null) {\n return Promise.reject(\n new Error(`Cannot connect() from state \"${state}\"`)\n );\n }\n\n const promise = ensurePending();\n try {\n openSocket();\n } catch (error) {\n transition(\"CLOSED\");\n socket = null;\n settleFailed(toError(error));\n }\n\n return promise;\n },\n\n send(data: unknown) {\n if (socket && state === \"open\") {\n if (outboundMiddlewares.length === 0) {\n // No outbound middleware: today's direct path, including\n // synchronous encode errors thrown to the caller.\n socket.send(codec.encode(data));\n } else {\n transmit(data);\n }\n return;\n }\n // An open is coming (or being retried): queue, bounded drop-oldest.\n if (\n queue !== null &&\n (state === \"connecting\" || state === \"reconnecting\")\n ) {\n if (queued.length >= queue.maxSize) {\n bus.emit(\"drop\", {\n data: queued.shift(),\n reason: \"overflow\"\n });\n }\n queued.push(data);\n notify();\n return;\n }\n throw new Error(\n `Cannot send: connection is not open (state: \"${state}\")`\n );\n },\n\n close(code?: number, reason?: string) {\n closeRequested = true;\n clearRetryTimer();\n stopHeartbeat();\n if (retryAttempt !== 0) {\n retryAttempt = 0;\n notify();\n }\n // The user hung up: queued messages will never send. Surface them\n // now (deterministically), not when the socket finishes closing.\n dropQueued();\n\n if (state === \"reconnecting\") {\n // No socket exists while waiting out the backoff: go straight\n // to closed and terminate any in-flight connect().\n transition(\"CLOSE_REQUESTED\");\n settleFailed(\n new Error(\"close() called before the connection opened\")\n );\n return;\n }\n if (!socket) {\n return;\n }\n transition(\"CLOSE_REQUESTED\");\n socket.close(code, reason);\n },\n\n on<K extends keyof ClientEventMap>(\n event: K,\n handler: (payload: ClientEventMap[K]) => void\n ) {\n bus.on(event, handler);\n return () => bus.off(event, handler);\n },\n\n use(middleware: Middleware | DirectionalMiddleware) {\n if (typeof middleware === \"function\") {\n middlewares.push(middleware);\n return api;\n }\n if (middleware.inbound) {\n middlewares.push(middleware.inbound);\n }\n if (middleware.outbound) {\n outboundMiddlewares.push(middleware.outbound);\n }\n return api;\n },\n\n getState(): ClientState {\n if (snapshot === null) {\n snapshot = Object.freeze({\n state,\n lastError,\n retryAttempt,\n queueLength: queued.length\n });\n }\n return snapshot;\n },\n\n subscribe(listener: () => void) {\n subscribers.add(listener);\n return () => {\n subscribers.delete(listener);\n };\n }\n };\n\n return api;\n}\n\n/** Normalize an unknown thrown value into an `Error`. */\nfunction toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n","import type { Middleware } from \"@/types\";\n\n/**\n * Replies to a textual `\"ping\"` with `\"pong\"` and stops the message there\n * (it is not emitted as a `message` event). A common keepalive convention.\n *\n * Opt in explicitly — it is not registered by default:\n *\n * ```typescript\n * import { defineClient, pingpong } from \"durablews\";\n *\n * const ws = defineClient({ url }).use(pingpong);\n * ```\n */\nexport const pingpong: Middleware = (ctx, next) => {\n if (ctx.data === \"ping\") {\n ctx.client.send(\"pong\");\n return;\n }\n return next();\n};\n","import { client } from \"@/client\";\nimport type { StandardSchemaV1 } from \"@/schema\";\nimport type { WebSocketClient, WebSocketClientConfig } from \"@/types\";\n\n/**\n * Creates a new {@link WebSocketClient}.\n *\n * Each call returns an independent client — there is no shared singleton.\n *\n * Inbound message typing, three ways:\n * - **Schema (recommended):** pass a [Standard Schema](https://standardschema.dev)\n * (zod, valibot, arktype, …) and the message type is inferred — plus every\n * inbound message is validated at runtime.\n * - **Generics:** `defineClient<Incoming, Outgoing>(config)` for types without\n * runtime validation.\n * - **Neither:** messages are `unknown`.\n *\n * @example\n * ```typescript\n * const Message = z.object({ type: z.string(), body: z.string() });\n * const ws = defineClient({ url: \"wss://example.com/socket\", schema: Message });\n *\n * ws.on(\"message\", (msg) => {\n * // msg is { type: string; body: string } — validated at runtime\n * });\n * await ws.connect();\n * ```\n */\nexport function defineClient<TSchema extends StandardSchemaV1>(\n config: WebSocketClientConfig & { readonly schema: TSchema }\n): WebSocketClient<StandardSchemaV1.InferOutput<TSchema>>;\nexport function defineClient<TIn = unknown, TOut = unknown>(\n config: WebSocketClientConfig\n): WebSocketClient<TIn, TOut>;\nexport function defineClient(config: WebSocketClientConfig): WebSocketClient {\n return client(config);\n}\n\nexport { RECONNECT_DEFAULTS } from \"@/backoff\";\nexport { jsonCodec } from \"@/codec\";\nexport { HEARTBEAT_TIMEOUT_CODE } from \"@/heartbeat\";\nexport { pingpong } from \"@/middleware\";\nexport { QUEUE_DEFAULTS } from \"@/queue\";\nexport type { StandardSchemaV1 } from \"@/schema\";\nexport { SchemaValidationError } from \"@/schema\";\nexport type {\n ClientEventMap,\n ClientState,\n Codec,\n ConnectionState,\n DirectionalMiddleware,\n DropEvent,\n HeartbeatOptions,\n MessageContext,\n Middleware,\n OutboundContext,\n OutboundMiddleware,\n QueueOptions,\n ReconnectingEvent,\n ReconnectOptions,\n StateChange,\n WebSocketClient,\n WebSocketClientConfig\n} from \"@/types\";\n"]}
1
+ {"version":3,"sources":["../src/backoff.ts","../src/codec.ts","../src/fsm.ts","../src/heartbeat.ts","../src/helpers/event-bus.ts","../src/pipeline.ts","../src/queue.ts","../src/schema.ts","../src/client.ts","../src/middleware/pingpong.ts","../src/index.ts"],"names":["promise"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ReconnectOptions } from \"@/types\";\n\n/** `ReconnectOptions` with every field filled in. */\nexport type ResolvedReconnect = Required<ReconnectOptions>;\n\n/**\n * The durable-by-default reconnection policy: full-jitter exponential backoff,\n * never give up. See the RFC's M3 decisions for the rationale behind each value.\n */\nexport const RECONNECT_DEFAULTS: ResolvedReconnect = {\n baseDelay: 500,\n factor: 2,\n maxDelay: 30_000,\n jitter: true,\n maxRetries: Number.POSITIVE_INFINITY,\n shouldReconnect: () => true\n};\n\n/**\n * Resolve the user's `reconnect` config: `false` disables reconnection\n * entirely (`null`), anything else is merged over the defaults.\n */\nexport function resolveReconnect(\n option: false | ReconnectOptions | undefined\n): ResolvedReconnect | null {\n if (option === false) {\n return null;\n }\n return { ...RECONNECT_DEFAULTS, ...option };\n}\n\n/**\n * Compute the backoff delay for a retry.\n *\n * The exponential delay is `baseDelay × factorᵃᵗᵗᵉᵐᵖᵗ`, capped at `maxDelay`.\n * With jitter (the default), the actual delay is drawn uniformly from\n * `[0, exponential]` — \"full jitter\", which spreads a fleet of simultaneously\n * dropped clients across the whole window instead of letting them retry in\n * synchronized waves.\n *\n * @param attempt - 0-based: the first retry is attempt `0`.\n * @param random - injectable for deterministic tests; defaults to `Math.random`.\n */\nexport function computeDelay(\n attempt: number,\n options: ResolvedReconnect,\n random: () => number = Math.random\n): number {\n const exponential = Math.min(\n options.maxDelay,\n options.baseDelay * options.factor ** attempt\n );\n return options.jitter ? random() * exponential : exponential;\n}\n","import type { Codec } from \"@/types\";\n\n/**\n * Parse a string as JSON, falling back to the raw string when it isn't valid\n * JSON. Lets text protocols that mix JSON and plain strings work unchanged.\n */\nfunction safeJSONParse(data: string): unknown {\n try {\n return JSON.parse(data);\n } catch {\n return data;\n }\n}\n\n/**\n * Whether a value is already a WebSocket-sendable binary frame and should be\n * passed through rather than JSON-encoded (`ArrayBuffer`, any typed array or\n * `DataView`, or a `Blob`).\n *\n * The predicate claims `BufferSource` (what `WebSocket.send` accepts), which\n * asserts the view is not backed by a `SharedArrayBuffer` — `send` rejects\n * those at runtime, so they were never sendable anyway.\n */\nfunction isBinary(data: unknown): data is BufferSource | Blob {\n return (\n data instanceof ArrayBuffer ||\n ArrayBuffer.isView(data) ||\n (typeof Blob !== \"undefined\" && data instanceof Blob)\n );\n}\n\n/**\n * The default codec: JSON over text frames, binary passed through.\n *\n * - `encode` — strings and binary frames (`ArrayBuffer`/typed arrays/`Blob`)\n * are sent verbatim; everything else is `JSON.stringify`d.\n * - `decode` — text frames are JSON-parsed (falling back to the raw string);\n * binary frames are passed through untouched.\n */\nexport const jsonCodec: Codec = {\n encode(data: unknown) {\n if (typeof data === \"string\" || isBinary(data)) {\n return data;\n }\n return JSON.stringify(data);\n },\n decode(data: unknown) {\n return typeof data === \"string\" ? safeJSONParse(data) : data;\n }\n};\n","import type { ConnectionEvent, ConnectionState } from \"@/types\";\n\n/**\n * The connection lifecycle as an explicit finite state machine.\n *\n * A connection has a small, fixed set of legal states and transitions. Modelling\n * it as a table — rather than a free-form reducer — means an illegal transition\n * (e.g. a `CLOSED` event arriving while already `closed`) is *representable as the\n * absence of an entry* and can be rejected, instead of silently doing nothing.\n * That silent no-op is exactly what allowed the original close/error bug.\n *\n * `ERROR` is deliberately absent from the table: a transport error does not by\n * itself change the connection state (the browser/runtime fires `error` and then\n * `close`), so it is handled out-of-band by the client (record + emit) and the\n * following `CLOSED` event performs the actual transition.\n */\nconst TRANSITIONS: Record<\n ConnectionState,\n Partial<Record<ConnectionEvent, ConnectionState>>\n> = {\n idle: { CONNECT: \"connecting\" },\n connecting: {\n OPEN: \"open\",\n CLOSE_REQUESTED: \"closing\",\n CLOSED: \"closed\",\n RETRY: \"reconnecting\"\n },\n open: {\n CLOSE_REQUESTED: \"closing\",\n CLOSED: \"closed\",\n RETRY: \"reconnecting\"\n },\n closing: { CLOSED: \"closed\" },\n // `reconnecting` = waiting out the backoff delay; no socket exists, so\n // CLOSE_REQUESTED goes straight to `closed` (there is nothing to wait for)\n // and OPEN/CLOSED cannot legally occur.\n reconnecting: {\n CONNECT: \"connecting\",\n CLOSE_REQUESTED: \"closed\"\n },\n closed: { CONNECT: \"connecting\" }\n};\n\n/**\n * Resolve the next state for an `(state, event)` pair.\n *\n * @returns the destination state, or `null` if the transition is illegal.\n */\nexport function nextState(\n state: ConnectionState,\n event: ConnectionEvent\n): ConnectionState | null {\n return TRANSITIONS[state][event] ?? null;\n}\n\n/**\n * Whether `(state, event)` is a legal transition.\n */\nexport function canTransition(\n state: ConnectionState,\n event: ConnectionEvent\n): boolean {\n return nextState(state, event) !== null;\n}\n","import type { HeartbeatOptions } from \"@/types\";\n\n/** `HeartbeatOptions` with every field filled in. */\nexport interface ResolvedHeartbeat {\n readonly interval: number;\n readonly message: unknown;\n readonly timeout: number;\n}\n\n/**\n * The close code used when the heartbeat declares the link dead (app-reserved\n * 4xxx range, mnemonic for HTTP 408 Request Timeout). Useful in a custom\n * `shouldReconnect` to distinguish heartbeat closes from server closes.\n */\nexport const HEARTBEAT_TIMEOUT_CODE = 4408;\n\n/**\n * Resolve the user's `heartbeat` config. Heartbeat is **opt-in**: absence means\n * off (`null`) — a naive \"no traffic → reconnect\" would kill legitimately\n * quiet-but-healthy connections, and any ping depends on app-level semantics\n * the library can't assume (see the RFC's M3 decisions).\n */\nexport function resolveHeartbeat(\n option: HeartbeatOptions | undefined\n): ResolvedHeartbeat | null {\n if (option === undefined) {\n return null;\n }\n return {\n interval: option.interval,\n message: option.message ?? \"ping\",\n timeout: option.timeout ?? option.interval\n };\n}\n","import type { EventBus } from \"@/types\";\n\n/**\n * Creates an event bus for managing event subscriptions and emissions.\n * Provides a simple pub/sub pattern with type-safe event handling.\n *\n * @returns EventBus instance with on, off, emit, and once methods\n *\n * @example\n * ```typescript\n * const eventBus = defineEventBus();\n *\n * // Subscribe to events\n * eventBus.on('user-login', (user) => {\n * console.log('User logged in:', user);\n * });\n *\n * // Emit events\n * eventBus.emit('user-login', { id: 1, name: 'John' });\n *\n * // One-time subscription\n * eventBus.once('app-ready', () => {\n * console.log('App is ready!');\n * });\n * ```\n */\nexport function defineEventBus(): EventBus {\n const listeners = new Map<string, Array<(payload: unknown) => void>>();\n\n /**\n * Subscribes to an event with a handler function.\n *\n * @template T - The type of the event payload\n * @param eventName - The name of the event to listen for\n * @param handler - Function to call when the event is emitted\n */\n function on<T = unknown>(\n eventName: string,\n handler: (payload: T) => void\n ): void {\n const handlers = listeners.get(eventName) ?? [];\n handlers.push(handler as (payload: unknown) => void);\n listeners.set(eventName, handlers);\n }\n\n /**\n * Unsubscribes a handler from an event.\n *\n * @template T - The type of the event payload\n * @param eventName - The name of the event to stop listening for\n * @param handler - The handler function to remove\n */\n function off<T = unknown>(\n eventName: string,\n handler: (payload: T) => void\n ) {\n const handlers = listeners.get(eventName);\n if (!handlers) {\n return;\n }\n listeners.set(\n eventName,\n handlers.filter((h) => h !== handler)\n );\n }\n\n /**\n * Subscribes to an event with a handler that will only be called once.\n * The handler is automatically removed after the first emission.\n *\n * @template T - The type of the event payload\n * @param eventName - The name of the event to listen for\n * @param handler - Function to call when the event is emitted (only once)\n */\n function once<T = unknown>(\n eventName: string,\n handler: (payload: T) => void\n ): void {\n const onceHandler = (payload: T) => {\n // Remove the handler after first invocation\n off(eventName, onceHandler as (payload: unknown) => void);\n // Call the original handler\n handler(payload);\n };\n\n // Add the wrapper handler\n on(eventName, onceHandler);\n }\n\n /**\n * Emits an event to all subscribed handlers.\n *\n * @template T - The type of the event payload\n * @param eventName - The name of the event to emit\n * @param payload - The data to pass to event handlers\n */\n function emit<T = unknown>(eventName: string, payload?: T) {\n const handlers = listeners.get(eventName);\n handlers?.forEach((fn) => {\n fn(payload);\n });\n }\n\n return { on, off, emit, once };\n}\n","/**\n * The functional shape shared by inbound and outbound middleware: receive a\n * context and a `next`, optionally async. The context type is the only thing\n * that differs per direction.\n */\ntype PipelineMiddleware<TContext> = (\n ctx: TContext,\n next: () => void | Promise<void>\n) => void | Promise<void>;\n\n/**\n * Runs a message through a middleware chain (onion model).\n *\n * Each middleware receives the shared context and a `next` it may call to pass\n * control onward. When the chain runs to completion, `terminal` is invoked —\n * for inbound messages that is where the client emits `message`; for outbound,\n * where it encodes and writes to the socket. A middleware that returns without\n * calling `next()` short-circuits the chain (and `terminal`).\n *\n * Mirrors the guard from the old store pipeline: calling `next()` more than once\n * in a single middleware is a bug and throws.\n *\n * @returns `void`, or a `Promise` if any middleware in the chain is async.\n */\nexport function runPipeline<TContext>(\n middlewares: readonly PipelineMiddleware<TContext>[],\n ctx: TContext,\n terminal: () => void\n): void | Promise<void> {\n let invoked = -1;\n\n function dispatch(i: number): void | Promise<void> {\n if (i <= invoked) {\n throw new Error(\"next() called multiple times\");\n }\n invoked = i;\n\n const middleware = middlewares[i];\n if (!middleware) {\n return terminal();\n }\n return middleware(ctx, () => dispatch(i + 1));\n }\n\n return dispatch(0);\n}\n","import type { QueueOptions } from \"@/types\";\n\n/** `QueueOptions` with every field filled in. */\nexport type ResolvedQueue = Required<QueueOptions>;\n\n/**\n * The default outbound-queue policy: bounded at 256 messages, drop-oldest.\n * Never silently unbounded (a memory leak), never silently lossy (every drop\n * emits a `drop` event). See the RFC's M3 decisions.\n */\nexport const QUEUE_DEFAULTS: ResolvedQueue = {\n maxSize: 256\n};\n\n/**\n * Resolve the user's `queue` config: `false` disables queueing entirely\n * (`null`), anything else is merged over the defaults.\n */\nexport function resolveQueue(\n option: false | QueueOptions | undefined\n): ResolvedQueue | null {\n if (option === false) {\n return null;\n }\n return { ...QUEUE_DEFAULTS, ...option };\n}\n","/**\n * The Standard Schema v1 interface (https://standardschema.dev), vendored as\n * types-only per the spec's guidance — implementing libraries (zod, valibot,\n * arktype, …) conform to this shape, so DurableWS can accept any of them\n * without depending on any of them. Keeps core zero-dependency.\n */\nexport interface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly \"~standard\": StandardSchemaV1.Props<Input, Output>;\n}\n\nexport declare namespace StandardSchemaV1 {\n export interface Props<Input = unknown, Output = Input> {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (\n value: unknown\n ) => Result<Output> | Promise<Result<Output>>;\n readonly types?: Types<Input, Output> | undefined;\n }\n\n export type Result<Output> = SuccessResult<Output> | FailureResult;\n\n export interface SuccessResult<Output> {\n readonly value: Output;\n readonly issues?: undefined;\n }\n\n export interface FailureResult {\n readonly issues: ReadonlyArray<Issue>;\n }\n\n export interface Issue {\n readonly message: string;\n readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;\n }\n\n export interface PathSegment {\n readonly key: PropertyKey;\n }\n\n export interface Types<Input = unknown, Output = Input> {\n readonly input: Input;\n readonly output: Output;\n }\n\n export type InferInput<Schema extends StandardSchemaV1> = NonNullable<\n Schema[\"~standard\"][\"types\"]\n >[\"input\"];\n\n export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<\n Schema[\"~standard\"][\"types\"]\n >[\"output\"];\n}\n\n/**\n * Thrown (as an `error` event payload) when an inbound message fails the\n * configured schema. The message is **not** emitted — handlers only ever see\n * data that passed validation.\n */\nexport class SchemaValidationError extends Error {\n readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;\n\n constructor(issues: ReadonlyArray<StandardSchemaV1.Issue>) {\n super(\n `Inbound message failed schema validation: ${issues\n .map((issue) => issue.message)\n .join(\"; \")}`\n );\n this.name = \"SchemaValidationError\";\n this.issues = issues;\n }\n}\n","import { computeDelay, resolveReconnect } from \"@/backoff\";\nimport { jsonCodec } from \"@/codec\";\nimport { nextState } from \"@/fsm\";\nimport { HEARTBEAT_TIMEOUT_CODE, resolveHeartbeat } from \"@/heartbeat\";\nimport { defineEventBus } from \"@/helpers/event-bus\";\nimport { runPipeline } from \"@/pipeline\";\nimport { resolveQueue } from \"@/queue\";\nimport { SchemaValidationError, type StandardSchemaV1 } from \"@/schema\";\nimport type {\n ClientEventMap,\n ClientState,\n ConnectionEvent,\n ConnectionState,\n DirectionalMiddleware,\n Middleware,\n OutboundMiddleware,\n WebSocketClient,\n WebSocketClientConfig\n} from \"@/types\";\n\n/**\n * Creates a WebSocket client driven by an explicit connection FSM.\n *\n * Durable by default: an unexpected disconnect schedules a reconnect with\n * full-jitter exponential backoff (see `backoff.ts`); pass `reconnect: false`\n * to opt out.\n *\n * @param config - Connection configuration (at minimum, a `url`).\n *\n * @example\n * ```typescript\n * const ws = client({ url: \"wss://example.com/socket\" });\n *\n * ws.on(\"message\", (data) => console.log(\"received:\", data));\n * ws.on(\"reconnecting\", ({ attempt, delay }) => {\n * console.log(`retry #${attempt} in ${Math.round(delay)}ms`);\n * });\n *\n * await ws.connect();\n * ws.send({ type: \"hello\", message: \"world\" });\n *\n * ws.close();\n * ```\n */\nexport function client(config: WebSocketClientConfig): WebSocketClient {\n const bus = defineEventBus();\n const codec = config.codec ?? jsonCodec;\n const reconnect = resolveReconnect(config.reconnect);\n const queue = resolveQueue(config.queue);\n const heartbeat = resolveHeartbeat(config.heartbeat);\n const middlewares: Middleware[] = [];\n const outboundMiddlewares: OutboundMiddleware[] = [];\n\n // Outbound messages awaiting an open socket, in send() order. Original\n // (un-encoded) values: encoding happens at flush, so a drop event can hand\n // the user back exactly what they passed to send().\n const queued: unknown[] = [];\n\n let socket: WebSocket | null = null;\n let state: ConnectionState = \"idle\";\n let lastError: Event | Error | null = null;\n\n // Heartbeat bookkeeping: when the last inbound frame (of any kind)\n // arrived, the ping interval, and the per-ping liveness deadline.\n let lastInboundAt = 0;\n let heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n let heartbeatDeadline: ReturnType<typeof setTimeout> | null = null;\n\n // The serialized outbound path (in play only when outbound middleware is\n // registered). `outboundTail` is the tail of the in-flight chain: while\n // set, every new message chains behind it, so socket writes keep send()\n // order even when a middleware awaits. `requeueIndex` keeps messages that\n // were mid-pipeline when the connection dropped ahead of newer queued\n // sends; it resets on every socket close.\n let outboundTail: Promise<void> | null = null;\n let requeueIndex = 0;\n\n // True between a user close() and the next connect(); suppresses\n // reconnection so \"the user hung up\" is never retried.\n let closeRequested = false;\n // Retries used in the current disconnection episode (see ClientState).\n let retryAttempt = 0;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n // subscribe() listeners plus the cached getState() snapshot. The cache\n // makes snapshots referentially stable between changes — required by\n // React's useSyncExternalStore (a fresh object per getSnapshot() call\n // would loop the renderer).\n const subscribers = new Set<() => void>();\n let snapshot: ClientState | null = null;\n\n // The in-flight connect() promise and its settlers. A single promise is\n // shared across concurrent/repeat connect() calls so the method is\n // idempotent, and it survives failed attempts while reconnection is\n // active — it settles only on first open or terminal failure.\n let pending: {\n readonly promise: Promise<void>;\n readonly resolve: () => void;\n readonly reject: (reason: Error) => void;\n } | null = null;\n\n /**\n * Applies an FSM event. Legal transitions update the state and emit\n * `statechange`; illegal ones are ignored (the table is the guard).\n *\n * @returns the new state if a transition occurred, otherwise `null`.\n */\n /**\n * Invalidates the cached snapshot and fires subscribe() listeners. Called\n * after every mutation of the observable state, before the corresponding\n * bus event, so any observer reading getState() sees fresh data.\n */\n function notify() {\n snapshot = null;\n for (const listener of [...subscribers]) {\n listener();\n }\n }\n\n function transition(event: ConnectionEvent): ConnectionState | null {\n const next = nextState(state, event);\n if (next === null || next === state) {\n return null;\n }\n\n const previous = state;\n state = next;\n notify();\n bus.emit(\"statechange\", { previous, current: next });\n return next;\n }\n\n function settleConnected() {\n pending?.resolve();\n pending = null;\n }\n\n function settleFailed(reason: Error) {\n pending?.reject(reason);\n pending = null;\n }\n\n function clearRetryTimer() {\n if (retryTimer !== null) {\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n }\n\n function stopHeartbeat() {\n if (heartbeatTimer !== null) {\n clearInterval(heartbeatTimer);\n heartbeatTimer = null;\n }\n if (heartbeatDeadline !== null) {\n clearTimeout(heartbeatDeadline);\n heartbeatDeadline = null;\n }\n }\n\n /**\n * While open, sends the heartbeat message every `interval` ms and arms a\n * liveness deadline after each ping. Any inbound frame before the deadline\n * proves the link; none means it's dead — force-close with code 4408,\n * which flows into the normal reconnect machinery (the close is not\n * user-initiated, so it is retryable).\n */\n function startHeartbeat() {\n if (heartbeat === null) {\n return;\n }\n stopHeartbeat();\n heartbeatTimer = setInterval(() => {\n if (state !== \"open\" || !socket) {\n return;\n }\n const pingSentAt = Date.now();\n try {\n socket.send(codec.encode(heartbeat.message));\n } catch (error) {\n bus.emit(\"error\", toError(error));\n }\n if (heartbeatDeadline !== null) {\n clearTimeout(heartbeatDeadline);\n }\n heartbeatDeadline = setTimeout(() => {\n heartbeatDeadline = null;\n if (state === \"open\" && socket && lastInboundAt < pingSentAt) {\n const failure = new Error(\n `Heartbeat timeout: no inbound traffic within ${heartbeat.timeout}ms of a ping`\n );\n lastError = failure;\n notify();\n bus.emit(\"error\", failure);\n socket.close(HEARTBEAT_TIMEOUT_CODE, \"heartbeat timeout\");\n }\n }, heartbeat.timeout);\n }, heartbeat.interval);\n }\n\n /**\n * Final stage of the outbound pipeline: encode the (possibly transformed)\n * message and write it to the socket. The connection may have changed\n * while middleware awaited, so re-check here: if a retry is underway, the\n * *original* value is re-queued ahead of newer sends (middleware re-runs\n * at the next transmission, keeping e.g. tokens fresh); on a dead-end\n * state it surfaces as a `drop` — never silently lost.\n */\n function wire(transformed: unknown, original: unknown) {\n if (socket && state === \"open\") {\n socket.send(codec.encode(transformed));\n return;\n }\n if (\n queue !== null &&\n (state === \"connecting\" || state === \"reconnecting\")\n ) {\n if (queued.length >= queue.maxSize) {\n bus.emit(\"drop\", {\n data: queued.shift(),\n reason: \"overflow\"\n });\n if (requeueIndex > 0) {\n requeueIndex -= 1;\n }\n }\n queued.splice(requeueIndex, 0, original);\n requeueIndex += 1;\n notify();\n return;\n }\n bus.emit(\"drop\", { data: original, reason: \"close\" });\n }\n\n /**\n * Runs one message through the outbound middleware chain. Errors are\n * per-message: a throw/rejection surfaces as `error` and only this\n * message is skipped.\n */\n function runOutbound(data: unknown): void | Promise<void> {\n const ctx = { data, client: api };\n try {\n const result = runPipeline(outboundMiddlewares, ctx, () => {\n wire(ctx.data, data);\n });\n if (result instanceof Promise) {\n return result.catch((error: unknown) => {\n bus.emit(\"error\", toError(error));\n });\n }\n } catch (error) {\n bus.emit(\"error\", toError(error));\n }\n }\n\n /**\n * Sends a message through the outbound middleware pipeline, preserving\n * send() order: while any message is in flight (async middleware), newer\n * messages chain behind it. With no middleware in flight and an all-sync\n * chain, the path stays fully synchronous.\n */\n function transmit(data: unknown) {\n if (outboundTail === null) {\n const result = runOutbound(data);\n if (result instanceof Promise) {\n setOutboundTail(result);\n }\n return;\n }\n setOutboundTail(outboundTail.then(() => runOutbound(data)));\n }\n\n function setOutboundTail(run: Promise<void>) {\n const tail = run.then(() => {\n // Chain drained: restore the synchronous fast path.\n if (outboundTail === tail) {\n outboundTail = null;\n }\n });\n outboundTail = tail;\n }\n\n /**\n * Sends every queued message in order. Called once the socket opens,\n * *before* the `open` event, so the backlog (sent earlier in time)\n * precedes anything an `open` handler sends — under async outbound\n * middleware \"precedes\" means pipeline order; socket writes follow it.\n * A per-message encode/send failure surfaces as an `error` event and\n * flushing continues.\n */\n function flushQueue() {\n const hadQueued = queued.length > 0;\n requeueIndex = 0;\n while (queued.length > 0 && socket && state === \"open\") {\n const data = queued.shift();\n if (outboundMiddlewares.length === 0) {\n try {\n socket.send(codec.encode(data));\n } catch (error) {\n bus.emit(\"error\", toError(error));\n }\n } else {\n transmit(data);\n }\n }\n if (hadQueued) {\n notify();\n }\n }\n\n /**\n * Empties the queue as `drop` events — these messages will never be sent\n * (user `close()` or terminal failure). Never silently lossy.\n */\n function dropQueued() {\n const hadQueued = queued.length > 0;\n while (queued.length > 0) {\n bus.emit(\"drop\", { data: queued.shift(), reason: \"close\" });\n }\n if (hadQueued) {\n notify();\n }\n }\n\n /**\n * Whether an unexpected close should be retried: reconnection enabled, not\n * a user `close()`, retries left, and no `shouldReconnect` veto.\n */\n function isRetryable(event: CloseEvent): boolean {\n return (\n reconnect !== null &&\n !closeRequested &&\n retryAttempt < reconnect.maxRetries &&\n reconnect.shouldReconnect(event)\n );\n }\n\n function openSocket() {\n if (lastError !== null) {\n lastError = null;\n notify();\n }\n socket = config.protocols\n ? new WebSocket(config.url, config.protocols)\n : new WebSocket(config.url);\n if (config.binaryType) {\n socket.binaryType = config.binaryType;\n }\n\n socket.onopen = () => {\n retryAttempt = 0;\n transition(\"OPEN\");\n flushQueue();\n startHeartbeat();\n bus.emit(\"open\");\n settleConnected();\n };\n\n socket.onmessage = (event: MessageEvent) => {\n lastInboundAt = Date.now();\n deliver(event.data);\n };\n\n socket.onerror = (event: Event) => {\n lastError = event;\n notify();\n bus.emit(\"error\", event);\n };\n\n socket.onclose = (event: CloseEvent) => {\n const wasConnecting = state === \"connecting\";\n socket = null;\n stopHeartbeat();\n // A new disconnected period begins: in-flight outbound messages\n // re-queue from the front (see wire()).\n requeueIndex = 0;\n\n if (reconnect !== null && isRetryable(event)) {\n // Event order is deliberate: statechange (state already moved\n // to `reconnecting`), then the close that caused it, then the\n // retry announcement with attempt + delay.\n retryAttempt += 1;\n const delay = computeDelay(retryAttempt - 1, reconnect);\n transition(\"RETRY\");\n bus.emit(\"close\", event);\n bus.emit(\"reconnecting\", { attempt: retryAttempt, delay });\n retryTimer = setTimeout(() => {\n retryTimer = null;\n if (transition(\"CONNECT\") !== null) {\n openSocket();\n }\n }, delay);\n return; // pending connect() survives the retry\n }\n\n transition(\"CLOSED\");\n dropQueued();\n bus.emit(\"close\", event);\n // Terminal for any in-flight connect(): closed before first open\n // with no (further) retries coming.\n if (wasConnecting || pending) {\n settleFailed(\n new Error(\n retryAttempt > 0\n ? `WebSocket reconnect gave up after ${retryAttempt} attempt(s) (code ${event.code})`\n : `WebSocket closed before opening (code ${event.code}${\n event.reason ? `: ${event.reason}` : \"\"\n })`\n )\n );\n }\n };\n }\n\n /**\n * Runs a decoded — and, if a schema is configured, validated — inbound\n * message through the middleware chain. If the chain completes, the\n * message is emitted as `message`; a middleware that throws or rejects\n * surfaces as an `error`. Validation precedes middleware, so middleware\n * only ever sees trusted data; an invalid message surfaces as an `error`\n * (`SchemaValidationError`) and never reaches middleware or `message`.\n */\n function deliver(raw: unknown) {\n const decoded = codec.decode(raw);\n if (config.schema === undefined) {\n dispatchMessage(decoded);\n return;\n }\n let result: ReturnType<StandardSchemaV1[\"~standard\"][\"validate\"]>;\n try {\n result = config.schema[\"~standard\"].validate(decoded);\n } catch (error) {\n bus.emit(\"error\", toError(error));\n return;\n }\n if (result instanceof Promise) {\n result.then(handleValidated, (error: unknown) => {\n bus.emit(\"error\", toError(error));\n });\n return;\n }\n handleValidated(result);\n }\n\n function handleValidated(result: StandardSchemaV1.Result<unknown>) {\n if (result.issues) {\n bus.emit(\"error\", new SchemaValidationError(result.issues));\n return;\n }\n dispatchMessage(result.value);\n }\n\n function dispatchMessage(data: unknown) {\n const ctx = { data, client: api };\n const emit = () => {\n bus.emit(\"message\", ctx.data);\n };\n try {\n const result = runPipeline(middlewares, ctx, emit);\n if (result instanceof Promise) {\n result.catch((error: unknown) => {\n bus.emit(\"error\", toError(error));\n });\n }\n } catch (error) {\n bus.emit(\"error\", toError(error));\n }\n }\n\n /** Lazily create (or reuse) the shared connect() promise. */\n function ensurePending(): Promise<void> {\n if (pending) {\n return pending.promise;\n }\n let resolve!: () => void;\n let reject!: (reason: Error) => void;\n const promise = new Promise<void>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n pending = { promise, resolve, reject };\n return promise;\n }\n\n const api: WebSocketClient = {\n get state() {\n return state;\n },\n\n connect() {\n if (state === \"open\") {\n return Promise.resolve();\n }\n if (state === \"connecting\") {\n return ensurePending();\n }\n if (state === \"closing\") {\n return Promise.reject(\n new Error(\n \"Cannot connect() while the connection is closing\"\n )\n );\n }\n if (state === \"reconnecting\") {\n // Skip the rest of the backoff wait and attempt now.\n clearRetryTimer();\n const promise = ensurePending();\n transition(\"CONNECT\");\n openSocket();\n return promise;\n }\n\n // idle or closed → start a fresh episode.\n closeRequested = false;\n retryAttempt = 0;\n if (transition(\"CONNECT\") === null) {\n return Promise.reject(\n new Error(`Cannot connect() from state \"${state}\"`)\n );\n }\n\n const promise = ensurePending();\n try {\n openSocket();\n } catch (error) {\n transition(\"CLOSED\");\n socket = null;\n settleFailed(toError(error));\n }\n\n return promise;\n },\n\n send(data: unknown) {\n if (socket && state === \"open\") {\n if (outboundMiddlewares.length === 0) {\n // No outbound middleware: today's direct path, including\n // synchronous encode errors thrown to the caller.\n socket.send(codec.encode(data));\n } else {\n transmit(data);\n }\n return;\n }\n // An open is coming (or being retried): queue, bounded drop-oldest.\n if (\n queue !== null &&\n (state === \"connecting\" || state === \"reconnecting\")\n ) {\n if (queued.length >= queue.maxSize) {\n bus.emit(\"drop\", {\n data: queued.shift(),\n reason: \"overflow\"\n });\n }\n queued.push(data);\n notify();\n return;\n }\n throw new Error(\n `Cannot send: connection is not open (state: \"${state}\")`\n );\n },\n\n close(code?: number, reason?: string) {\n closeRequested = true;\n clearRetryTimer();\n stopHeartbeat();\n if (retryAttempt !== 0) {\n retryAttempt = 0;\n notify();\n }\n // The user hung up: queued messages will never send. Surface them\n // now (deterministically), not when the socket finishes closing.\n dropQueued();\n\n if (state === \"reconnecting\") {\n // No socket exists while waiting out the backoff: go straight\n // to closed and terminate any in-flight connect().\n transition(\"CLOSE_REQUESTED\");\n settleFailed(\n new Error(\"close() called before the connection opened\")\n );\n return;\n }\n if (!socket) {\n return;\n }\n transition(\"CLOSE_REQUESTED\");\n socket.close(code, reason);\n },\n\n on<K extends keyof ClientEventMap>(\n event: K,\n handler: (payload: ClientEventMap[K]) => void\n ) {\n bus.on(event, handler);\n return () => bus.off(event, handler);\n },\n\n use(middleware: Middleware | DirectionalMiddleware) {\n if (typeof middleware === \"function\") {\n middlewares.push(middleware);\n return api;\n }\n if (middleware.inbound) {\n middlewares.push(middleware.inbound);\n }\n if (middleware.outbound) {\n outboundMiddlewares.push(middleware.outbound);\n }\n return api;\n },\n\n getState(): ClientState {\n if (snapshot === null) {\n snapshot = Object.freeze({\n state,\n lastError,\n retryAttempt,\n queueLength: queued.length\n });\n }\n return snapshot;\n },\n\n subscribe(listener: () => void) {\n subscribers.add(listener);\n return () => {\n subscribers.delete(listener);\n };\n }\n };\n\n return api;\n}\n\n/** Normalize an unknown thrown value into an `Error`. */\nfunction toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n","import type { Middleware } from \"@/types\";\n\n/**\n * Replies to a textual `\"ping\"` with `\"pong\"` and stops the message there\n * (it is not emitted as a `message` event). A common keepalive convention.\n *\n * Opt in explicitly, it is not registered by default:\n *\n * ```typescript\n * import { defineClient, pingpong } from \"durablews\";\n *\n * const ws = defineClient({ url }).use(pingpong);\n * ```\n */\nexport const pingpong: Middleware = (ctx, next) => {\n if (ctx.data === \"ping\") {\n ctx.client.send(\"pong\");\n return;\n }\n return next();\n};\n","import { client } from \"@/client\";\nimport type { StandardSchemaV1 } from \"@/schema\";\nimport type { WebSocketClient, WebSocketClientConfig } from \"@/types\";\n\n/**\n * Creates a new {@link WebSocketClient}.\n *\n * Each call returns an independent client — there is no shared singleton.\n *\n * Inbound message typing, three ways:\n * - **Schema (recommended):** pass a [Standard Schema](https://standardschema.dev)\n * (zod, valibot, arktype, …) and the message type is inferred — plus every\n * inbound message is validated at runtime.\n * - **Generics:** `defineClient<Incoming, Outgoing>(config)` for types without\n * runtime validation.\n * - **Neither:** messages are `unknown`.\n *\n * @example\n * ```typescript\n * const Message = z.object({ type: z.string(), body: z.string() });\n * const ws = defineClient({ url: \"wss://example.com/socket\", schema: Message });\n *\n * ws.on(\"message\", (msg) => {\n * // msg is { type: string; body: string } — validated at runtime\n * });\n * await ws.connect();\n * ```\n */\nexport function defineClient<TSchema extends StandardSchemaV1>(\n config: WebSocketClientConfig & { readonly schema: TSchema }\n): WebSocketClient<StandardSchemaV1.InferOutput<TSchema>>;\nexport function defineClient<TIn = unknown, TOut = unknown>(\n config: WebSocketClientConfig\n): WebSocketClient<TIn, TOut>;\nexport function defineClient(config: WebSocketClientConfig): WebSocketClient {\n return client(config);\n}\n\nexport { RECONNECT_DEFAULTS } from \"@/backoff\";\nexport { jsonCodec } from \"@/codec\";\nexport { HEARTBEAT_TIMEOUT_CODE } from \"@/heartbeat\";\nexport { pingpong } from \"@/middleware/pingpong\";\nexport { QUEUE_DEFAULTS } from \"@/queue\";\nexport type { StandardSchemaV1 } from \"@/schema\";\nexport { SchemaValidationError } from \"@/schema\";\nexport type {\n ClientEventMap,\n ClientState,\n Codec,\n ConnectionState,\n DirectionalMiddleware,\n DropEvent,\n HeartbeatOptions,\n MessageContext,\n Middleware,\n OutboundContext,\n OutboundMiddleware,\n QueueOptions,\n ReconnectingEvent,\n ReconnectOptions,\n StateChange,\n WebSocketClient,\n WebSocketClientConfig\n} from \"@/types\";\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { R as ReconnectOptions, C as Codec, M as Middleware, Q as QueueOptions, S as StandardSchemaV1, a as WebSocketClientConfig, W as WebSocketClient } from './types-BDvztGcG.cjs';
2
- export { b as ClientEventMap, c as ClientState, d as ConnectionState, D as DirectionalMiddleware, e as DropEvent, H as HeartbeatOptions, f as MessageContext, O as OutboundContext, g as OutboundMiddleware, h as ReconnectingEvent, i as SchemaValidationError, j as StateChange } from './types-BDvztGcG.cjs';
1
+ import { R as ReconnectOptions, C as Codec, Q as QueueOptions, S as StandardSchemaV1, a as WebSocketClientConfig, W as WebSocketClient } from './types-BgbiWEXo.cjs';
2
+ export { b as ClientEventMap, c as ClientState, d as ConnectionState, D as DirectionalMiddleware, e as DropEvent, H as HeartbeatOptions, M as MessageContext, f as Middleware, O as OutboundContext, g as OutboundMiddleware, h as ReconnectingEvent, i as SchemaValidationError, j as StateChange } from './types-BgbiWEXo.cjs';
3
+ export { p as pingpong } from './pingpong-C9ZRTx3O.cjs';
3
4
 
4
5
  /** `ReconnectOptions` with every field filled in. */
5
6
  type ResolvedReconnect = Required<ReconnectOptions>;
@@ -26,20 +27,6 @@ declare const jsonCodec: Codec;
26
27
  */
27
28
  declare const HEARTBEAT_TIMEOUT_CODE = 4408;
28
29
 
29
- /**
30
- * Replies to a textual `"ping"` with `"pong"` and stops the message there
31
- * (it is not emitted as a `message` event). A common keepalive convention.
32
- *
33
- * Opt in explicitly — it is not registered by default:
34
- *
35
- * ```typescript
36
- * import { defineClient, pingpong } from "durablews";
37
- *
38
- * const ws = defineClient({ url }).use(pingpong);
39
- * ```
40
- */
41
- declare const pingpong: Middleware;
42
-
43
30
  /** `QueueOptions` with every field filled in. */
44
31
  type ResolvedQueue = Required<QueueOptions>;
45
32
  /**
@@ -78,4 +65,4 @@ declare function defineClient<TSchema extends StandardSchemaV1>(config: WebSocke
78
65
  }): WebSocketClient<StandardSchemaV1.InferOutput<TSchema>>;
79
66
  declare function defineClient<TIn = unknown, TOut = unknown>(config: WebSocketClientConfig): WebSocketClient<TIn, TOut>;
80
67
 
81
- export { Codec, HEARTBEAT_TIMEOUT_CODE, Middleware, QUEUE_DEFAULTS, QueueOptions, RECONNECT_DEFAULTS, ReconnectOptions, StandardSchemaV1, WebSocketClient, WebSocketClientConfig, defineClient, jsonCodec, pingpong };
68
+ export { Codec, HEARTBEAT_TIMEOUT_CODE, QUEUE_DEFAULTS, QueueOptions, RECONNECT_DEFAULTS, ReconnectOptions, StandardSchemaV1, WebSocketClient, WebSocketClientConfig, defineClient, jsonCodec };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { R as ReconnectOptions, C as Codec, M as Middleware, Q as QueueOptions, S as StandardSchemaV1, a as WebSocketClientConfig, W as WebSocketClient } from './types-BDvztGcG.js';
2
- export { b as ClientEventMap, c as ClientState, d as ConnectionState, D as DirectionalMiddleware, e as DropEvent, H as HeartbeatOptions, f as MessageContext, O as OutboundContext, g as OutboundMiddleware, h as ReconnectingEvent, i as SchemaValidationError, j as StateChange } from './types-BDvztGcG.js';
1
+ import { R as ReconnectOptions, C as Codec, Q as QueueOptions, S as StandardSchemaV1, a as WebSocketClientConfig, W as WebSocketClient } from './types-BgbiWEXo.js';
2
+ export { b as ClientEventMap, c as ClientState, d as ConnectionState, D as DirectionalMiddleware, e as DropEvent, H as HeartbeatOptions, M as MessageContext, f as Middleware, O as OutboundContext, g as OutboundMiddleware, h as ReconnectingEvent, i as SchemaValidationError, j as StateChange } from './types-BgbiWEXo.js';
3
+ export { p as pingpong } from './pingpong-BAgJFxAc.js';
3
4
 
4
5
  /** `ReconnectOptions` with every field filled in. */
5
6
  type ResolvedReconnect = Required<ReconnectOptions>;
@@ -26,20 +27,6 @@ declare const jsonCodec: Codec;
26
27
  */
27
28
  declare const HEARTBEAT_TIMEOUT_CODE = 4408;
28
29
 
29
- /**
30
- * Replies to a textual `"ping"` with `"pong"` and stops the message there
31
- * (it is not emitted as a `message` event). A common keepalive convention.
32
- *
33
- * Opt in explicitly — it is not registered by default:
34
- *
35
- * ```typescript
36
- * import { defineClient, pingpong } from "durablews";
37
- *
38
- * const ws = defineClient({ url }).use(pingpong);
39
- * ```
40
- */
41
- declare const pingpong: Middleware;
42
-
43
30
  /** `QueueOptions` with every field filled in. */
44
31
  type ResolvedQueue = Required<QueueOptions>;
45
32
  /**
@@ -78,4 +65,4 @@ declare function defineClient<TSchema extends StandardSchemaV1>(config: WebSocke
78
65
  }): WebSocketClient<StandardSchemaV1.InferOutput<TSchema>>;
79
66
  declare function defineClient<TIn = unknown, TOut = unknown>(config: WebSocketClientConfig): WebSocketClient<TIn, TOut>;
80
67
 
81
- export { Codec, HEARTBEAT_TIMEOUT_CODE, Middleware, QUEUE_DEFAULTS, QueueOptions, RECONNECT_DEFAULTS, ReconnectOptions, StandardSchemaV1, WebSocketClient, WebSocketClientConfig, defineClient, jsonCodec, pingpong };
68
+ export { Codec, HEARTBEAT_TIMEOUT_CODE, QUEUE_DEFAULTS, QueueOptions, RECONNECT_DEFAULTS, ReconnectOptions, StandardSchemaV1, WebSocketClient, WebSocketClientConfig, defineClient, jsonCodec };
package/dist/index.js CHANGED
@@ -1,20 +1,12 @@
1
1
  import { client } from './chunk-MG4TK53H.js';
2
2
  export { HEARTBEAT_TIMEOUT_CODE, QUEUE_DEFAULTS, RECONNECT_DEFAULTS, SchemaValidationError, jsonCodec } from './chunk-MG4TK53H.js';
3
-
4
- // src/middleware.ts
5
- var pingpong = (ctx, next) => {
6
- if (ctx.data === "ping") {
7
- ctx.client.send("pong");
8
- return;
9
- }
10
- return next();
11
- };
3
+ export { pingpong } from './chunk-5MUMKGJL.js';
12
4
 
13
5
  // src/index.ts
14
6
  function defineClient(config) {
15
7
  return client(config);
16
8
  }
17
9
 
18
- export { defineClient, pingpong };
10
+ export { defineClient };
19
11
  //# sourceMappingURL=index.js.map
20
12
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/middleware.ts","../src/index.ts"],"names":[],"mappings":";;;;AAcO,IAAM,QAAA,GAAuB,CAAC,GAAA,EAAK,IAAA,KAAS;AAC/C,EAAA,IAAI,GAAA,CAAI,SAAS,MAAA,EAAQ;AACrB,IAAA,GAAA,CAAI,MAAA,CAAO,KAAK,MAAM,CAAA;AACtB,IAAA;AAAA,EACJ;AACA,EAAA,OAAO,IAAA,EAAK;AAChB;;;ACcO,SAAS,aAAa,MAAA,EAAgD;AACzE,EAAA,OAAO,OAAO,MAAM,CAAA;AACxB","file":"index.js","sourcesContent":["import type { Middleware } from \"@/types\";\n\n/**\n * Replies to a textual `\"ping\"` with `\"pong\"` and stops the message there\n * (it is not emitted as a `message` event). A common keepalive convention.\n *\n * Opt in explicitly — it is not registered by default:\n *\n * ```typescript\n * import { defineClient, pingpong } from \"durablews\";\n *\n * const ws = defineClient({ url }).use(pingpong);\n * ```\n */\nexport const pingpong: Middleware = (ctx, next) => {\n if (ctx.data === \"ping\") {\n ctx.client.send(\"pong\");\n return;\n }\n return next();\n};\n","import { client } from \"@/client\";\nimport type { StandardSchemaV1 } from \"@/schema\";\nimport type { WebSocketClient, WebSocketClientConfig } from \"@/types\";\n\n/**\n * Creates a new {@link WebSocketClient}.\n *\n * Each call returns an independent client — there is no shared singleton.\n *\n * Inbound message typing, three ways:\n * - **Schema (recommended):** pass a [Standard Schema](https://standardschema.dev)\n * (zod, valibot, arktype, …) and the message type is inferred — plus every\n * inbound message is validated at runtime.\n * - **Generics:** `defineClient<Incoming, Outgoing>(config)` for types without\n * runtime validation.\n * - **Neither:** messages are `unknown`.\n *\n * @example\n * ```typescript\n * const Message = z.object({ type: z.string(), body: z.string() });\n * const ws = defineClient({ url: \"wss://example.com/socket\", schema: Message });\n *\n * ws.on(\"message\", (msg) => {\n * // msg is { type: string; body: string } — validated at runtime\n * });\n * await ws.connect();\n * ```\n */\nexport function defineClient<TSchema extends StandardSchemaV1>(\n config: WebSocketClientConfig & { readonly schema: TSchema }\n): WebSocketClient<StandardSchemaV1.InferOutput<TSchema>>;\nexport function defineClient<TIn = unknown, TOut = unknown>(\n config: WebSocketClientConfig\n): WebSocketClient<TIn, TOut>;\nexport function defineClient(config: WebSocketClientConfig): WebSocketClient {\n return client(config);\n}\n\nexport { RECONNECT_DEFAULTS } from \"@/backoff\";\nexport { jsonCodec } from \"@/codec\";\nexport { HEARTBEAT_TIMEOUT_CODE } from \"@/heartbeat\";\nexport { pingpong } from \"@/middleware\";\nexport { QUEUE_DEFAULTS } from \"@/queue\";\nexport type { StandardSchemaV1 } from \"@/schema\";\nexport { SchemaValidationError } from \"@/schema\";\nexport type {\n ClientEventMap,\n ClientState,\n Codec,\n ConnectionState,\n DirectionalMiddleware,\n DropEvent,\n HeartbeatOptions,\n MessageContext,\n Middleware,\n OutboundContext,\n OutboundMiddleware,\n QueueOptions,\n ReconnectingEvent,\n ReconnectOptions,\n StateChange,\n WebSocketClient,\n WebSocketClientConfig\n} from \"@/types\";\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAkCO,SAAS,aAAa,MAAA,EAAgD;AACzE,EAAA,OAAO,OAAO,MAAM,CAAA;AACxB","file":"index.js","sourcesContent":["import { client } from \"@/client\";\nimport type { StandardSchemaV1 } from \"@/schema\";\nimport type { WebSocketClient, WebSocketClientConfig } from \"@/types\";\n\n/**\n * Creates a new {@link WebSocketClient}.\n *\n * Each call returns an independent client — there is no shared singleton.\n *\n * Inbound message typing, three ways:\n * - **Schema (recommended):** pass a [Standard Schema](https://standardschema.dev)\n * (zod, valibot, arktype, …) and the message type is inferred — plus every\n * inbound message is validated at runtime.\n * - **Generics:** `defineClient<Incoming, Outgoing>(config)` for types without\n * runtime validation.\n * - **Neither:** messages are `unknown`.\n *\n * @example\n * ```typescript\n * const Message = z.object({ type: z.string(), body: z.string() });\n * const ws = defineClient({ url: \"wss://example.com/socket\", schema: Message });\n *\n * ws.on(\"message\", (msg) => {\n * // msg is { type: string; body: string } — validated at runtime\n * });\n * await ws.connect();\n * ```\n */\nexport function defineClient<TSchema extends StandardSchemaV1>(\n config: WebSocketClientConfig & { readonly schema: TSchema }\n): WebSocketClient<StandardSchemaV1.InferOutput<TSchema>>;\nexport function defineClient<TIn = unknown, TOut = unknown>(\n config: WebSocketClientConfig\n): WebSocketClient<TIn, TOut>;\nexport function defineClient(config: WebSocketClientConfig): WebSocketClient {\n return client(config);\n}\n\nexport { RECONNECT_DEFAULTS } from \"@/backoff\";\nexport { jsonCodec } from \"@/codec\";\nexport { HEARTBEAT_TIMEOUT_CODE } from \"@/heartbeat\";\nexport { pingpong } from \"@/middleware/pingpong\";\nexport { QUEUE_DEFAULTS } from \"@/queue\";\nexport type { StandardSchemaV1 } from \"@/schema\";\nexport { SchemaValidationError } from \"@/schema\";\nexport type {\n ClientEventMap,\n ClientState,\n Codec,\n ConnectionState,\n DirectionalMiddleware,\n DropEvent,\n HeartbeatOptions,\n MessageContext,\n Middleware,\n OutboundContext,\n OutboundMiddleware,\n QueueOptions,\n ReconnectingEvent,\n ReconnectOptions,\n StateChange,\n WebSocketClient,\n WebSocketClientConfig\n} from \"@/types\";\n"]}
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ // src/middleware/auth.ts
4
+ function auth(options) {
5
+ return {
6
+ outbound: async (ctx, next) => {
7
+ const token = await options.token();
8
+ ctx.data = options.inject(ctx.data, token);
9
+ await next();
10
+ }
11
+ };
12
+ }
13
+
14
+ // src/middleware/dedup.ts
15
+ function dedup(options) {
16
+ const max = options.window ?? 1e3;
17
+ const seen = /* @__PURE__ */ new Set();
18
+ return {
19
+ inbound: (ctx, next) => {
20
+ const k = options.key(ctx.data);
21
+ if (seen.has(k)) {
22
+ return;
23
+ }
24
+ seen.add(k);
25
+ if (seen.size > max) {
26
+ const oldest = seen.values().next().value;
27
+ if (oldest !== void 0) {
28
+ seen.delete(oldest);
29
+ }
30
+ }
31
+ return next();
32
+ }
33
+ };
34
+ }
35
+
36
+ // src/middleware/logger.ts
37
+ function logger(options = {}) {
38
+ const sink = options.log ?? ((entry) => console.debug("[durablews]", entry));
39
+ const redact = options.redact ?? ((data) => data);
40
+ const direction = options.direction ?? "both";
41
+ const record = (dir, data, state) => {
42
+ sink({
43
+ direction: dir,
44
+ data: redact(data),
45
+ timestamp: Date.now(),
46
+ state
47
+ });
48
+ };
49
+ const inbound = (ctx, next) => {
50
+ record("inbound", ctx.data, ctx.client.state);
51
+ return next();
52
+ };
53
+ const outbound = (ctx, next) => {
54
+ record("outbound", ctx.data, ctx.client.state);
55
+ return next();
56
+ };
57
+ if (direction === "inbound") {
58
+ return { inbound };
59
+ }
60
+ if (direction === "outbound") {
61
+ return { outbound };
62
+ }
63
+ return { inbound, outbound };
64
+ }
65
+
66
+ // src/middleware/pingpong.ts
67
+ var pingpong = (ctx, next) => {
68
+ if (ctx.data === "ping") {
69
+ ctx.client.send("pong");
70
+ return;
71
+ }
72
+ return next();
73
+ };
74
+
75
+ exports.auth = auth;
76
+ exports.dedup = dedup;
77
+ exports.logger = logger;
78
+ exports.pingpong = pingpong;
79
+ //# sourceMappingURL=middleware.cjs.map
80
+ //# sourceMappingURL=middleware.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/auth.ts","../src/middleware/dedup.ts","../src/middleware/logger.ts","../src/middleware/pingpong.ts"],"names":[],"mappings":"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","file":"middleware.cjs","sourcesContent":["import type { DirectionalMiddleware } from \"@/types\";\n\n/**\n * Options for {@link auth}.\n */\nexport interface AuthOptions<TOut = unknown> {\n /**\n * Produces the current credential. Called at **transmission time** for\n * every outbound message, so a message queued across a reconnect is sent\n * with a token that is fresh when it actually goes out, not when `send()`\n * was first called. May be async (e.g. await a refresh endpoint).\n */\n token: () => string | Promise<string>;\n /**\n * Injects the token into the outgoing message and returns the value to\n * send. Required: messages are app-shaped, so there is no universal\n * placement. The queue and `drop` events keep the original untouched.\n */\n inject: (data: TOut, token: string) => TOut;\n}\n\n/**\n * Outbound middleware that injects a fresh credential into every outgoing\n * message, resolved at transmission time.\n *\n * The hard parts (transmission-time execution, ordered async) are in the core\n * pipeline; this is the small, correct glue over them. Because the token is\n * resolved when the message actually goes out, a message that was queued\n * across a 30s reconnect still carries a current token.\n *\n * ```typescript\n * import { defineClient } from \"durablews\";\n * import { auth } from \"durablews/middleware\";\n *\n * const ws = defineClient({ url }).use(\n * auth({\n * token: () => getAccessToken(), // may be async\n * inject: (data, token) => ({ ...data, token })\n * })\n * );\n * ```\n *\n * If `token()` throws or rejects, that one message is not sent and the failure\n * surfaces as an `error` event; later messages are unaffected.\n */\nexport function auth<TOut = unknown>(\n options: AuthOptions<TOut>\n): DirectionalMiddleware<unknown, TOut> {\n return {\n outbound: async (ctx, next) => {\n const token = await options.token();\n ctx.data = options.inject(ctx.data, token);\n await next();\n }\n };\n}\n","import type { DirectionalMiddleware } from \"@/types\";\n\n/**\n * Options for {@link dedup}.\n */\nexport interface DedupOptions {\n /**\n * Extracts the identity of a message. Two inbound messages with the same\n * key are treated as duplicates. Required: messages have no built-in id.\n */\n key: (data: unknown) => string;\n /**\n * How many recent keys to remember. Bounded, drop-oldest: once the limit\n * is reached the oldest key is forgotten (a duplicate that arrives later\n * than `window` distinct messages ago is no longer detected). Default\n * `1000`.\n */\n window?: number;\n}\n\n/**\n * Inbound middleware that drops duplicate messages, so a server that\n * redelivers (at-least-once) never reaches your handler twice.\n *\n * Memory is bounded by `window` (drop-oldest), in the same spirit as the core\n * send queue: never silently unbounded.\n *\n * ```typescript\n * import { defineClient } from \"durablews\";\n * import { dedup } from \"durablews/middleware\";\n *\n * const ws = defineClient({ url }).use(\n * dedup({ key: (m) => (m as { id: string }).id })\n * );\n * ```\n *\n * A duplicate is short-circuited: it is not emitted as a `message` event (and\n * no `drop` event fires, since that is reserved for outbound delivery loss).\n */\nexport function dedup(options: DedupOptions): DirectionalMiddleware {\n const max = options.window ?? 1000;\n const seen = new Set<string>();\n\n return {\n inbound: (ctx, next) => {\n const k = options.key(ctx.data);\n if (seen.has(k)) {\n return;\n }\n seen.add(k);\n if (seen.size > max) {\n const oldest = seen.values().next().value;\n if (oldest !== undefined) {\n seen.delete(oldest);\n }\n }\n return next();\n }\n };\n}\n","import type {\n ConnectionState,\n DirectionalMiddleware,\n Middleware,\n OutboundMiddleware\n} from \"@/types\";\n\n/**\n * One logged message, in either direction.\n */\nexport interface LogEntry {\n /** Which way the message was flowing. */\n direction: \"inbound\" | \"outbound\";\n /** The message, after {@link LoggerOptions.redact} if one was given. */\n data: unknown;\n /** `Date.now()` when the message was logged. */\n timestamp: number;\n /** The connection state at log time. */\n state: ConnectionState;\n}\n\n/**\n * Options for {@link logger}.\n */\nexport interface LoggerOptions {\n /**\n * Where entries go. Defaults to `console.debug`. Swap in your structured\n * logger to pipe traffic into an observability stack.\n */\n log?: (entry: LogEntry) => void;\n /**\n * Scrub secrets/PII before logging. Receives the message and returns what\n * to log; the original is never altered, so the wire and your handlers are\n * unaffected. Defaults to logging the message as-is.\n */\n redact?: (data: unknown) => unknown;\n /** Which direction(s) to log. Defaults to `\"both\"`. */\n direction?: \"inbound\" | \"outbound\" | \"both\";\n}\n\n/**\n * Logs every message passing through the client, in both directions, without\n * altering it.\n *\n * The production-grade part is {@link LoggerOptions.redact}: structured output\n * with secrets and PII scrubbed, so you never leak an auth token into your\n * logs.\n *\n * ```typescript\n * import { defineClient } from \"durablews\";\n * import { logger } from \"durablews/middleware\";\n *\n * const ws = defineClient({ url }).use(\n * logger({\n * redact: (data) => ({ ...data, token: \"[redacted]\" })\n * })\n * );\n * ```\n */\nexport function logger(options: LoggerOptions = {}): DirectionalMiddleware {\n const sink =\n options.log ?? ((entry) => console.debug(\"[durablews]\", entry));\n const redact = options.redact ?? ((data: unknown) => data);\n const direction = options.direction ?? \"both\";\n\n const record = (\n dir: LogEntry[\"direction\"],\n data: unknown,\n state: ConnectionState\n ): void => {\n sink({\n direction: dir,\n data: redact(data),\n timestamp: Date.now(),\n state\n });\n };\n\n const inbound: Middleware = (ctx, next) => {\n record(\"inbound\", ctx.data, ctx.client.state);\n return next();\n };\n const outbound: OutboundMiddleware = (ctx, next) => {\n record(\"outbound\", ctx.data, ctx.client.state);\n return next();\n };\n\n if (direction === \"inbound\") {\n return { inbound };\n }\n if (direction === \"outbound\") {\n return { outbound };\n }\n return { inbound, outbound };\n}\n","import type { Middleware } from \"@/types\";\n\n/**\n * Replies to a textual `\"ping\"` with `\"pong\"` and stops the message there\n * (it is not emitted as a `message` event). A common keepalive convention.\n *\n * Opt in explicitly, it is not registered by default:\n *\n * ```typescript\n * import { defineClient, pingpong } from \"durablews\";\n *\n * const ws = defineClient({ url }).use(pingpong);\n * ```\n */\nexport const pingpong: Middleware = (ctx, next) => {\n if (ctx.data === \"ping\") {\n ctx.client.send(\"pong\");\n return;\n }\n return next();\n};\n"]}
@@ -0,0 +1,138 @@
1
+ import { D as DirectionalMiddleware, d as ConnectionState } from './types-BgbiWEXo.cjs';
2
+ export { p as pingpong } from './pingpong-C9ZRTx3O.cjs';
3
+
4
+ /**
5
+ * Options for {@link auth}.
6
+ */
7
+ interface AuthOptions<TOut = unknown> {
8
+ /**
9
+ * Produces the current credential. Called at **transmission time** for
10
+ * every outbound message, so a message queued across a reconnect is sent
11
+ * with a token that is fresh when it actually goes out, not when `send()`
12
+ * was first called. May be async (e.g. await a refresh endpoint).
13
+ */
14
+ token: () => string | Promise<string>;
15
+ /**
16
+ * Injects the token into the outgoing message and returns the value to
17
+ * send. Required: messages are app-shaped, so there is no universal
18
+ * placement. The queue and `drop` events keep the original untouched.
19
+ */
20
+ inject: (data: TOut, token: string) => TOut;
21
+ }
22
+ /**
23
+ * Outbound middleware that injects a fresh credential into every outgoing
24
+ * message, resolved at transmission time.
25
+ *
26
+ * The hard parts (transmission-time execution, ordered async) are in the core
27
+ * pipeline; this is the small, correct glue over them. Because the token is
28
+ * resolved when the message actually goes out, a message that was queued
29
+ * across a 30s reconnect still carries a current token.
30
+ *
31
+ * ```typescript
32
+ * import { defineClient } from "durablews";
33
+ * import { auth } from "durablews/middleware";
34
+ *
35
+ * const ws = defineClient({ url }).use(
36
+ * auth({
37
+ * token: () => getAccessToken(), // may be async
38
+ * inject: (data, token) => ({ ...data, token })
39
+ * })
40
+ * );
41
+ * ```
42
+ *
43
+ * If `token()` throws or rejects, that one message is not sent and the failure
44
+ * surfaces as an `error` event; later messages are unaffected.
45
+ */
46
+ declare function auth<TOut = unknown>(options: AuthOptions<TOut>): DirectionalMiddleware<unknown, TOut>;
47
+
48
+ /**
49
+ * Options for {@link dedup}.
50
+ */
51
+ interface DedupOptions {
52
+ /**
53
+ * Extracts the identity of a message. Two inbound messages with the same
54
+ * key are treated as duplicates. Required: messages have no built-in id.
55
+ */
56
+ key: (data: unknown) => string;
57
+ /**
58
+ * How many recent keys to remember. Bounded, drop-oldest: once the limit
59
+ * is reached the oldest key is forgotten (a duplicate that arrives later
60
+ * than `window` distinct messages ago is no longer detected). Default
61
+ * `1000`.
62
+ */
63
+ window?: number;
64
+ }
65
+ /**
66
+ * Inbound middleware that drops duplicate messages, so a server that
67
+ * redelivers (at-least-once) never reaches your handler twice.
68
+ *
69
+ * Memory is bounded by `window` (drop-oldest), in the same spirit as the core
70
+ * send queue: never silently unbounded.
71
+ *
72
+ * ```typescript
73
+ * import { defineClient } from "durablews";
74
+ * import { dedup } from "durablews/middleware";
75
+ *
76
+ * const ws = defineClient({ url }).use(
77
+ * dedup({ key: (m) => (m as { id: string }).id })
78
+ * );
79
+ * ```
80
+ *
81
+ * A duplicate is short-circuited: it is not emitted as a `message` event (and
82
+ * no `drop` event fires, since that is reserved for outbound delivery loss).
83
+ */
84
+ declare function dedup(options: DedupOptions): DirectionalMiddleware;
85
+
86
+ /**
87
+ * One logged message, in either direction.
88
+ */
89
+ interface LogEntry {
90
+ /** Which way the message was flowing. */
91
+ direction: "inbound" | "outbound";
92
+ /** The message, after {@link LoggerOptions.redact} if one was given. */
93
+ data: unknown;
94
+ /** `Date.now()` when the message was logged. */
95
+ timestamp: number;
96
+ /** The connection state at log time. */
97
+ state: ConnectionState;
98
+ }
99
+ /**
100
+ * Options for {@link logger}.
101
+ */
102
+ interface LoggerOptions {
103
+ /**
104
+ * Where entries go. Defaults to `console.debug`. Swap in your structured
105
+ * logger to pipe traffic into an observability stack.
106
+ */
107
+ log?: (entry: LogEntry) => void;
108
+ /**
109
+ * Scrub secrets/PII before logging. Receives the message and returns what
110
+ * to log; the original is never altered, so the wire and your handlers are
111
+ * unaffected. Defaults to logging the message as-is.
112
+ */
113
+ redact?: (data: unknown) => unknown;
114
+ /** Which direction(s) to log. Defaults to `"both"`. */
115
+ direction?: "inbound" | "outbound" | "both";
116
+ }
117
+ /**
118
+ * Logs every message passing through the client, in both directions, without
119
+ * altering it.
120
+ *
121
+ * The production-grade part is {@link LoggerOptions.redact}: structured output
122
+ * with secrets and PII scrubbed, so you never leak an auth token into your
123
+ * logs.
124
+ *
125
+ * ```typescript
126
+ * import { defineClient } from "durablews";
127
+ * import { logger } from "durablews/middleware";
128
+ *
129
+ * const ws = defineClient({ url }).use(
130
+ * logger({
131
+ * redact: (data) => ({ ...data, token: "[redacted]" })
132
+ * })
133
+ * );
134
+ * ```
135
+ */
136
+ declare function logger(options?: LoggerOptions): DirectionalMiddleware;
137
+
138
+ export { type AuthOptions, type DedupOptions, type LogEntry, type LoggerOptions, auth, dedup, logger };
@@ -0,0 +1,138 @@
1
+ import { D as DirectionalMiddleware, d as ConnectionState } from './types-BgbiWEXo.js';
2
+ export { p as pingpong } from './pingpong-BAgJFxAc.js';
3
+
4
+ /**
5
+ * Options for {@link auth}.
6
+ */
7
+ interface AuthOptions<TOut = unknown> {
8
+ /**
9
+ * Produces the current credential. Called at **transmission time** for
10
+ * every outbound message, so a message queued across a reconnect is sent
11
+ * with a token that is fresh when it actually goes out, not when `send()`
12
+ * was first called. May be async (e.g. await a refresh endpoint).
13
+ */
14
+ token: () => string | Promise<string>;
15
+ /**
16
+ * Injects the token into the outgoing message and returns the value to
17
+ * send. Required: messages are app-shaped, so there is no universal
18
+ * placement. The queue and `drop` events keep the original untouched.
19
+ */
20
+ inject: (data: TOut, token: string) => TOut;
21
+ }
22
+ /**
23
+ * Outbound middleware that injects a fresh credential into every outgoing
24
+ * message, resolved at transmission time.
25
+ *
26
+ * The hard parts (transmission-time execution, ordered async) are in the core
27
+ * pipeline; this is the small, correct glue over them. Because the token is
28
+ * resolved when the message actually goes out, a message that was queued
29
+ * across a 30s reconnect still carries a current token.
30
+ *
31
+ * ```typescript
32
+ * import { defineClient } from "durablews";
33
+ * import { auth } from "durablews/middleware";
34
+ *
35
+ * const ws = defineClient({ url }).use(
36
+ * auth({
37
+ * token: () => getAccessToken(), // may be async
38
+ * inject: (data, token) => ({ ...data, token })
39
+ * })
40
+ * );
41
+ * ```
42
+ *
43
+ * If `token()` throws or rejects, that one message is not sent and the failure
44
+ * surfaces as an `error` event; later messages are unaffected.
45
+ */
46
+ declare function auth<TOut = unknown>(options: AuthOptions<TOut>): DirectionalMiddleware<unknown, TOut>;
47
+
48
+ /**
49
+ * Options for {@link dedup}.
50
+ */
51
+ interface DedupOptions {
52
+ /**
53
+ * Extracts the identity of a message. Two inbound messages with the same
54
+ * key are treated as duplicates. Required: messages have no built-in id.
55
+ */
56
+ key: (data: unknown) => string;
57
+ /**
58
+ * How many recent keys to remember. Bounded, drop-oldest: once the limit
59
+ * is reached the oldest key is forgotten (a duplicate that arrives later
60
+ * than `window` distinct messages ago is no longer detected). Default
61
+ * `1000`.
62
+ */
63
+ window?: number;
64
+ }
65
+ /**
66
+ * Inbound middleware that drops duplicate messages, so a server that
67
+ * redelivers (at-least-once) never reaches your handler twice.
68
+ *
69
+ * Memory is bounded by `window` (drop-oldest), in the same spirit as the core
70
+ * send queue: never silently unbounded.
71
+ *
72
+ * ```typescript
73
+ * import { defineClient } from "durablews";
74
+ * import { dedup } from "durablews/middleware";
75
+ *
76
+ * const ws = defineClient({ url }).use(
77
+ * dedup({ key: (m) => (m as { id: string }).id })
78
+ * );
79
+ * ```
80
+ *
81
+ * A duplicate is short-circuited: it is not emitted as a `message` event (and
82
+ * no `drop` event fires, since that is reserved for outbound delivery loss).
83
+ */
84
+ declare function dedup(options: DedupOptions): DirectionalMiddleware;
85
+
86
+ /**
87
+ * One logged message, in either direction.
88
+ */
89
+ interface LogEntry {
90
+ /** Which way the message was flowing. */
91
+ direction: "inbound" | "outbound";
92
+ /** The message, after {@link LoggerOptions.redact} if one was given. */
93
+ data: unknown;
94
+ /** `Date.now()` when the message was logged. */
95
+ timestamp: number;
96
+ /** The connection state at log time. */
97
+ state: ConnectionState;
98
+ }
99
+ /**
100
+ * Options for {@link logger}.
101
+ */
102
+ interface LoggerOptions {
103
+ /**
104
+ * Where entries go. Defaults to `console.debug`. Swap in your structured
105
+ * logger to pipe traffic into an observability stack.
106
+ */
107
+ log?: (entry: LogEntry) => void;
108
+ /**
109
+ * Scrub secrets/PII before logging. Receives the message and returns what
110
+ * to log; the original is never altered, so the wire and your handlers are
111
+ * unaffected. Defaults to logging the message as-is.
112
+ */
113
+ redact?: (data: unknown) => unknown;
114
+ /** Which direction(s) to log. Defaults to `"both"`. */
115
+ direction?: "inbound" | "outbound" | "both";
116
+ }
117
+ /**
118
+ * Logs every message passing through the client, in both directions, without
119
+ * altering it.
120
+ *
121
+ * The production-grade part is {@link LoggerOptions.redact}: structured output
122
+ * with secrets and PII scrubbed, so you never leak an auth token into your
123
+ * logs.
124
+ *
125
+ * ```typescript
126
+ * import { defineClient } from "durablews";
127
+ * import { logger } from "durablews/middleware";
128
+ *
129
+ * const ws = defineClient({ url }).use(
130
+ * logger({
131
+ * redact: (data) => ({ ...data, token: "[redacted]" })
132
+ * })
133
+ * );
134
+ * ```
135
+ */
136
+ declare function logger(options?: LoggerOptions): DirectionalMiddleware;
137
+
138
+ export { type AuthOptions, type DedupOptions, type LogEntry, type LoggerOptions, auth, dedup, logger };
@@ -0,0 +1,68 @@
1
+ export { pingpong } from './chunk-5MUMKGJL.js';
2
+
3
+ // src/middleware/auth.ts
4
+ function auth(options) {
5
+ return {
6
+ outbound: async (ctx, next) => {
7
+ const token = await options.token();
8
+ ctx.data = options.inject(ctx.data, token);
9
+ await next();
10
+ }
11
+ };
12
+ }
13
+
14
+ // src/middleware/dedup.ts
15
+ function dedup(options) {
16
+ const max = options.window ?? 1e3;
17
+ const seen = /* @__PURE__ */ new Set();
18
+ return {
19
+ inbound: (ctx, next) => {
20
+ const k = options.key(ctx.data);
21
+ if (seen.has(k)) {
22
+ return;
23
+ }
24
+ seen.add(k);
25
+ if (seen.size > max) {
26
+ const oldest = seen.values().next().value;
27
+ if (oldest !== void 0) {
28
+ seen.delete(oldest);
29
+ }
30
+ }
31
+ return next();
32
+ }
33
+ };
34
+ }
35
+
36
+ // src/middleware/logger.ts
37
+ function logger(options = {}) {
38
+ const sink = options.log ?? ((entry) => console.debug("[durablews]", entry));
39
+ const redact = options.redact ?? ((data) => data);
40
+ const direction = options.direction ?? "both";
41
+ const record = (dir, data, state) => {
42
+ sink({
43
+ direction: dir,
44
+ data: redact(data),
45
+ timestamp: Date.now(),
46
+ state
47
+ });
48
+ };
49
+ const inbound = (ctx, next) => {
50
+ record("inbound", ctx.data, ctx.client.state);
51
+ return next();
52
+ };
53
+ const outbound = (ctx, next) => {
54
+ record("outbound", ctx.data, ctx.client.state);
55
+ return next();
56
+ };
57
+ if (direction === "inbound") {
58
+ return { inbound };
59
+ }
60
+ if (direction === "outbound") {
61
+ return { outbound };
62
+ }
63
+ return { inbound, outbound };
64
+ }
65
+
66
+ export { auth, dedup, logger };
67
+ //# sourceMappingURL=middleware.js.map
68
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/auth.ts","../src/middleware/dedup.ts","../src/middleware/logger.ts"],"names":[],"mappings":";;;AA6CO,SAAS,KACZ,OAAA,EACoC;AACpC,EAAA,OAAO;AAAA,IACH,QAAA,EAAU,OAAO,GAAA,EAAK,IAAA,KAAS;AAC3B,MAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,KAAA,EAAM;AAClC,MAAA,GAAA,CAAI,IAAA,GAAO,OAAA,CAAQ,MAAA,CAAO,GAAA,CAAI,MAAM,KAAK,CAAA;AACzC,MAAA,MAAM,IAAA,EAAK;AAAA,IACf;AAAA,GACJ;AACJ;;;AChBO,SAAS,MAAM,OAAA,EAA8C;AAChE,EAAA,MAAM,GAAA,GAAM,QAAQ,MAAA,IAAU,GAAA;AAC9B,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAE7B,EAAA,OAAO;AAAA,IACH,OAAA,EAAS,CAAC,GAAA,EAAK,IAAA,KAAS;AACpB,MAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,GAAA,CAAI,GAAA,CAAI,IAAI,CAAA;AAC9B,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,EAAG;AACb,QAAA;AAAA,MACJ;AACA,MAAA,IAAA,CAAK,IAAI,CAAC,CAAA;AACV,MAAA,IAAI,IAAA,CAAK,OAAO,GAAA,EAAK;AACjB,QAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,EAAO,CAAE,MAAK,CAAE,KAAA;AACpC,QAAA,IAAI,WAAW,MAAA,EAAW;AACtB,UAAA,IAAA,CAAK,OAAO,MAAM,CAAA;AAAA,QACtB;AAAA,MACJ;AACA,MAAA,OAAO,IAAA,EAAK;AAAA,IAChB;AAAA,GACJ;AACJ;;;ACAO,SAAS,MAAA,CAAO,OAAA,GAAyB,EAAC,EAA0B;AACvE,EAAA,MAAM,IAAA,GACF,QAAQ,GAAA,KAAQ,CAAC,UAAU,OAAA,CAAQ,KAAA,CAAM,eAAe,KAAK,CAAA,CAAA;AACjE,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,KAAW,CAAC,IAAA,KAAkB,IAAA,CAAA;AACrD,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,MAAA;AAEvC,EAAA,MAAM,MAAA,GAAS,CACX,GAAA,EACA,IAAA,EACA,KAAA,KACO;AACP,IAAA,IAAA,CAAK;AAAA,MACD,SAAA,EAAW,GAAA;AAAA,MACX,IAAA,EAAM,OAAO,IAAI,CAAA;AAAA,MACjB,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,MACpB;AAAA,KACH,CAAA;AAAA,EACL,CAAA;AAEA,EAAA,MAAM,OAAA,GAAsB,CAAC,GAAA,EAAK,IAAA,KAAS;AACvC,IAAA,MAAA,CAAO,SAAA,EAAW,GAAA,CAAI,IAAA,EAAM,GAAA,CAAI,OAAO,KAAK,CAAA;AAC5C,IAAA,OAAO,IAAA,EAAK;AAAA,EAChB,CAAA;AACA,EAAA,MAAM,QAAA,GAA+B,CAAC,GAAA,EAAK,IAAA,KAAS;AAChD,IAAA,MAAA,CAAO,UAAA,EAAY,GAAA,CAAI,IAAA,EAAM,GAAA,CAAI,OAAO,KAAK,CAAA;AAC7C,IAAA,OAAO,IAAA,EAAK;AAAA,EAChB,CAAA;AAEA,EAAA,IAAI,cAAc,SAAA,EAAW;AACzB,IAAA,OAAO,EAAE,OAAA,EAAQ;AAAA,EACrB;AACA,EAAA,IAAI,cAAc,UAAA,EAAY;AAC1B,IAAA,OAAO,EAAE,QAAA,EAAS;AAAA,EACtB;AACA,EAAA,OAAO,EAAE,SAAS,QAAA,EAAS;AAC/B","file":"middleware.js","sourcesContent":["import type { DirectionalMiddleware } from \"@/types\";\n\n/**\n * Options for {@link auth}.\n */\nexport interface AuthOptions<TOut = unknown> {\n /**\n * Produces the current credential. Called at **transmission time** for\n * every outbound message, so a message queued across a reconnect is sent\n * with a token that is fresh when it actually goes out, not when `send()`\n * was first called. May be async (e.g. await a refresh endpoint).\n */\n token: () => string | Promise<string>;\n /**\n * Injects the token into the outgoing message and returns the value to\n * send. Required: messages are app-shaped, so there is no universal\n * placement. The queue and `drop` events keep the original untouched.\n */\n inject: (data: TOut, token: string) => TOut;\n}\n\n/**\n * Outbound middleware that injects a fresh credential into every outgoing\n * message, resolved at transmission time.\n *\n * The hard parts (transmission-time execution, ordered async) are in the core\n * pipeline; this is the small, correct glue over them. Because the token is\n * resolved when the message actually goes out, a message that was queued\n * across a 30s reconnect still carries a current token.\n *\n * ```typescript\n * import { defineClient } from \"durablews\";\n * import { auth } from \"durablews/middleware\";\n *\n * const ws = defineClient({ url }).use(\n * auth({\n * token: () => getAccessToken(), // may be async\n * inject: (data, token) => ({ ...data, token })\n * })\n * );\n * ```\n *\n * If `token()` throws or rejects, that one message is not sent and the failure\n * surfaces as an `error` event; later messages are unaffected.\n */\nexport function auth<TOut = unknown>(\n options: AuthOptions<TOut>\n): DirectionalMiddleware<unknown, TOut> {\n return {\n outbound: async (ctx, next) => {\n const token = await options.token();\n ctx.data = options.inject(ctx.data, token);\n await next();\n }\n };\n}\n","import type { DirectionalMiddleware } from \"@/types\";\n\n/**\n * Options for {@link dedup}.\n */\nexport interface DedupOptions {\n /**\n * Extracts the identity of a message. Two inbound messages with the same\n * key are treated as duplicates. Required: messages have no built-in id.\n */\n key: (data: unknown) => string;\n /**\n * How many recent keys to remember. Bounded, drop-oldest: once the limit\n * is reached the oldest key is forgotten (a duplicate that arrives later\n * than `window` distinct messages ago is no longer detected). Default\n * `1000`.\n */\n window?: number;\n}\n\n/**\n * Inbound middleware that drops duplicate messages, so a server that\n * redelivers (at-least-once) never reaches your handler twice.\n *\n * Memory is bounded by `window` (drop-oldest), in the same spirit as the core\n * send queue: never silently unbounded.\n *\n * ```typescript\n * import { defineClient } from \"durablews\";\n * import { dedup } from \"durablews/middleware\";\n *\n * const ws = defineClient({ url }).use(\n * dedup({ key: (m) => (m as { id: string }).id })\n * );\n * ```\n *\n * A duplicate is short-circuited: it is not emitted as a `message` event (and\n * no `drop` event fires, since that is reserved for outbound delivery loss).\n */\nexport function dedup(options: DedupOptions): DirectionalMiddleware {\n const max = options.window ?? 1000;\n const seen = new Set<string>();\n\n return {\n inbound: (ctx, next) => {\n const k = options.key(ctx.data);\n if (seen.has(k)) {\n return;\n }\n seen.add(k);\n if (seen.size > max) {\n const oldest = seen.values().next().value;\n if (oldest !== undefined) {\n seen.delete(oldest);\n }\n }\n return next();\n }\n };\n}\n","import type {\n ConnectionState,\n DirectionalMiddleware,\n Middleware,\n OutboundMiddleware\n} from \"@/types\";\n\n/**\n * One logged message, in either direction.\n */\nexport interface LogEntry {\n /** Which way the message was flowing. */\n direction: \"inbound\" | \"outbound\";\n /** The message, after {@link LoggerOptions.redact} if one was given. */\n data: unknown;\n /** `Date.now()` when the message was logged. */\n timestamp: number;\n /** The connection state at log time. */\n state: ConnectionState;\n}\n\n/**\n * Options for {@link logger}.\n */\nexport interface LoggerOptions {\n /**\n * Where entries go. Defaults to `console.debug`. Swap in your structured\n * logger to pipe traffic into an observability stack.\n */\n log?: (entry: LogEntry) => void;\n /**\n * Scrub secrets/PII before logging. Receives the message and returns what\n * to log; the original is never altered, so the wire and your handlers are\n * unaffected. Defaults to logging the message as-is.\n */\n redact?: (data: unknown) => unknown;\n /** Which direction(s) to log. Defaults to `\"both\"`. */\n direction?: \"inbound\" | \"outbound\" | \"both\";\n}\n\n/**\n * Logs every message passing through the client, in both directions, without\n * altering it.\n *\n * The production-grade part is {@link LoggerOptions.redact}: structured output\n * with secrets and PII scrubbed, so you never leak an auth token into your\n * logs.\n *\n * ```typescript\n * import { defineClient } from \"durablews\";\n * import { logger } from \"durablews/middleware\";\n *\n * const ws = defineClient({ url }).use(\n * logger({\n * redact: (data) => ({ ...data, token: \"[redacted]\" })\n * })\n * );\n * ```\n */\nexport function logger(options: LoggerOptions = {}): DirectionalMiddleware {\n const sink =\n options.log ?? ((entry) => console.debug(\"[durablews]\", entry));\n const redact = options.redact ?? ((data: unknown) => data);\n const direction = options.direction ?? \"both\";\n\n const record = (\n dir: LogEntry[\"direction\"],\n data: unknown,\n state: ConnectionState\n ): void => {\n sink({\n direction: dir,\n data: redact(data),\n timestamp: Date.now(),\n state\n });\n };\n\n const inbound: Middleware = (ctx, next) => {\n record(\"inbound\", ctx.data, ctx.client.state);\n return next();\n };\n const outbound: OutboundMiddleware = (ctx, next) => {\n record(\"outbound\", ctx.data, ctx.client.state);\n return next();\n };\n\n if (direction === \"inbound\") {\n return { inbound };\n }\n if (direction === \"outbound\") {\n return { outbound };\n }\n return { inbound, outbound };\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { f as Middleware } from './types-BgbiWEXo.js';
2
+
3
+ /**
4
+ * Replies to a textual `"ping"` with `"pong"` and stops the message there
5
+ * (it is not emitted as a `message` event). A common keepalive convention.
6
+ *
7
+ * Opt in explicitly, it is not registered by default:
8
+ *
9
+ * ```typescript
10
+ * import { defineClient, pingpong } from "durablews";
11
+ *
12
+ * const ws = defineClient({ url }).use(pingpong);
13
+ * ```
14
+ */
15
+ declare const pingpong: Middleware;
16
+
17
+ export { pingpong as p };
@@ -0,0 +1,17 @@
1
+ import { f as Middleware } from './types-BgbiWEXo.cjs';
2
+
3
+ /**
4
+ * Replies to a textual `"ping"` with `"pong"` and stops the message there
5
+ * (it is not emitted as a `message` event). A common keepalive convention.
6
+ *
7
+ * Opt in explicitly, it is not registered by default:
8
+ *
9
+ * ```typescript
10
+ * import { defineClient, pingpong } from "durablews";
11
+ *
12
+ * const ws = defineClient({ url }).use(pingpong);
13
+ * ```
14
+ */
15
+ declare const pingpong: Middleware;
16
+
17
+ export { pingpong as p };
package/dist/react.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UseWebSocketSource } from './binding-bZNBe2Bz.cjs';
2
- import { W as WebSocketClient, d as ConnectionState, S as StandardSchemaV1, a as WebSocketClientConfig } from './types-BDvztGcG.cjs';
1
+ import { U as UseWebSocketSource } from './binding-JaDDUOyB.cjs';
2
+ import { W as WebSocketClient, d as ConnectionState, S as StandardSchemaV1, a as WebSocketClientConfig } from './types-BgbiWEXo.cjs';
3
3
 
4
4
  /**
5
5
  * What {@link useWebSocket} returns: the client itself plus its observable
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UseWebSocketSource } from './binding-gyPQiK9i.js';
2
- import { W as WebSocketClient, d as ConnectionState, S as StandardSchemaV1, a as WebSocketClientConfig } from './types-BDvztGcG.js';
1
+ import { U as UseWebSocketSource } from './binding-Ig2fzg88.js';
2
+ import { W as WebSocketClient, d as ConnectionState, S as StandardSchemaV1, a as WebSocketClientConfig } from './types-BgbiWEXo.js';
3
3
 
4
4
  /**
5
5
  * What {@link useWebSocket} returns: the client itself plus its observable
@@ -407,4 +407,4 @@ interface WebSocketClient<TIn = unknown, TOut = unknown> {
407
407
  subscribe(listener: () => void): () => void;
408
408
  }
409
409
 
410
- export { type Codec as C, type DirectionalMiddleware as D, type HeartbeatOptions as H, type Middleware as M, type OutboundContext as O, type QueueOptions as Q, type ReconnectOptions as R, StandardSchemaV1 as S, type WebSocketClient as W, type WebSocketClientConfig as a, type ClientEventMap as b, type ClientState as c, type ConnectionState as d, type DropEvent as e, type MessageContext as f, type OutboundMiddleware as g, type ReconnectingEvent as h, SchemaValidationError as i, type StateChange as j };
410
+ export { type Codec as C, type DirectionalMiddleware as D, type HeartbeatOptions as H, type MessageContext as M, type OutboundContext as O, type QueueOptions as Q, type ReconnectOptions as R, StandardSchemaV1 as S, type WebSocketClient as W, type WebSocketClientConfig as a, type ClientEventMap as b, type ClientState as c, type ConnectionState as d, type DropEvent as e, type Middleware as f, type OutboundMiddleware as g, type ReconnectingEvent as h, SchemaValidationError as i, type StateChange as j };
@@ -407,4 +407,4 @@ interface WebSocketClient<TIn = unknown, TOut = unknown> {
407
407
  subscribe(listener: () => void): () => void;
408
408
  }
409
409
 
410
- export { type Codec as C, type DirectionalMiddleware as D, type HeartbeatOptions as H, type Middleware as M, type OutboundContext as O, type QueueOptions as Q, type ReconnectOptions as R, StandardSchemaV1 as S, type WebSocketClient as W, type WebSocketClientConfig as a, type ClientEventMap as b, type ClientState as c, type ConnectionState as d, type DropEvent as e, type MessageContext as f, type OutboundMiddleware as g, type ReconnectingEvent as h, SchemaValidationError as i, type StateChange as j };
410
+ export { type Codec as C, type DirectionalMiddleware as D, type HeartbeatOptions as H, type MessageContext as M, type OutboundContext as O, type QueueOptions as Q, type ReconnectOptions as R, StandardSchemaV1 as S, type WebSocketClient as W, type WebSocketClientConfig as a, type ClientEventMap as b, type ClientState as c, type ConnectionState as d, type DropEvent as e, type Middleware as f, type OutboundMiddleware as g, type ReconnectingEvent as h, SchemaValidationError as i, type StateChange as j };
package/dist/vue.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ComputedRef, ShallowRef } from 'vue';
2
- import { U as UseWebSocketSource } from './binding-bZNBe2Bz.cjs';
3
- import { W as WebSocketClient, d as ConnectionState, S as StandardSchemaV1, a as WebSocketClientConfig } from './types-BDvztGcG.cjs';
2
+ import { U as UseWebSocketSource } from './binding-JaDDUOyB.cjs';
3
+ import { W as WebSocketClient, d as ConnectionState, S as StandardSchemaV1, a as WebSocketClientConfig } from './types-BgbiWEXo.cjs';
4
4
 
5
5
  /**
6
6
  * Vue bindings for durablews — `import { useWebSocket } from "durablews/vue"`.
package/dist/vue.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ComputedRef, ShallowRef } from 'vue';
2
- import { U as UseWebSocketSource } from './binding-gyPQiK9i.js';
3
- import { W as WebSocketClient, d as ConnectionState, S as StandardSchemaV1, a as WebSocketClientConfig } from './types-BDvztGcG.js';
2
+ import { U as UseWebSocketSource } from './binding-Ig2fzg88.js';
3
+ import { W as WebSocketClient, d as ConnectionState, S as StandardSchemaV1, a as WebSocketClientConfig } from './types-BgbiWEXo.js';
4
4
 
5
5
  /**
6
6
  * Vue bindings for durablews — `import { useWebSocket } from "durablews/vue"`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "durablews",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "A resilient, TypeScript-based WebSocket client",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -46,6 +46,16 @@
46
46
  "types": "./dist/compat.d.cts",
47
47
  "default": "./dist/compat.cjs"
48
48
  }
49
+ },
50
+ "./middleware": {
51
+ "import": {
52
+ "types": "./dist/middleware.d.ts",
53
+ "default": "./dist/middleware.js"
54
+ },
55
+ "require": {
56
+ "types": "./dist/middleware.d.cts",
57
+ "default": "./dist/middleware.cjs"
58
+ }
49
59
  }
50
60
  },
51
61
  "sideEffects": false,
@@ -152,6 +162,27 @@
152
162
  "path": "dist/compat.js",
153
163
  "limit": "4 KB",
154
164
  "brotli": true
165
+ },
166
+ {
167
+ "name": "durablews/middleware (auth only, tree-shaken)",
168
+ "path": "dist/middleware.js",
169
+ "import": "{ auth }",
170
+ "limit": "0.15 KB",
171
+ "brotli": true
172
+ },
173
+ {
174
+ "name": "durablews/middleware (logger only, tree-shaken)",
175
+ "path": "dist/middleware.js",
176
+ "import": "{ logger }",
177
+ "limit": "0.3 KB",
178
+ "brotli": true
179
+ },
180
+ {
181
+ "name": "durablews/middleware (dedup only, tree-shaken)",
182
+ "path": "dist/middleware.js",
183
+ "import": "{ dedup }",
184
+ "limit": "0.25 KB",
185
+ "brotli": true
155
186
  }
156
187
  ],
157
188
  "scripts": {