@exhumer/signalr-client 1.0.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/LICENSE.md +25 -0
- package/README.md +460 -0
- package/dist/index.cjs +2787 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1079 -0
- package/dist/index.d.ts +1079 -0
- package/dist/index.js +2706 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
Copyright © 2026 eXhumer
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
|
7
|
+
obtaining a copy of this software and associated documentation
|
|
8
|
+
files (the “Software”), to deal in the Software without
|
|
9
|
+
restriction, including without limitation the rights to use,
|
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the
|
|
12
|
+
Software is furnished to do so, subject to the following
|
|
13
|
+
conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be
|
|
16
|
+
included in all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
# @exhumer/signalr-client
|
|
2
|
+
|
|
3
|
+
ASP.NET Core SignalR client for Node.js. HTTP and WebSocket layers are backed entirely by [undici](https://github.com/nodejs/undici) - no `node-fetch`, no `ws`, no browser shims required.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js ≥ 20
|
|
8
|
+
- TypeScript ≥ 5.9 (for consumers using types)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @exhumer/signalr-client
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { HubConnectionBuilder, LogLevel } from '@exhumer/signalr-client';
|
|
20
|
+
|
|
21
|
+
const connection = new HubConnectionBuilder()
|
|
22
|
+
.withUrl('https://example.com/chathub')
|
|
23
|
+
.configureLogging(LogLevel.Information)
|
|
24
|
+
.withAutomaticReconnect()
|
|
25
|
+
.build();
|
|
26
|
+
|
|
27
|
+
connection.on('ReceiveMessage', (user: string, message: string) => {
|
|
28
|
+
console.log(`${user}: ${message}`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await connection.start();
|
|
32
|
+
|
|
33
|
+
await connection.send('SendMessage', 'Alice', 'Hello!');
|
|
34
|
+
const result = await connection.invoke<string>('Echo', 'hello');
|
|
35
|
+
|
|
36
|
+
await connection.stop();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## `HubConnectionBuilder`
|
|
40
|
+
|
|
41
|
+
All configuration is done through the fluent builder before calling `build()`.
|
|
42
|
+
|
|
43
|
+
### `.withUrl(url, options?)`
|
|
44
|
+
|
|
45
|
+
Required. Sets the hub endpoint URL.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
builder.withUrl('https://example.com/hub', {
|
|
49
|
+
// Bearer token factory - called before each negotiate attempt
|
|
50
|
+
accessTokenFactory: () => fetchToken(),
|
|
51
|
+
|
|
52
|
+
// Bitmask of transports to allow (default: all three)
|
|
53
|
+
transport: HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents,
|
|
54
|
+
|
|
55
|
+
// Extra headers appended to every request
|
|
56
|
+
headers: { 'X-Tenant': 'acme' },
|
|
57
|
+
|
|
58
|
+
// Skip /negotiate and connect directly via WebSocket
|
|
59
|
+
// (only valid when transport is exclusively WebSockets)
|
|
60
|
+
skipNegotiation: true,
|
|
61
|
+
|
|
62
|
+
// How long without a server message before the connection is considered dead
|
|
63
|
+
serverTimeoutInMilliseconds: 60_000, // default: 30 000
|
|
64
|
+
|
|
65
|
+
// How often to send a keep-alive ping to the server
|
|
66
|
+
keepAliveIntervalInMilliseconds: 10_000, // default: 15 000
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `.configureLogging(logLevelOrLogger)`
|
|
71
|
+
|
|
72
|
+
Pass a `LogLevel` constant to use the built-in `ConsoleLogger`, or an object implementing `ILogger` to use your own.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
builder.configureLogging(LogLevel.Warning);
|
|
76
|
+
|
|
77
|
+
// Custom logger
|
|
78
|
+
builder.configureLogging({
|
|
79
|
+
log(level, message) { myLogger.write(level, message); },
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `.withAutomaticReconnect()`
|
|
84
|
+
|
|
85
|
+
Enable automatic reconnection on unexpected disconnects.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// Default delays: 0 ms, 2 s, 10 s, 30 s - then give up
|
|
89
|
+
builder.withAutomaticReconnect();
|
|
90
|
+
|
|
91
|
+
// Custom delay sequence (ms)
|
|
92
|
+
builder.withAutomaticReconnect([0, 1_000, 5_000, 10_000, 30_000]);
|
|
93
|
+
|
|
94
|
+
// Custom policy - return null to stop retrying
|
|
95
|
+
builder.withAutomaticReconnect({
|
|
96
|
+
nextRetryDelayInMilliseconds({ previousRetryCount, elapsedMilliseconds }) {
|
|
97
|
+
if (elapsedMilliseconds > 60_000) return null; // give up after 1 min
|
|
98
|
+
return Math.min(1_000 * 2 ** previousRetryCount, 30_000); // exponential backoff
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `.withCookies(jar?)`
|
|
104
|
+
|
|
105
|
+
Enable automatic cookie handling. Every request in the session (negotiate, WebSocket upgrade, SSE, long-polling) shares the same `CookieJar`.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { HubConnectionBuilder, CookieJar } from '@exhumer/signalr-client';
|
|
109
|
+
|
|
110
|
+
// Automatic jar - server cookies are collected and replayed automatically
|
|
111
|
+
builder.withCookies();
|
|
112
|
+
|
|
113
|
+
// Pre-seeded jar - inject an auth cookie obtained before connecting
|
|
114
|
+
const jar = new CookieJar();
|
|
115
|
+
await jar.setCookie('session=abc123', 'https://example.com');
|
|
116
|
+
builder.withCookies(jar);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`withCookies()` and `withDispatcher()` are mutually exclusive. To combine cookie support with a custom dispatcher, compose the `cookie` interceptor instead - see [Cookie handling with a custom dispatcher](#cookie-handling-with-a-custom-dispatcher).
|
|
120
|
+
|
|
121
|
+
### `.withDispatcher(dispatcher)`
|
|
122
|
+
|
|
123
|
+
Set an undici `Dispatcher` for the entire connection session. Accepts any subclass - `Agent`, `Pool`, `Client`, `ProxyAgent`, `MockAgent`, etc.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { ProxyAgent } from 'undici';
|
|
127
|
+
|
|
128
|
+
builder.withDispatcher(new ProxyAgent('http://proxy.corp:8080'));
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `.withHttpClient(client)`
|
|
132
|
+
|
|
133
|
+
Substitute the HTTP client used for negotiate, SSE, and long-polling requests. Useful for swapping to a different undici primitive or injecting a mock in tests.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { FetchHttpClient } from '@exhumer/signalr-client';
|
|
137
|
+
|
|
138
|
+
builder.withHttpClient(new FetchHttpClient());
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `.build()`
|
|
142
|
+
|
|
143
|
+
Returns a `HubConnection`. Throws if `.withUrl()` was never called.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## `HubConnection`
|
|
148
|
+
|
|
149
|
+
### Lifecycle
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
await connection.start(); // Negotiate, pick transport, perform handshake
|
|
153
|
+
await connection.stop(); // Graceful shutdown
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`start()` throws if the connection is not in the `Disconnected` state.
|
|
157
|
+
|
|
158
|
+
#### State
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
connection.state // HubConnectionState string
|
|
162
|
+
connection.connectionId // string | null - assigned after negotiate
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`HubConnectionState` values: `"Disconnected"`, `"Connecting"`, `"Connected"`, `"Disconnecting"`, `"Reconnecting"`.
|
|
166
|
+
|
|
167
|
+
### Invoking hub methods
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// invoke - awaits the server's return value
|
|
171
|
+
const result = await connection.invoke<string>('Echo', 'hello');
|
|
172
|
+
|
|
173
|
+
// send - fire-and-forget; server sends no completion message
|
|
174
|
+
await connection.send('Broadcast', 'hello everyone');
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Server streaming
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const stream = connection.stream<number>('Counter', 10);
|
|
181
|
+
|
|
182
|
+
const sub = stream.subscribe({
|
|
183
|
+
next: (value) => console.log(value),
|
|
184
|
+
error: (err) => console.error(err),
|
|
185
|
+
complete: () => console.log('done'),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Cancel early
|
|
189
|
+
sub.dispose();
|
|
190
|
+
|
|
191
|
+
// Or with the `using` keyword (TS 5.2+)
|
|
192
|
+
using sub = connection.stream<number>('Counter', 10).subscribe({ next: console.log });
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Receiving hub method calls
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
// Register a handler - multiple handlers per method are supported
|
|
199
|
+
connection.on('ReceiveMessage', (user: string, msg: string) => {
|
|
200
|
+
console.log(`${user}: ${msg}`);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Remove a specific handler
|
|
204
|
+
connection.off('ReceiveMessage', handler);
|
|
205
|
+
|
|
206
|
+
// Remove all handlers for a method
|
|
207
|
+
connection.off('ReceiveMessage');
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Connection lifecycle callbacks
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
connection.onclose((error) => {
|
|
214
|
+
if (error) console.error('Connection closed with error:', error);
|
|
215
|
+
else console.log('Connection closed cleanly.');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
connection.onreconnecting((error) => {
|
|
219
|
+
console.warn('Reconnecting...', error?.message);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
connection.onreconnected((connectionId) => {
|
|
223
|
+
console.log('Reconnected. New connection ID:', connectionId);
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Transports
|
|
230
|
+
|
|
231
|
+
The client negotiates the best available transport automatically, trying them in this order of preference:
|
|
232
|
+
|
|
233
|
+
1. **WebSockets** - full-duplex, lowest latency
|
|
234
|
+
2. **Server-Sent Events** - server-push only; client sends via separate HTTP POSTs
|
|
235
|
+
3. **Long Polling** - maximum compatibility; fallback of last resort
|
|
236
|
+
|
|
237
|
+
You can restrict which transports are attempted via the `transport` bitmask in `withUrl()`:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { HttpTransportType } from '@exhumer/signalr-client';
|
|
241
|
+
|
|
242
|
+
builder.withUrl(url, {
|
|
243
|
+
transport: HttpTransportType.WebSockets | HttpTransportType.LongPolling,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Skip negotiate and force WebSocket directly
|
|
247
|
+
builder.withUrl(url, {
|
|
248
|
+
transport: HttpTransportType.WebSockets,
|
|
249
|
+
skipNegotiation: true,
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
All three transport classes are also exported for advanced direct use: `WebSocketTransport`, `ServerSentEventsTransport`, `LongPollingTransport`.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Cookie handling with a custom dispatcher
|
|
258
|
+
|
|
259
|
+
`withCookies()` and `withDispatcher()` cannot be used together. To combine both - for example, routing through a proxy while also handling cookies - compose the `cookie` interceptor onto your dispatcher:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { ProxyAgent } from 'undici';
|
|
263
|
+
import { HubConnectionBuilder, CookieJar, cookie } from '@exhumer/signalr-client';
|
|
264
|
+
|
|
265
|
+
const jar = new CookieJar();
|
|
266
|
+
const agent = new ProxyAgent('http://proxy:8080').compose(cookie({ jar }));
|
|
267
|
+
|
|
268
|
+
const connection = new HubConnectionBuilder()
|
|
269
|
+
.withUrl('https://example.com/hub')
|
|
270
|
+
.withDispatcher(agent)
|
|
271
|
+
.build();
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`CookieJar` and `cookie` are re-exported from `@exhumer/signalr-client` - no separate install of `tough-cookie` or `@exhumer/undici-cookie-agent` is needed.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Protocols
|
|
279
|
+
|
|
280
|
+
The library currently ships two hub protocol implementations.
|
|
281
|
+
|
|
282
|
+
**JSON** (default, always active):
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { JsonHubProtocol } from '@exhumer/signalr-client';
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`HubConnection` uses `JsonHubProtocol` internally - no configuration required.
|
|
289
|
+
|
|
290
|
+
**MessagePack** (optional):
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import { MsgpackHubProtocol } from '@exhumer/signalr-client';
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`MsgpackHubProtocol` is exported for custom transport/protocol use cases. It depends on `@msgpack/msgpack` which is a regular dependency of this package.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## HTTP clients
|
|
301
|
+
|
|
302
|
+
Five undici-backed HTTP client implementations are exported. The default (`DispatchHttpClient`) is created automatically and fits most use cases. The others are available if you need a specific undici primitive.
|
|
303
|
+
|
|
304
|
+
| Export | Undici primitive | Notes |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| `DispatchHttpClient` | `Dispatcher#dispatch()` | Default. Also aliased as `HttpClient`. |
|
|
307
|
+
| `RequestHttpClient` | `undici.request()` | |
|
|
308
|
+
| `FetchHttpClient` | `undici.fetch()` | WHATWG-compatible. |
|
|
309
|
+
| `StreamHttpClient` | `undici.stream()` | Factory-callback pattern. |
|
|
310
|
+
| `PipelineHttpClient` | `undici.pipeline()` | Duplex pipe pattern. |
|
|
311
|
+
|
|
312
|
+
Inject via `.withHttpClient()` on the builder, or use standalone:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { DispatchHttpClient } from '@exhumer/signalr-client';
|
|
316
|
+
|
|
317
|
+
const client = new DispatchHttpClient();
|
|
318
|
+
const res = await client.get('https://example.com/api/data');
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Errors
|
|
324
|
+
|
|
325
|
+
All error classes are exported and support both `instanceof` checks and type guard helpers.
|
|
326
|
+
|
|
327
|
+
| Class | When thrown |
|
|
328
|
+
|---|---|
|
|
329
|
+
| `HubError` | Server-side hub method error |
|
|
330
|
+
| `AbortError` | In-flight operation cancelled (e.g. `stop()` called) |
|
|
331
|
+
| `TransportError` | Network-level failure; has `.statusCode` |
|
|
332
|
+
| `HandshakeError` | Server rejected the SignalR protocol handshake |
|
|
333
|
+
| `UnsupportedTransportError` | No acceptable transport could be negotiated |
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
import { isHubError, isAbortError, isTransportError } from '@exhumer/signalr-client';
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
await connection.invoke('DoWork');
|
|
340
|
+
} catch (err) {
|
|
341
|
+
if (isHubError(err)) console.error('Server error:', err.message);
|
|
342
|
+
else if (isAbortError(err)) console.warn('Cancelled');
|
|
343
|
+
else if (isTransportError(err)) console.error('HTTP', err.statusCode);
|
|
344
|
+
else throw err;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Logging
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
import { LogLevel, ConsoleLogger, NullLogger } from '@exhumer/signalr-client';
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`LogLevel` values (ascending severity): `Trace`, `Debug`, `Information`, `Warning`, `Error`, `Critical`, `None`.
|
|
357
|
+
|
|
358
|
+
`ConsoleLogger` routes messages to `console.debug` / `console.log` / `console.warn` / `console.error` depending on severity, prefixed with an ISO timestamp and level label.
|
|
359
|
+
|
|
360
|
+
`NullLogger` discards everything. It is the default when no logger is configured.
|
|
361
|
+
|
|
362
|
+
To plug in a third-party logger, implement `ILogger`:
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
import type { ILogger, LogLevel } from '@exhumer/signalr-client';
|
|
366
|
+
import pino from 'pino';
|
|
367
|
+
|
|
368
|
+
const logger = pino();
|
|
369
|
+
|
|
370
|
+
const signalrLogger: ILogger = {
|
|
371
|
+
log(level: LogLevel, message: string) {
|
|
372
|
+
logger.info({ level }, message);
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
builder.configureLogging(signalrLogger);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Advanced: implementing a custom retry policy
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
import type { IRetryPolicy, RetryContext } from '@exhumer/signalr-client';
|
|
385
|
+
|
|
386
|
+
class ExponentialBackoff implements IRetryPolicy {
|
|
387
|
+
nextRetryDelayInMilliseconds({ previousRetryCount, elapsedMilliseconds }: RetryContext): number | null {
|
|
388
|
+
if (elapsedMilliseconds > 2 * 60 * 1000) return null; // give up after 2 min
|
|
389
|
+
return Math.min(500 * 2 ** previousRetryCount, 30_000);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
builder.withAutomaticReconnect(new ExponentialBackoff());
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Advanced: implementing a custom transport
|
|
399
|
+
|
|
400
|
+
Implement `ITransport` to use a completely different underlying mechanism (e.g. a raw TCP socket):
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
import type { ITransport } from '@exhumer/signalr-client';
|
|
404
|
+
import { TransferFormat } from '@exhumer/signalr-client';
|
|
405
|
+
|
|
406
|
+
class MyTransport implements ITransport {
|
|
407
|
+
readonly name = 'MyTransport';
|
|
408
|
+
onreceive: ((data: string | Uint8Array) => void) | null = null;
|
|
409
|
+
onclose: ((error?: Error) => void) | null = null;
|
|
410
|
+
|
|
411
|
+
async connect(url: string, transferFormat: TransferFormat): Promise<void> { /* ... */ }
|
|
412
|
+
async send(data: string | Uint8Array): Promise<void> { /* ... */ }
|
|
413
|
+
async stop(): Promise<void> { /* ... */ }
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Build & development
|
|
420
|
+
|
|
421
|
+
```sh
|
|
422
|
+
# Build ESM + CJS bundles and type declarations
|
|
423
|
+
npm run build
|
|
424
|
+
|
|
425
|
+
# Type-check source files
|
|
426
|
+
npm run typecheck
|
|
427
|
+
|
|
428
|
+
# Type-check test files
|
|
429
|
+
npm run typecheck:test
|
|
430
|
+
|
|
431
|
+
# Run tests once
|
|
432
|
+
npm test
|
|
433
|
+
|
|
434
|
+
# Run tests in watch mode
|
|
435
|
+
npm run test:watch
|
|
436
|
+
|
|
437
|
+
# Run all benchmarks
|
|
438
|
+
npm run bench
|
|
439
|
+
|
|
440
|
+
# Run a specific benchmark suite
|
|
441
|
+
npm run bench:protocol
|
|
442
|
+
npm run bench:transport
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
The build uses [tsup](https://tsup.egoist.dev/) and produces:
|
|
446
|
+
|
|
447
|
+
| File | Format | Purpose |
|
|
448
|
+
|---|---|---|
|
|
449
|
+
| `dist/index.js` | ESM | `import` condition |
|
|
450
|
+
| `dist/index.cjs` | CJS | `require` condition |
|
|
451
|
+
| `dist/index.d.ts` | Types | ESM consumers |
|
|
452
|
+
| `dist/index.d.cts` | Types | CJS consumers |
|
|
453
|
+
|
|
454
|
+
Source maps are emitted for all four files.
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## License
|
|
459
|
+
|
|
460
|
+
MIT
|