@spinajs/http-socket-client 0.0.0-stage → 3.0.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.
Files changed (134) hide show
  1. package/README.md +129 -2
  2. package/lib/cjs/browser.d.ts +4 -0
  3. package/lib/cjs/browser.d.ts.map +1 -0
  4. package/lib/cjs/browser.js +21 -0
  5. package/lib/cjs/browser.js.map +1 -0
  6. package/lib/cjs/client.d.ts +94 -0
  7. package/lib/cjs/client.d.ts.map +1 -0
  8. package/lib/cjs/client.js +139 -0
  9. package/lib/cjs/client.js.map +1 -0
  10. package/lib/cjs/commands.d.ts +30 -0
  11. package/lib/cjs/commands.d.ts.map +1 -0
  12. package/lib/cjs/commands.js +132 -0
  13. package/lib/cjs/commands.js.map +1 -0
  14. package/lib/cjs/connection.d.ts +71 -0
  15. package/lib/cjs/connection.d.ts.map +1 -0
  16. package/lib/cjs/connection.js +233 -0
  17. package/lib/cjs/connection.js.map +1 -0
  18. package/lib/cjs/contract.d.ts +27 -0
  19. package/lib/cjs/contract.d.ts.map +1 -0
  20. package/lib/cjs/contract.js +3 -0
  21. package/lib/cjs/contract.js.map +1 -0
  22. package/lib/cjs/durable.d.ts +122 -0
  23. package/lib/cjs/durable.d.ts.map +1 -0
  24. package/lib/cjs/durable.js +396 -0
  25. package/lib/cjs/durable.js.map +1 -0
  26. package/lib/cjs/emitter.d.ts +22 -0
  27. package/lib/cjs/emitter.d.ts.map +1 -0
  28. package/lib/cjs/emitter.js +82 -0
  29. package/lib/cjs/emitter.js.map +1 -0
  30. package/lib/cjs/ids.d.ts +7 -0
  31. package/lib/cjs/ids.d.ts.map +1 -0
  32. package/lib/cjs/ids.js +11 -0
  33. package/lib/cjs/ids.js.map +1 -0
  34. package/lib/cjs/index.d.ts +17 -0
  35. package/lib/cjs/index.d.ts.map +1 -0
  36. package/lib/cjs/index.js +36 -0
  37. package/lib/cjs/index.js.map +1 -0
  38. package/lib/cjs/node/file-outbox.d.ts +40 -0
  39. package/lib/cjs/node/file-outbox.d.ts.map +1 -0
  40. package/lib/cjs/node/file-outbox.js +234 -0
  41. package/lib/cjs/node/file-outbox.js.map +1 -0
  42. package/lib/cjs/node/index.d.ts +4 -0
  43. package/lib/cjs/node/index.d.ts.map +1 -0
  44. package/lib/cjs/node/index.js +21 -0
  45. package/lib/cjs/node/index.js.map +1 -0
  46. package/lib/cjs/outbox-indexeddb.d.ts +38 -0
  47. package/lib/cjs/outbox-indexeddb.d.ts.map +1 -0
  48. package/lib/cjs/outbox-indexeddb.js +212 -0
  49. package/lib/cjs/outbox-indexeddb.js.map +1 -0
  50. package/lib/cjs/outbox.d.ts +53 -0
  51. package/lib/cjs/outbox.d.ts.map +1 -0
  52. package/lib/cjs/outbox.js +41 -0
  53. package/lib/cjs/outbox.js.map +1 -0
  54. package/lib/cjs/package.json +1 -0
  55. package/lib/cjs/reconnect.d.ts +21 -0
  56. package/lib/cjs/reconnect.d.ts.map +1 -0
  57. package/lib/cjs/reconnect.js +37 -0
  58. package/lib/cjs/reconnect.js.map +1 -0
  59. package/lib/cjs/streams.d.ts +91 -0
  60. package/lib/cjs/streams.d.ts.map +1 -0
  61. package/lib/cjs/streams.js +302 -0
  62. package/lib/cjs/streams.js.map +1 -0
  63. package/lib/cjs/timeout.d.ts +4 -0
  64. package/lib/cjs/timeout.d.ts.map +1 -0
  65. package/lib/cjs/timeout.js +13 -0
  66. package/lib/cjs/timeout.js.map +1 -0
  67. package/lib/mjs/browser.d.ts +4 -0
  68. package/lib/mjs/browser.d.ts.map +1 -0
  69. package/lib/mjs/browser.js +3 -0
  70. package/lib/mjs/browser.js.map +1 -0
  71. package/lib/mjs/client.d.ts +94 -0
  72. package/lib/mjs/client.d.ts.map +1 -0
  73. package/lib/mjs/client.js +135 -0
  74. package/lib/mjs/client.js.map +1 -0
  75. package/lib/mjs/commands.d.ts +30 -0
  76. package/lib/mjs/commands.d.ts.map +1 -0
  77. package/lib/mjs/commands.js +128 -0
  78. package/lib/mjs/commands.js.map +1 -0
  79. package/lib/mjs/connection.d.ts +71 -0
  80. package/lib/mjs/connection.d.ts.map +1 -0
  81. package/lib/mjs/connection.js +229 -0
  82. package/lib/mjs/connection.js.map +1 -0
  83. package/lib/mjs/contract.d.ts +27 -0
  84. package/lib/mjs/contract.d.ts.map +1 -0
  85. package/lib/mjs/contract.js +2 -0
  86. package/lib/mjs/contract.js.map +1 -0
  87. package/lib/mjs/durable.d.ts +122 -0
  88. package/lib/mjs/durable.d.ts.map +1 -0
  89. package/lib/mjs/durable.js +392 -0
  90. package/lib/mjs/durable.js.map +1 -0
  91. package/lib/mjs/emitter.d.ts +22 -0
  92. package/lib/mjs/emitter.d.ts.map +1 -0
  93. package/lib/mjs/emitter.js +77 -0
  94. package/lib/mjs/emitter.js.map +1 -0
  95. package/lib/mjs/ids.d.ts +7 -0
  96. package/lib/mjs/ids.d.ts.map +1 -0
  97. package/lib/mjs/ids.js +8 -0
  98. package/lib/mjs/ids.js.map +1 -0
  99. package/lib/mjs/index.d.ts +17 -0
  100. package/lib/mjs/index.d.ts.map +1 -0
  101. package/lib/mjs/index.js +9 -0
  102. package/lib/mjs/index.js.map +1 -0
  103. package/lib/mjs/node/file-outbox.d.ts +40 -0
  104. package/lib/mjs/node/file-outbox.d.ts.map +1 -0
  105. package/lib/mjs/node/file-outbox.js +230 -0
  106. package/lib/mjs/node/file-outbox.js.map +1 -0
  107. package/lib/mjs/node/index.d.ts +4 -0
  108. package/lib/mjs/node/index.d.ts.map +1 -0
  109. package/lib/mjs/node/index.js +3 -0
  110. package/lib/mjs/node/index.js.map +1 -0
  111. package/lib/mjs/outbox-indexeddb.d.ts +38 -0
  112. package/lib/mjs/outbox-indexeddb.d.ts.map +1 -0
  113. package/lib/mjs/outbox-indexeddb.js +208 -0
  114. package/lib/mjs/outbox-indexeddb.js.map +1 -0
  115. package/lib/mjs/outbox.d.ts +53 -0
  116. package/lib/mjs/outbox.d.ts.map +1 -0
  117. package/lib/mjs/outbox.js +37 -0
  118. package/lib/mjs/outbox.js.map +1 -0
  119. package/lib/mjs/package.json +1 -0
  120. package/lib/mjs/reconnect.d.ts +21 -0
  121. package/lib/mjs/reconnect.d.ts.map +1 -0
  122. package/lib/mjs/reconnect.js +33 -0
  123. package/lib/mjs/reconnect.js.map +1 -0
  124. package/lib/mjs/streams.d.ts +91 -0
  125. package/lib/mjs/streams.d.ts.map +1 -0
  126. package/lib/mjs/streams.js +297 -0
  127. package/lib/mjs/streams.js.map +1 -0
  128. package/lib/mjs/timeout.d.ts +4 -0
  129. package/lib/mjs/timeout.d.ts.map +1 -0
  130. package/lib/mjs/timeout.js +9 -0
  131. package/lib/mjs/timeout.js.map +1 -0
  132. package/lib/tsconfig.cjs.tsbuildinfo +1 -0
  133. package/lib/tsconfig.mjs.tsbuildinfo +1 -0
  134. package/package.json +67 -4
