durablews 1.0.1 → 2.0.0-alpha.1

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,122 +1,92 @@
1
- # DurableWS (Durable Web Sockets)
2
-
3
- DurableWS is a resilient, TypeScript-based WebSocket client designed for modern web applications. It offers robust connection handling, automatic reconnection with exponential backoff, idle detection, message queueing, and easy subscription management, ensuring your application maintains a reliable real-time connection under varying network conditions.
4
-
5
- ## Features
6
-
7
- - **Automatic Reconnection**: Implements exponential backoff strategy to handle disconnections gracefully.
8
- - **Idle Detection**: Automatically detects idle states and triggers custom handlers.
9
- - **Message Queueing**: Outgoing messages are queued when the connection is down and sent when it's restored.
10
- - **TypeScript Support**: Fully typed interfaces for a better development experience.
1
+ # DurableWS
2
+
3
+ > The WebSocket client that survives the real world automatic reconnection,
4
+ > bounded queueing, and typed messages. Zero dependencies, every modern
5
+ > runtime, durable by default.
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
10
+ > [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
12
+ > use it.
13
+
14
+ ## What you get
15
+
16
+ - **Automatic reconnection, on by default** — full-jitter exponential
17
+ backoff, unlimited retries, a `shouldReconnect` veto, and `reconnecting`
18
+ events for your UI.
19
+ - **Message queueing while disconnected, on by default** — bounded,
20
+ drop-oldest, flushed in order on open. Every dropped message fires a `drop`
21
+ event; nothing is silently lost.
22
+ - **Opt-in heartbeat / idle detection** — quietly-dead links are closed (code
23
+ `4408`) and recovered through the normal reconnect machinery.
24
+ - **Typed + validated messages** — pass any
25
+ [Standard Schema](https://standardschema.dev) (zod, valibot, arktype, …)
26
+ and inbound messages are type-inferred *and* runtime-validated.
27
+ - **Middleware, inbound and outbound** — an onion pipeline with async-safe,
28
+ 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
31
+ `durablews/react` (hook); the frameworks are optional peers.
32
+ - **A drop-in `WebSocket` class** — `durablews/compat` for one-line migration
33
+ or `webSocketImpl` injection, with a published known-deviations table.
34
+ - **Zero runtime dependencies**, built on the standard global `WebSocket`.
35
+
36
+ ## Requirements
37
+
38
+ - **Node.js ≥ 22** — the first release where the global `WebSocket` is
39
+ available unflagged. (There is no `ws` dependency, by design.)
40
+ - Any modern runtime with a global `WebSocket`: current browsers, Deno, Bun,
41
+ and Cloudflare Workers.
11
42
 
12
43
  ## Installation
13
44
 
14
45
  ```bash
15
- npm install durablews
16
- ```
17
-
18
- or
19
-
20
- ```bash
21
- yarn add durablews
46
+ npm install durablews@alpha
22
47
  ```
23
48
 
24
- ## Basic Usage
25
-
26
- To get started with DurableWS, first import the client in your project:
49
+ ## Quick start
27
50
 
28
51
  ```ts
29
52
  import { defineClient } from "durablews";
30
- ```
31
53
 
32
- Create a new WebSocket client instance by specifying the configuration:
54
+ const client = defineClient({ url: "wss://example.com/socket" });
33
55
 
34
- ```ts
35
- const client = await defineClient({
36
- url: "wss://your.websocket.server",
37
- autoConnect: true, // Automatically connects
38
- maxReconnectAttempts: 5,
39
- maxQueueSize: 50,
40
- idleTimeout: 300000, // 5 minutes
41
- getToken: async () => {
42
- const response = await fetch("/my/token/issuer");
43
- const data = await response.json();
44
- return data.token;
45
- }
56
+ client.on("message", (data) => {
57
+ console.log("received:", data);
46
58
  });
47
- ```
48
59
 
49
- ### Sending Messages
60
+ await client.connect();
61
+ client.send({ type: "hello", message: "world" });
50
62
 
51
- ```typescript
52
- client.send({
53
- action: "greet",
54
- data: {
55
- message: "Hello, World!"
56
- }
57
- });
63
+ // later
64
+ client.close();
58
65
  ```
59
66
 
60
- ### Handling Events
67
+ Typed and validated, with any Standard Schema:
61
68
 
62
- ```typescript
63
- client.onMessage((message) => {
64
- console.log("Received message:", message);
65
- });
66
-
67
- client.onOpen(() => {
68
- console.log("Connection opened");
69
- });
70
-
71
- client.onError((error) => {
72
- console.error("Connection error:", error);
73
- });
69
+ ```ts
70
+ import { z } from "zod";
74
71
 
75
- client.onClose(() => {
76
- console.log("Connection closed");
77
- });
72
+ const Message = z.object({ type: z.string(), body: z.string() });
73
+ const client = defineClient({ url, schema: Message });
78
74
 
79
- client.onIdle(() => {
80
- console.log("Client is idle");
75
+ client.on("message", (msg) => {
76
+ // msg: { type: string; body: string } — validated at runtime
81
77
  });
82
78
  ```
83
79
 
84
- ## API Reference
80
+ ## Documentation
85
81
 
86
- ### `defineClient(config: WebSocketClientConfig): Promise<WebSocketClient>`
87
-
88
- Initializes and returns a WebSocket client instance.
89
-
90
- #### Config Options
91
-
92
- - `url`: WebSocket server URL.
93
- - `autoConnect` (optional): Whether to connect immediately upon instantiation.
94
- - `maxReconnectAttempts` (optional): Maximum number of reconnection attempts.
95
- - `maxQueueSize` (optional): Maximum size of the message queue.
96
- - `idleTimeout` (optional): Duration in milliseconds before the client is considered idle.
97
- - `authTokenURL` (optional): A URL for token issuance.
98
- - `getToken (optional): Function that returns a token for authentication. Can be synchronous or asynchronous.
99
-
100
- ### WebSocketClient Methods
101
-
102
- - `send(message: Record<string, unknown>)`: Send a message through the WebSocket connection.
103
- - `subscribe(channelName: string)`: Subscribe to a channel.
104
- - `connect()`: Manually initiate the connection.
105
- - `disconnect()`: Disconnect the WebSocket connection.
106
- - `isReady()`: Returns a promise that resolves when the connection is ready.
107
- - `getStatus()`: Returns the current connection status.
108
- - `onMessage(listener: OnMessageHandler)`: Register a message event listener.
109
- - `onError(listener: OnErrorHandler)`: Register an error event listener.
110
- - `onOpen(listener: OnOpenHandler)`: Register an open event listener.
111
- - `onClose(listener: OnCloseHandler)`: Register a close event listener.
112
- - `onIdle(listener: OnIdleHandler)`: Register an idle event listener.
82
+ **[durablews.imns.co](https://durablews.imns.co)** — getting started, guides
83
+ (durability tuning, middleware, codecs, drop-in compat), framework pages, the
84
+ API reference, and an honest comparison with the alternatives.
113
85
 
114
86
  ## Contributing
115
87
 
116
- We welcome contributions! If you'd like to contribute, please fork the repository and use a feature branch. Pull requests are warmly welcome.
117
-
118
- For major changes, please open an issue first to discuss what you would like to change.
88
+ Contributions are welcome see [CONTRIBUTING](https://github.com/imnsco/DurableWS/blob/main/CONTRIBUTING.md).
119
89
 
120
90
  ## License
121
91
 
122
- [Mozilla Public License 2.0](https://www.mozilla.org/en-US/MPL/2.0/)
92
+ [MPL-2.0](./LICENSE) © Nate Smith
@@ -0,0 +1,11 @@
1
+ import { a as WebSocketClientConfig, W as WebSocketClient } from './types-BDvztGcG.cjs';
2
+
3
+ /**
4
+ * What a framework binding accepts: either a config (the binding creates and
5
+ * **owns** the client — connecting it and closing it with the component) or an
6
+ * existing client (the binding only **observes** it — sharing one connection
7
+ * across many components without any binding taking over its lifecycle).
8
+ */
9
+ type UseWebSocketSource<TIn = unknown, TOut = unknown> = WebSocketClientConfig | WebSocketClient<TIn, TOut>;
10
+
11
+ export type { UseWebSocketSource as U };
@@ -0,0 +1,11 @@
1
+ import { a as WebSocketClientConfig, W as WebSocketClient } from './types-BDvztGcG.js';
2
+
3
+ /**
4
+ * What a framework binding accepts: either a config (the binding creates and
5
+ * **owns** the client — connecting it and closing it with the component) or an
6
+ * existing client (the binding only **observes** it — sharing one connection
7
+ * across many components without any binding taking over its lifecycle).
8
+ */
9
+ type UseWebSocketSource<TIn = unknown, TOut = unknown> = WebSocketClientConfig | WebSocketClient<TIn, TOut>;
10
+
11
+ export type { UseWebSocketSource as U };
@@ -0,0 +1,22 @@
1
+ import { client } from './chunk-MG4TK53H.js';
2
+
3
+ // src/helpers/binding.ts
4
+ function resolveSource(source) {
5
+ if (isClient(source)) {
6
+ return { client: source, owned: false };
7
+ }
8
+ return {
9
+ client: client(source),
10
+ owned: true
11
+ };
12
+ }
13
+ function isClient(source) {
14
+ return typeof source.subscribe === "function";
15
+ }
16
+ function canConnect() {
17
+ return typeof WebSocket !== "undefined";
18
+ }
19
+
20
+ export { canConnect, resolveSource };
21
+ //# sourceMappingURL=chunk-GIDTCSPD.js.map
22
+ //# sourceMappingURL=chunk-GIDTCSPD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helpers/binding.ts"],"names":[],"mappings":";;;AAuBO,SAAS,cACZ,MAAA,EACyB;AACzB,EAAA,IAAI,QAAA,CAAS,MAAM,CAAA,EAAG;AAClB,IAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,EAAQ,KAAA,EAAO,KAAA,EAAM;AAAA,EAC1C;AACA,EAAA,OAAO;AAAA,IACH,MAAA,EAAQ,OAAO,MAAM,CAAA;AAAA,IACrB,KAAA,EAAO;AAAA,GACX;AACJ;AAEA,SAAS,SACL,MAAA,EACoC;AACpC,EAAA,OAAO,OAAQ,OAA2B,SAAA,KAAc,UAAA;AAC5D;AAOO,SAAS,UAAA,GAAsB;AAClC,EAAA,OAAO,OAAO,SAAA,KAAc,WAAA;AAChC","file":"chunk-GIDTCSPD.js","sourcesContent":["import { client } from \"@/client\";\nimport type { WebSocketClient, WebSocketClientConfig } from \"@/types\";\n\n/**\n * What a framework binding accepts: either a config (the binding creates and\n * **owns** the client — connecting it and closing it with the component) or an\n * existing client (the binding only **observes** it — sharing one connection\n * across many components without any binding taking over its lifecycle).\n */\nexport type UseWebSocketSource<TIn = unknown, TOut = unknown> =\n | WebSocketClientConfig\n | WebSocketClient<TIn, TOut>;\n\nexport interface ResolvedSource<TIn = unknown, TOut = unknown> {\n readonly client: WebSocketClient<TIn, TOut>;\n /** Whether the binding created the client and therefore owns its lifecycle. */\n readonly owned: boolean;\n}\n\n/**\n * Resolves a binding source into a client plus ownership. A config produces a\n * fresh, owned client; a passed-in client is borrowed as-is.\n */\nexport function resolveSource<TIn = unknown, TOut = unknown>(\n source: UseWebSocketSource<TIn, TOut>\n): ResolvedSource<TIn, TOut> {\n if (isClient(source)) {\n return { client: source, owned: false };\n }\n return {\n client: client(source) as WebSocketClient<TIn, TOut>,\n owned: true\n };\n}\n\nfunction isClient<TIn, TOut>(\n source: UseWebSocketSource<TIn, TOut>\n): source is WebSocketClient<TIn, TOut> {\n return typeof (source as WebSocketClient).subscribe === \"function\";\n}\n\n/**\n * Whether a standard `WebSocket` global exists. Bindings skip auto-connect in\n * its absence (e.g. SSR), so creating a component server-side never throws —\n * the client connects when setup runs again in the browser.\n */\nexport function canConnect(): boolean {\n return typeof WebSocket !== \"undefined\";\n}\n"]}