package/README.md CHANGED
@@ -1,3 +1,130 @@
1
- # Temporary Holding Version
1
+ # @spinajs/http-socket-client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Client for `@spinajs/http-socket`, for browsers and Node. It handles the connection state, reconnects with backoff, and offers commands with timeouts and cancellation, server events, stream replies with flow control, and typed contracts. Its only dependencies are `socket.io-client` and `@spinajs/http-socket-common`, which it re-exports.
4
+
5
+ A runnable server and client are in [`packages/http-socket-example`](../http-socket-example).
6
+
7
+ Full documentation, including outbox stores and every extension point: [`docs/http-socket-client`](../../docs/http-socket-client/README.md).
8
+
9
+ ## Connect
10
+
11
+ ```ts
12
+ import { SocketClient } from '@spinajs/http-socket-client';
13
+
14
+ const client = new SocketClient({
15
+ url: 'https://app.example.com',
16
+ clientId: storedClientId, // stable per install; generated when omitted
17
+ auth: async () => ({ token: await tokens.current() }), // asked on every (re)connect
18
+ reconnect: { minMs: 500, maxMs: 30_000, jitter: 0.3 }, // the defaults
19
+ defaultTimeoutMs: 15_000, // the default
20
+ });
21
+ ```
22
+
23
+ `storedClientId` and `tokens` stand for your own code. Other options: `path` (socket.io endpoint, `/socket.io` by default), `connectTimeoutMs` (20 s), `io` (passed to `socket.io-client`; reconnection, `autoConnect`, `forceNew`, `retries` and `auth` stay owned by the client) and `onListenerError`.
24
+
25
+ - The client connects on its own unless `autoConnect: false`.
26
+ - `client.state` is `'connecting' | 'online' | 'offline' | 'closed'`, and `client.on('state', (state, previous) => ...)` reports every change.
27
+ - `disconnect()` goes offline until `connect()`. `close()` is final: it rejects everything pending, except durable commands the server already answered (see Durable commands). It returns a promise; see below.
28
+
29
+ **Reconnect.** The client retries with exponential backoff and jitter, and asks `auth()` again on every attempt. It stops in these cases:
30
+
31
+ | Server answer | Lifecycle event |
32
+ |---|---|
33
+ | `unauthenticated` | `auth_required` |
34
+ | `version_mismatch` | `incompatible` |
35
+ | any other refusal with `retryable: false`, except `internal`, which is retried (`forbidden` for a foreign origin, `invalid_argument` ...) | `refused` |
36
+
37
+ `connect()` tries again. A `sys.shutdown { retryAfterMs }` notice delays the next attempt. A `sys.disconnect` whose reason is a non-retryable error code (the server sends `unauthenticated` for a revoked credential) stops the client in the same way.
38
+
39
+ ## Commands
40
+
41
+ ```ts
42
+ const doc = await client.command<OpenResult>('docs.open', { id }, { timeoutMs: 5_000, signal, whenOffline: 'wait' });
43
+ ```
44
+
45
+ - `timeoutMs` covers the whole call. The server gets the remaining time as `deadline`.
46
+ - `whenOffline: 'fail'` (the default) rejects at once with `unavailable`. `'wait'` holds the command until the client is online or the timeout passes.
47
+ - Aborting `signal` sends `cancel`; the server's handler sees its own signal fire.
48
+ - A drop in the middle of a command rejects it with a retryable `unavailable`. Plain commands are never sent twice.
49
+ - Every rejection is a `SocketException` with `code`, `message`, `retryable` and, when the server sent them, `retryAfterMs`, `details` and `errorId`. Trust `retryable` and `retryAfterMs` when deciding to retry.
50
+
51
+ ## Events
52
+
53
+ ```ts
54
+ const off = client.on<Committed>('docs.committed', (payload) => apply(payload));
55
+ client.emit('docs.cursor', { x, y }); // fire-and-forget; false and dropped when not online
56
+ ```
57
+
58
+ The lifecycle names (`state`, `auth_required`, `incompatible`, `refused`, `acked`, `failed`, `held`) are routed to the client's own listeners, so a server event with one of these names never reaches `on()`.
59
+
60
+ ## Durable commands
61
+
62
+ ```ts
63
+ import { SocketClient } from '@spinajs/http-socket-client';
64
+ import { IndexedDbOutboxStore } from '@spinajs/http-socket-client/browser'; // Node: FileOutboxStore from '/node'
65
+
66
+ const client = new SocketClient({ url, clientId: storedClientId, outbox: new IndexedDbOutboxStore('app') });
67
+ client.on('acked', ({ entry, result }) => {});
68
+ client.on('failed', ({ entry, error }) => {}); // refused for good; the entry waits in the store's failed list
69
+ await client.durable('docs.edit', { id, op }, { key: opId }).catch(() => undefined);
70
+ ```
71
+
72
+ - The command is written to the outbox before it is sent, then sent in order, up to `durable.window` (16) at once, each with `after` set to the previous unacknowledged command. It carries no deadline. The server command must be `idempotent`. With `ordered: 'client'` it waits in the server's queue behind its predecessor; without it, the server only checks that the predecessor has completed and otherwise answers a retryable `unavailable`, which parks it until the predecessor's answer, then sends it again. Either way it runs after its predecessor, whatever its queue position.
73
+ - After any drop everything unacknowledged is sent again, and so is a command unanswered for `durable.ackTimeoutMs` (60 s). Retryable errors are retried with the reconnect backoff.
74
+ - `unauthenticated` keeps the queue and emits `held`; nothing is sent until the connection comes online again.
75
+ - The key is your `key` (1 to 256 printable ASCII characters, not starting with `${clientId}:`) or a generated `${clientId}:<nonce>`.
76
+ - Any `outbox` option, an explicit `MemoryOutboxStore` included, requires an explicit `clientId` stored by the app (the constructor throws a `RangeError` otherwise): a new random id after a reload would break exactly-once and ordering. Without the option, the default `MemoryOutboxStore` is used; it does not survive a reload.
77
+ - `durable()` rejects when the server refuses the command for good and when the client closes before the server answered it (the entry stays in the outbox). A command the server already answered settles with that answer once the outbox has applied it, also after `close()`. Attach a `catch` or follow `acked` / `failed`.
78
+ - `acked` and `failed` fire in send order, also for entries resumed from the outbox after a reload. The exception is `close()`: a command the server answered settles after the close, while an earlier one still unanswered is rejected and reported by the next client.
79
+ - `await client.close()` before you open the next client or store on the same outbox, and keep your `acked` / `failed` listeners until it resolves. It resolves once the outbox has no write of this client in flight and every answered command has been reported. An answered command whose write lands after the close is reported only there: a resumed entry has no caller, and its `acked` is its only report. A `FileOutboxStore` opened on the same file while the old one still writes would overwrite that file from a stale copy, dropping new entries and bringing back removed ones.
80
+ - `FileOutboxStore` keeps payloads as JSON, so its `add` (and `durable()`) rejects with a `TypeError` a payload that a JSON round trip would change: `Uint8Array`, `Buffer`, `ArrayBuffer`, `Date`, `Map`, `Set`, class instances, `NaN`/`Infinity`, `undefined` inside an array. Nothing is written. Encode such values yourself (bytes as base64, dates as ISO strings). An `undefined` property is allowed: the wire drops it too. `IndexedDbOutboxStore` and `MemoryOutboxStore` keep binary.
81
+
82
+ ## Streams
83
+
84
+ ```ts
85
+ for await (const frame of client.stream<SnapshotFrame>('docs.snapshot', { docId }, { credits: 8 })) {
86
+ apply(frame);
87
+ if (cancelled) break; // sends cancel; the server generator's finally runs
88
+ }
89
+ ```
90
+
91
+ - `stream()` returns an async iterator. The request goes out on the first `next()`. Calls to `next()` must not overlap.
92
+ - **Flow control.**
93
+ - `credits` (default `DEFAULT_STREAM_CREDITS`, 8) is the most chunks that are ever in flight or buffered.
94
+ - The client grants more as you read, in batches of at least half the window.
95
+ - It must not exceed the server's `http.socket.streams.maxCredits` (64 by default). The server also refuses a window that, with what is granted but not yet used, goes over it.
96
+ - **Timeouts.** `timeoutMs` bounds the wait for the reply, and then each wait in `next()` for the next frame. A consumer that is slow to call `next()` is never timed out. The server ends a stream that gets no credit for its `streams.stallTimeoutMs`.
97
+ - **Failures.** On a server `fail`, a dropped connection (`unavailable`, retryable) or a closed client, the chunks already received are delivered first, then the iterator throws. Aborting `signal` drops buffered chunks, cancels the stream and throws `cancelled`.
98
+ - **Mismatches.** `stream()` on a command that answers a plain result throws `failed_precondition`, and so does `command()` on a stream command.
99
+ - **Resume.** The library does not resume streams. Put the position in the payload (for example `fromChunk`) and start a new stream after a retryable failure; with `whenOffline: 'wait'` it waits for the reconnect. `loadSnapshot` in [`packages/http-socket-example/src/client.ts`](../http-socket-example/src/client.ts) is a complete example.
100
+
101
+ ## Typed contracts
102
+
103
+ One contract module, shared by the server and the client, types names and payloads:
104
+
105
+ ```ts
106
+ import { defineContract } from '@spinajs/http-socket-client';
107
+
108
+ export type DocsContract = {
109
+ commands: {
110
+ 'docs.open': [{ id: string }, { title: string }];
111
+ 'docs.snapshot': [{ docId: string; fromChunk?: number }, AsyncIterable<SnapshotFrame>];
112
+ };
113
+ events: { 'docs.committed': { seq: number } };
114
+ clientEvents: { 'docs.cursor': { x: number; y: number } };
115
+ };
116
+
117
+ export const docsContract = defineContract<DocsContract>();
118
+
119
+ const docs = client.typed(docsContract);
120
+ const opened = await docs.command('docs.open', { id: 'a' }); // { title: string }
121
+ for await (const frame of docs.stream('docs.snapshot', { docId: 'a' })) {
122
+ } // frame: SnapshotFrame
123
+ docs.on('docs.committed', ({ seq }) => {});
124
+ ```
125
+
126
+ Declare contracts with `type`, not `interface`. A stream command declares its result as `AsyncIterable<Item>`. The server side imports `defineContract` and the contract types from `@spinajs/http-socket-common` (or `@spinajs/http-socket`, which re-exports it); [`packages/http-socket-example/src/contract.ts`](../http-socket-example/src/contract.ts) is shared by both sides.
127
+
128
+ ## Binary
129
+
130
+ `Uint8Array`, `ArrayBuffer` and `Buffer` anywhere in a payload travel as binary attachments and count toward the server's `maxPayloadBytes` by byte length. Binary results arrive as an `ArrayBuffer` in browsers and as a `Buffer` in Node; `new Uint8Array(result)` reads both.
@@ -0,0 +1,4 @@
1
+ export * from './index.js';
2
+ export { IndexedDbOutboxStore } from './outbox-indexeddb.js';
3
+ export type { IndexedDbOutboxOptions } from './outbox-indexeddb.js';
4
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.IndexedDbOutboxStore = void 0;
18
+ __exportStar(require("./index.js"), exports);
19
+ var outbox_indexeddb_js_1 = require("./outbox-indexeddb.js");
20
+ Object.defineProperty(exports, "IndexedDbOutboxStore", { enumerable: true, get: function () { return outbox_indexeddb_js_1.IndexedDbOutboxStore; } });
21
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAA2B;AAC3B,6DAA6D;AAApD,2HAAA,oBAAoB,OAAA"}
@@ -0,0 +1,94 @@
1
+ import { type ContractShape, type SocketContract, type SocketError } from '@spinajs/http-socket-common';
2
+ import { type CommandOptions } from './commands.js';
3
+ import { type AuthProvider, type ConnectionState, type IoOptions } from './connection.js';
4
+ import type { TypedSocketClient } from './contract.js';
5
+ import { type AckedCommand, type DurableCommandOptions, type DurableOptions, type FailedCommand, type HeldCommand } from './durable.js';
6
+ import { type OutboxStore } from './outbox.js';
7
+ import type { ReconnectOptions } from './reconnect.js';
8
+ import { type StreamOptions } from './streams.js';
9
+ export interface SocketClientOptions {
10
+ /** Server origin; a path in it selects a socket.io namespace. */
11
+ url: string;
12
+ /** socket.io endpoint path (socket.io's default: '/socket.io'). */
13
+ path?: string;
14
+ /** Asked on every (re)connect; its fields go into the handshake. */
15
+ auth?: AuthProvider;
16
+ /** Stable per install, persisted by the app; 1 to MAX_ID_LENGTH printable ASCII characters. Generated when omitted. */
17
+ clientId?: string;
18
+ reconnect?: Partial<ReconnectOptions>;
19
+ defaultTimeoutMs?: number;
20
+ connectTimeoutMs?: number;
21
+ autoConnect?: boolean;
22
+ /** Passed to socket.io-client; reconnection, autoConnect, forceNew, retries and auth stay owned by the client. */
23
+ io?: IoOptions;
24
+ /**
25
+ * Where durable commands wait for their reply. The MemoryOutboxStore default does not survive a reload: browsers pass
26
+ * IndexedDbOutboxStore ( `/browser` ), Node passes FileOutboxStore ( `/node` ). Requires an explicit `clientId`.
27
+ */
28
+ outbox?: OutboxStore;
29
+ durable?: DurableOptions;
30
+ onListenerError?: (error: unknown) => void;
31
+ }
32
+ export interface ClientLifecycleEvents {
33
+ state: (state: ConnectionState, previous: ConnectionState) => void;
34
+ auth_required: (error: SocketError) => void;
35
+ incompatible: (error: SocketError) => void;
36
+ /** The server refused the connection for a reason no retry can fix (forbidden, invalid_argument …); connect() tries again. */
37
+ refused: (error: SocketError) => void;
38
+ /** A durable command was acknowledged; also for commands resumed from the outbox after a reload. In send order. */
39
+ acked: (event: AckedCommand) => void;
40
+ /** A durable command the server refused for good; it is in the outbox's failed list. In send order. */
41
+ failed: (event: FailedCommand) => void;
42
+ /** A durable command was refused as unauthenticated and stays queued; the queue sends nothing until the connection comes online again. */
43
+ held: (event: HeldCommand) => void;
44
+ }
45
+ export type LifecycleEventName = keyof ClientLifecycleEvents;
46
+ /** Names `on()` routes to client lifecycle listeners; server events with these names are unreachable. */
47
+ export declare const LIFECYCLE_EVENTS: ReadonlySet<string>;
48
+ export type ServerEventListener<T = unknown> = (payload: T) => void;
49
+ export type Unsubscribe = () => void;
50
+ export declare class SocketClient {
51
+ readonly clientId: string;
52
+ private readonly connection;
53
+ private readonly commands;
54
+ private readonly durableQueue;
55
+ private readonly streams;
56
+ private readonly lifecycle;
57
+ private readonly serverEvents;
58
+ private controlled;
59
+ constructor(options: SocketClientOptions);
60
+ private autoConnect;
61
+ get state(): ConnectionState;
62
+ connect(): void;
63
+ /** Goes offline and stops reconnecting until connect(). */
64
+ disconnect(): void;
65
+ /**
66
+ * Terminal: rejects pending commands and releases the socket. Resolves once the outbox has no write of this
67
+ * client in flight and every answered durable command has been reported: await it before opening the next
68
+ * client or store on the same outbox, and keep the `acked` / `failed` listeners until then.
69
+ * The outbox store is not closed: call `store.close()` yourself to release its lock and connection before reopening the same name.
70
+ */
71
+ close(): Promise<void>;
72
+ /** The same client, typed by a contract shared with the server; no runtime cost. */
73
+ typed<C extends ContractShape>(_contract: SocketContract<C>): TypedSocketClient<C>;
74
+ command<R = unknown>(name: string, payload?: unknown, options?: CommandOptions): Promise<R>;
75
+ /**
76
+ * Persisted, ordered and retried until the server answers ( spec §6 ); the server command must be idempotent; `ordered: 'client'` is optional, the `after` fence alone orders execution.
77
+ * With the default MemoryOutboxStore the queue is lost on reload; pass `outbox` ( IndexedDbOutboxStore in browsers, FileOutboxStore in Node ) to keep it.
78
+ */
79
+ durable<R = unknown>(name: string, payload?: unknown, options?: DurableCommandOptions): Promise<R>;
80
+ /**
81
+ * A stream reply as an async iterator. The request goes out on the first `next()`; at most
82
+ * `credits` chunks are in flight or buffered; `break` cancels it on the server.
83
+ */
84
+ stream<T = unknown>(name: string, payload?: unknown, options?: StreamOptions): AsyncIterableIterator<T>;
85
+ /** Fire-and-forget event to the server; returns false and drops it when not online. */
86
+ emit(name: string, payload?: unknown): boolean;
87
+ on<K extends LifecycleEventName>(event: K, listener: ClientLifecycleEvents[K]): Unsubscribe;
88
+ on<T = unknown>(event: string, listener: ServerEventListener<T>): Unsubscribe;
89
+ once<K extends LifecycleEventName>(event: K, listener: ClientLifecycleEvents[K]): Unsubscribe;
90
+ once<T = unknown>(event: string, listener: ServerEventListener<T>): Unsubscribe;
91
+ off<K extends LifecycleEventName>(event: K, listener: ClientLifecycleEvents[K]): void;
92
+ off<T = unknown>(event: string, listener: ServerEventListener<T>): void;
93
+ }
94
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SocketClient = exports.LIFECYCLE_EVENTS = void 0;
4
+ const http_socket_common_1 = require("@spinajs/http-socket-common");
5
+ const commands_js_1 = require("./commands.js");
6
+ const connection_js_1 = require("./connection.js");
7
+ const durable_js_1 = require("./durable.js");
8
+ const emitter_js_1 = require("./emitter.js");
9
+ const ids_js_1 = require("./ids.js");
10
+ const outbox_js_1 = require("./outbox.js");
11
+ const streams_js_1 = require("./streams.js");
12
+ /** Names `on()` routes to client lifecycle listeners; server events with these names are unreachable. */
13
+ exports.LIFECYCLE_EVENTS = new Set(['state', 'auth_required', 'incompatible', 'refused', 'acked', 'failed', 'held']);
14
+ function isLifecycleEvent(event) {
15
+ return exports.LIFECYCLE_EVENTS.has(event);
16
+ }
17
+ class SocketClient {
18
+ constructor(options) {
19
+ // set once the app calls connect(), disconnect() or close(), which then override autoConnect
20
+ this.controlled = false;
21
+ const onListenerError = options.onListenerError ?? emitter_js_1.rethrowAsync;
22
+ // persisted keys are `${clientId}:…`: a fresh random id after a reload would break exactly-once and ordering
23
+ if (options.outbox !== undefined && options.clientId === undefined) {
24
+ throw new RangeError('an outbox requires a stable clientId, persisted by the app');
25
+ }
26
+ this.clientId = options.clientId ?? (0, ids_js_1.randomId)();
27
+ this.lifecycle = new emitter_js_1.Emitter(onListenerError);
28
+ this.serverEvents = new emitter_js_1.Emitter(onListenerError);
29
+ this.connection = new connection_js_1.Connection({
30
+ url: options.url,
31
+ path: options.path,
32
+ clientId: this.clientId,
33
+ auth: options.auth,
34
+ reconnect: options.reconnect,
35
+ connectTimeoutMs: options.connectTimeoutMs,
36
+ io: options.io,
37
+ onListenerError,
38
+ });
39
+ try {
40
+ this.commands = new commands_js_1.CommandDispatcher(this.connection, options.defaultTimeoutMs ?? commands_js_1.DEFAULT_TIMEOUT_MS);
41
+ this.durableQueue = new durable_js_1.DurableQueue({
42
+ connection: this.connection,
43
+ commands: this.commands,
44
+ store: options.outbox ?? new outbox_js_1.MemoryOutboxStore(),
45
+ clientId: this.clientId,
46
+ reconnect: options.reconnect,
47
+ durable: options.durable,
48
+ onListenerError,
49
+ });
50
+ }
51
+ catch (error) {
52
+ // invalid options must not leave a socket behind
53
+ this.connection.close();
54
+ throw error;
55
+ }
56
+ this.durableQueue.events.on('acked', (event) => this.lifecycle.emit('acked', event));
57
+ this.durableQueue.events.on('failed', (event) => this.lifecycle.emit('failed', event));
58
+ this.durableQueue.events.on('held', (event) => this.lifecycle.emit('held', event));
59
+ this.streams = new streams_js_1.StreamDispatcher(this.connection, () => this.commands.nextId(), options.defaultTimeoutMs ?? commands_js_1.DEFAULT_TIMEOUT_MS);
60
+ this.connection.events.on('state', (state, previous) => this.lifecycle.emit('state', state, previous));
61
+ this.connection.events.on('auth_required', (error) => this.lifecycle.emit('auth_required', error));
62
+ this.connection.events.on('incompatible', (error) => this.lifecycle.emit('incompatible', error));
63
+ this.connection.events.on('refused', (error) => this.lifecycle.emit('refused', error));
64
+ this.connection.listen(http_socket_common_1.WIRE_EVENTS.event, (frame) => {
65
+ if (typeof frame?.name === 'string')
66
+ this.serverEvents.emit(frame.name, frame.payload);
67
+ });
68
+ // a microtask, so listeners attached right after construction still see 'connecting'
69
+ if (options.autoConnect !== false)
70
+ queueMicrotask(() => this.autoConnect());
71
+ }
72
+ autoConnect() {
73
+ if (!this.controlled)
74
+ this.connection.connect();
75
+ }
76
+ get state() {
77
+ return this.connection.state;
78
+ }
79
+ connect() {
80
+ this.controlled = true;
81
+ this.connection.connect();
82
+ }
83
+ /** Goes offline and stops reconnecting until connect(). */
84
+ disconnect() {
85
+ this.controlled = true;
86
+ this.connection.disconnect();
87
+ }
88
+ /**
89
+ * Terminal: rejects pending commands and releases the socket. Resolves once the outbox has no write of this
90
+ * client in flight and every answered durable command has been reported: await it before opening the next
91
+ * client or store on the same outbox, and keep the `acked` / `failed` listeners until then.
92
+ * The outbox store is not closed: call `store.close()` yourself to release its lock and connection before reopening the same name.
93
+ */
94
+ close() {
95
+ this.controlled = true;
96
+ this.connection.close();
97
+ return this.durableQueue.close();
98
+ }
99
+ /** The same client, typed by a contract shared with the server; no runtime cost. */
100
+ typed(_contract) {
101
+ return this;
102
+ }
103
+ command(name, payload, options) {
104
+ return this.commands.execute(name, payload, options);
105
+ }
106
+ /**
107
+ * Persisted, ordered and retried until the server answers ( spec §6 ); the server command must be idempotent; `ordered: 'client'` is optional, the `after` fence alone orders execution.
108
+ * With the default MemoryOutboxStore the queue is lost on reload; pass `outbox` ( IndexedDbOutboxStore in browsers, FileOutboxStore in Node ) to keep it.
109
+ */
110
+ durable(name, payload, options) {
111
+ return this.durableQueue.submit(name, payload, options);
112
+ }
113
+ /**
114
+ * A stream reply as an async iterator. The request goes out on the first `next()`; at most
115
+ * `credits` chunks are in flight or buffered; `break` cancels it on the server.
116
+ */
117
+ stream(name, payload, options) {
118
+ return this.streams.stream(name, payload, options);
119
+ }
120
+ /** Fire-and-forget event to the server; returns false and drops it when not online. */
121
+ emit(name, payload) {
122
+ const frame = { name, payload };
123
+ return this.connection.send(http_socket_common_1.WIRE_EVENTS.event, frame);
124
+ }
125
+ on(event, listener) {
126
+ return isLifecycleEvent(event) ? this.lifecycle.on(event, listener) : this.serverEvents.on(event, listener);
127
+ }
128
+ once(event, listener) {
129
+ return isLifecycleEvent(event) ? this.lifecycle.once(event, listener) : this.serverEvents.once(event, listener);
130
+ }
131
+ off(event, listener) {
132
+ if (isLifecycleEvent(event))
133
+ this.lifecycle.off(event, listener);
134
+ else
135
+ this.serverEvents.off(event, listener);
136
+ }
137
+ }
138
+ exports.SocketClient = SocketClient;
139
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { Connection } from './connection.js';
2
+ export type WhenOffline = 'fail' | 'wait';
3
+ export interface CommandOptions {
4
+ /** Budget for the whole call: the wait for a connection (whenOffline 'wait') and for the reply. */
5
+ timeoutMs?: number;
6
+ signal?: AbortSignal;
7
+ whenOffline?: WhenOffline;
8
+ /** W3C trace context, carried into the server handler. */
9
+ traceparent?: string;
10
+ }
11
+ export declare const DEFAULT_TIMEOUT_MS = 15000;
12
+ /** Plain (non-durable) commands: one attempt each, never re-sent. */
13
+ export declare class CommandDispatcher {
14
+ private readonly connection;
15
+ private readonly defaultTimeoutMs;
16
+ private seq;
17
+ private readonly waiting;
18
+ private readonly inFlight;
19
+ constructor(connection: Connection, defaultTimeoutMs?: number);
20
+ /** Unique per client instance; durable and stream requests take ids from here too. */
21
+ nextId(): string;
22
+ execute(name: string, payload: unknown, options?: CommandOptions): Promise<unknown>;
23
+ private transmit;
24
+ private onReply;
25
+ private onState;
26
+ private succeed;
27
+ private fail;
28
+ private finish;
29
+ }
30
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,iBAAiB,CAAC;AAGnE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1C,MAAM,WAAW,cAAc;IAC7B,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAuBzC,qEAAqE;AACrE,qBAAa,iBAAiB;IAMhB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAAc,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IALtF,OAAO,CAAC,GAAG,CAAK;IAEhB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IAEtD,YAA6B,UAAU,EAAE,UAAU,EAAmB,gBAAgB,GAAE,MAA2B,EAGlH;IAED,sFAAsF;IAC/E,MAAM,IAAI,MAAM,CAEtB;IAEM,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAwC7F;IAED,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,MAAM;CAQf"}
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommandDispatcher = exports.DEFAULT_TIMEOUT_MS = void 0;
4
+ const http_socket_common_1 = require("@spinajs/http-socket-common");
5
+ const timeout_js_1 = require("./timeout.js");
6
+ exports.DEFAULT_TIMEOUT_MS = 15_000;
7
+ const CLOSED = { code: 'unavailable', message: 'the client is closed', retryable: false };
8
+ const OFFLINE = { code: 'unavailable', message: 'the client is offline', retryable: true };
9
+ const STAYED_OFFLINE = { code: 'unavailable', message: 'the client stayed offline until the timeout', retryable: true };
10
+ const CONNECTION_LOST = { code: 'unavailable', message: 'the connection dropped before the reply', retryable: true };
11
+ const NO_REPLY = { code: 'deadline_exceeded', message: 'no reply before the timeout', retryable: true };
12
+ const CANCELLED = { code: 'cancelled', message: 'the command was cancelled', retryable: false };
13
+ const STREAM_REPLY = { code: 'failed_precondition', message: 'the command replies with a stream; use stream()', retryable: false };
14
+ const MALFORMED_REPLY = { code: 'internal', message: 'malformed reply', retryable: false };
15
+ /** Plain (non-durable) commands: one attempt each, never re-sent. */
16
+ class CommandDispatcher {
17
+ constructor(connection, defaultTimeoutMs = exports.DEFAULT_TIMEOUT_MS) {
18
+ this.connection = connection;
19
+ this.defaultTimeoutMs = defaultTimeoutMs;
20
+ this.seq = 0;
21
+ // insertion-ordered, so commands held while offline go out in call order
22
+ this.waiting = new Set();
23
+ this.inFlight = new Set();
24
+ (0, timeout_js_1.assertTimeoutMs)('defaultTimeoutMs', defaultTimeoutMs);
25
+ connection.events.on('state', (state, previous) => this.onState(state, previous));
26
+ }
27
+ /** Unique per client instance; durable and stream requests take ids from here too. */
28
+ nextId() {
29
+ return (++this.seq).toString(36);
30
+ }
31
+ execute(name, payload, options = {}) {
32
+ const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
33
+ try {
34
+ (0, timeout_js_1.assertTimeoutMs)('timeoutMs', timeoutMs);
35
+ }
36
+ catch (error) {
37
+ return Promise.reject(error);
38
+ }
39
+ if (this.connection.state === 'closed')
40
+ return rejected(CLOSED);
41
+ if (options.signal?.aborted)
42
+ return rejected(CANCELLED);
43
+ if (!this.connection.connected && (options.whenOffline ?? 'fail') === 'fail')
44
+ return rejected(OFFLINE);
45
+ const request = { id: this.nextId(), name, payload };
46
+ if (options.traceparent !== undefined)
47
+ request.traceparent = options.traceparent;
48
+ const signal = options.signal;
49
+ return new Promise((resolve, reject) => {
50
+ const onAbort = () => {
51
+ if (command.sent) {
52
+ const cancel = { id: request.id };
53
+ this.connection.send(http_socket_common_1.WIRE_EVENTS.cancel, cancel);
54
+ }
55
+ this.fail(command, CANCELLED);
56
+ };
57
+ const timer = setTimeout(() => this.fail(command, command.sent ? NO_REPLY : STAYED_OFFLINE), timeoutMs);
58
+ const command = {
59
+ request,
60
+ deadlineAt: Date.now() + timeoutMs,
61
+ sent: false,
62
+ done: false,
63
+ resolve,
64
+ reject,
65
+ dispose: () => {
66
+ clearTimeout(timer);
67
+ signal?.removeEventListener('abort', onAbort);
68
+ },
69
+ };
70
+ signal?.addEventListener('abort', onAbort, { once: true });
71
+ if (this.connection.connected)
72
+ this.transmit(command);
73
+ else
74
+ this.waiting.add(command);
75
+ });
76
+ }
77
+ transmit(command) {
78
+ this.waiting.delete(command);
79
+ // relative, so client/server clock skew cannot expire it; the server ignores a deadline <= 0
80
+ command.request.deadline = Math.max(1, command.deadlineAt - Date.now());
81
+ command.sent = true;
82
+ this.inFlight.add(command);
83
+ const sent = this.connection.request(http_socket_common_1.WIRE_EVENTS.command, command.request, (reply) => this.onReply(command, reply));
84
+ if (!sent)
85
+ this.fail(command, CONNECTION_LOST);
86
+ }
87
+ onReply(command, reply) {
88
+ if (typeof reply !== 'object' || reply === null || typeof reply.ok !== 'boolean')
89
+ return this.fail(command, MALFORMED_REPLY);
90
+ if (!reply.ok)
91
+ return this.fail(command, (0, http_socket_common_1.isSocketError)(reply.error) ? reply.error : MALFORMED_REPLY);
92
+ // a CommandStreamReply: the result follows as stream frames, which only stream() consumes
93
+ if ('stream' in reply)
94
+ return this.fail(command, STREAM_REPLY);
95
+ this.succeed(command, reply.result);
96
+ }
97
+ onState(state, previous) {
98
+ if (state === 'closed') {
99
+ for (const command of [...this.waiting, ...this.inFlight])
100
+ this.fail(command, CLOSED);
101
+ return;
102
+ }
103
+ if (previous === 'online')
104
+ for (const command of [...this.inFlight])
105
+ this.fail(command, CONNECTION_LOST);
106
+ if (state === 'online')
107
+ for (const command of [...this.waiting])
108
+ this.transmit(command);
109
+ }
110
+ succeed(command, value) {
111
+ if (this.finish(command))
112
+ command.resolve(value);
113
+ }
114
+ fail(command, error) {
115
+ if (this.finish(command))
116
+ command.reject(http_socket_common_1.SocketException.fromWire(error));
117
+ }
118
+ finish(command) {
119
+ if (command.done)
120
+ return false;
121
+ command.done = true;
122
+ command.dispose();
123
+ this.waiting.delete(command);
124
+ this.inFlight.delete(command);
125
+ return true;
126
+ }
127
+ }
128
+ exports.CommandDispatcher = CommandDispatcher;
129
+ function rejected(error) {
130
+ return Promise.reject(http_socket_common_1.SocketException.fromWire(error));
131
+ }
132
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"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"}