@api.global/typedsocket 5.1.2 → 6.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.clienttagreconciler.d.ts +51 -0
- package/dist_ts/classes.clienttagreconciler.js +290 -0
- package/dist_ts/classes.nativebyteerror.d.ts +13 -0
- package/dist_ts/classes.nativebyteerror.js +26 -0
- package/dist_ts/classes.nativebytemanager.d.ts +327 -0
- package/dist_ts/classes.nativebytemanager.js +2343 -0
- package/dist_ts/{typedsocket.classes.typedsocket.d.ts → classes.typedsocket.d.ts} +122 -13
- package/dist_ts/classes.typedsocket.js +1733 -0
- package/dist_ts/classes.typedsockettagpolicymanager.d.ts +112 -0
- package/dist_ts/classes.typedsockettagpolicymanager.js +549 -0
- package/dist_ts/constants.nativebytes.d.ts +59 -0
- package/dist_ts/constants.nativebytes.js +58 -0
- package/dist_ts/helpers.nativebytecodec.d.ts +8 -0
- package/dist_ts/helpers.nativebytecodec.js +328 -0
- package/dist_ts/helpers.websocket.d.ts +8 -0
- package/dist_ts/helpers.websocket.js +13 -0
- package/dist_ts/index.d.ts +9 -1
- package/dist_ts/index.js +6 -2
- package/dist_ts/interfaces.diagnostics.d.ts +73 -0
- package/dist_ts/interfaces.diagnostics.js +2 -0
- package/dist_ts/interfaces.nativebytes.d.ts +187 -0
- package/dist_ts/interfaces.nativebytes.js +2 -0
- package/{ts/typedsocket.plugins.ts → dist_ts/plugins.d.ts} +3 -9
- package/dist_ts/plugins.js +14 -0
- package/{license → license.md} +2 -2
- package/package.json +22 -18
- package/readme.hints.md +60 -19
- package/readme.md +536 -56
- package/dist_ts/typedsocket.classes.typedsocket.js +0 -941
- package/dist_ts/typedsocket.plugins.d.ts +0 -11
- package/dist_ts/typedsocket.plugins.js +0 -13
- package/npmextra.json +0 -35
- package/ts/00_commitinfo_data.ts +0 -8
- package/ts/index.ts +0 -1
- package/ts/typedsocket.classes.typedsocket.ts +0 -1156
package/readme.md
CHANGED
|
@@ -11,18 +11,12 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
11
11
|
- 🔒 **Full Type Safety** - Leverages TypeScript for compile-time checking of all request/response payloads
|
|
12
12
|
- 🔄 **Bi-directional Communication** - Both server and client can initiate requests
|
|
13
13
|
- 🔌 **Auto-reconnect** - Client automatically reconnects on connection loss
|
|
14
|
-
- 🏷️ **Connection Tagging** -
|
|
14
|
+
- 🏷️ **Policy-Gated Connection Tagging** - Default-deny client tags and protected server metadata
|
|
15
15
|
- 🌐 **Browser Compatible** - Works in both Node.js and browser environments
|
|
16
16
|
- 🚀 **SmartServe Integration** - Native support for SmartServe's WebSocket handling
|
|
17
17
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
20
|
-
```bash
|
|
21
|
-
npm install @api.global/typedsocket
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Or with pnpm:
|
|
25
|
-
|
|
26
20
|
```bash
|
|
27
21
|
pnpm add @api.global/typedsocket
|
|
28
22
|
```
|
|
@@ -34,6 +28,11 @@ pnpm add @api.global/typedsocket
|
|
|
34
28
|
- TypeScript project setup
|
|
35
29
|
- Basic understanding of async/await patterns
|
|
36
30
|
- Familiarity with `@api.global/typedrequest` concepts
|
|
31
|
+
- `@api.global/typedrequest` 5 and `@api.global/typedrequest-interfaces` 5
|
|
32
|
+
- `@push.rocks/smartserve` 4.2.1 or newer within major 4 for server integrations
|
|
33
|
+
|
|
34
|
+
TypedSocket 6, SmartServe 4.2.1, and TypedRequest 5.2.1 share one `TypedRouter` contract. Do not
|
|
35
|
+
mix older router contracts or bridge the mismatch with casts.
|
|
37
36
|
|
|
38
37
|
### Define Your Request Interface
|
|
39
38
|
|
|
@@ -82,6 +81,7 @@ const smartServe = new SmartServe({
|
|
|
82
81
|
port: 3000,
|
|
83
82
|
websocket: {
|
|
84
83
|
typedRouter,
|
|
84
|
+
transportOwner: server.webSocketTransportOwner,
|
|
85
85
|
},
|
|
86
86
|
});
|
|
87
87
|
server.attachSmartServe(smartServe);
|
|
@@ -114,9 +114,10 @@ const smartServe = new SmartServe({
|
|
|
114
114
|
port: 3000,
|
|
115
115
|
websocket: {
|
|
116
116
|
typedRouter,
|
|
117
|
+
transportOwner: typedSocket.webSocketTransportOwner,
|
|
117
118
|
onConnectionOpen: (peer) => {
|
|
118
|
-
//
|
|
119
|
-
|
|
119
|
+
// Server metadata is protected from client overwrite/removal.
|
|
120
|
+
typedSocket.setServerTag(peer, 'client');
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
});
|
|
@@ -148,6 +149,7 @@ const smartServe = new SmartServe({
|
|
|
148
149
|
if (context.url.hostname === 'admin.example.com') return adminRouter;
|
|
149
150
|
return undefined;
|
|
150
151
|
},
|
|
152
|
+
transportOwner: typedSocket.webSocketTransportOwner,
|
|
151
153
|
},
|
|
152
154
|
});
|
|
153
155
|
typedSocket.attachSmartServe(smartServe);
|
|
@@ -157,14 +159,290 @@ await smartServe.start();
|
|
|
157
159
|
TypedSocket adds a private, one-way fallback router containing its protocol
|
|
158
160
|
handlers to every application router. Duplicate protocol method names are
|
|
159
161
|
rejected during composition and on later router mutations; `stop()` releases
|
|
160
|
-
the owned fallback edges. `fromSmartServe()` remains available as a
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
the owned fallback edges. `fromSmartServe()` remains available as a JSON-only
|
|
163
|
+
attachment helper that delegates to `createServer()` and `attachSmartServe()`.
|
|
164
|
+
|
|
165
|
+
Connection lookup via `findTargetConnection()`/`findAllTargetConnections()`
|
|
166
|
+
spans every peer attached to this server's routing surfaces. Deprecated
|
|
167
|
+
implicit single-peer targeting is narrower: multi-router servers throw
|
|
168
|
+
`'TypedSocket multi-router servers require an explicit targetConnection'`
|
|
169
|
+
before any lookup, and single-router servers only consider peers bound to that
|
|
170
|
+
router's surface. Multi-surface servers should tag or filter peers by surface
|
|
171
|
+
and pass an explicit target to `createTypedRequest()`.
|
|
172
|
+
|
|
173
|
+
### Native Byte Streams
|
|
174
|
+
|
|
175
|
+
TypedSocket 6 negotiates `native-byte-v1` on each physical WebSocket before
|
|
176
|
+
restoring tags or publishing the client as connected. Native bytes require the
|
|
177
|
+
SmartServe 4.2.1 raw-frame owner to be selected when SmartServe is constructed. The
|
|
178
|
+
server construction order is strict:
|
|
179
|
+
|
|
180
|
+
1. Create the application `TypedRouter` instances.
|
|
181
|
+
2. Call `TypedSocket.createServer(routerOrRouters, options?)`.
|
|
182
|
+
3. Construct `SmartServe` with the composed router and
|
|
183
|
+
`transportOwner: typedSocket.webSocketTransportOwner`.
|
|
184
|
+
4. Call `typedSocket.attachSmartServe(smartServe)`.
|
|
185
|
+
5. Start SmartServe.
|
|
186
|
+
|
|
187
|
+
`fromSmartServe()` supports JSON-only attachment to an existing server. It cannot add a
|
|
188
|
+
raw-frame owner to a SmartServe instance that has already selected transports,
|
|
189
|
+
so native negotiation succeeds only for peers already bound to the exact
|
|
190
|
+
`webSocketTransportOwner` object.
|
|
191
|
+
|
|
192
|
+
Client and server options accept `nativeByteCapabilityMode`:
|
|
193
|
+
|
|
194
|
+
- `optional` is the default. JSON RPC remains available if native negotiation is unavailable.
|
|
195
|
+
- `required` fails client startup when negotiation does not return `native-byte-v1`. On servers it closes peers that do not negotiate the capability.
|
|
196
|
+
- `disabled` advertises no native capability and keeps JSON RPC available.
|
|
197
|
+
|
|
198
|
+
#### Preferred VirtualStream Facade
|
|
199
|
+
|
|
200
|
+
For bounded byte payloads, application code uses
|
|
201
|
+
`TypedSocket.createVirtualStream()` and passes the returned
|
|
202
|
+
`VirtualStream<Uint8Array>` facade in its typed DTO. TypedRequest transfers the
|
|
203
|
+
opaque transport descriptor automatically. The receiving application drains
|
|
204
|
+
and durably commits the stream before confirmation; the sender sees only the
|
|
205
|
+
reversed `send` facade and its durable completion receipt.
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import * as typedrequest from '@api.global/typedrequest';
|
|
209
|
+
import * as typedrequestInterfaces from '@api.global/typedrequest-interfaces';
|
|
210
|
+
import { SmartServe } from '@push.rocks/smartserve';
|
|
211
|
+
import { createSha256Hasher } from '@push.rocks/smarthash/web';
|
|
212
|
+
import {
|
|
213
|
+
TypedSocket,
|
|
214
|
+
type TNativeByteAuthorityOperation,
|
|
215
|
+
} from '@api.global/typedsocket';
|
|
216
|
+
|
|
217
|
+
interface IUploadRequest extends typedrequestInterfaces.implementsTR<
|
|
218
|
+
typedrequestInterfaces.ITypedRequest,
|
|
219
|
+
IUploadRequest
|
|
220
|
+
> {
|
|
221
|
+
method: 'uploadBytes';
|
|
222
|
+
request: {
|
|
223
|
+
uploadId: string;
|
|
224
|
+
byteLength: number;
|
|
225
|
+
sha256: string;
|
|
226
|
+
contentType: string;
|
|
227
|
+
};
|
|
228
|
+
response: {
|
|
229
|
+
stream: typedrequestInterfaces.INativeByteVirtualStream<Uint8Array>;
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface IUploadAuthorization {
|
|
234
|
+
uploadId: string;
|
|
235
|
+
principalId: string;
|
|
236
|
+
credentialRevision: string;
|
|
237
|
+
configRevision: string;
|
|
238
|
+
bindingRevision: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
interface IDurableUpload {
|
|
242
|
+
writable: WritableStream<Uint8Array>;
|
|
243
|
+
commit(): Promise<void>;
|
|
244
|
+
abort(reasonArg: unknown): Promise<void>;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// These hooks belong to the application and its durable storage layer.
|
|
248
|
+
declare function bindUploadAuthorization(uploadIdArg: string): IUploadAuthorization;
|
|
249
|
+
declare function revalidateUpload(
|
|
250
|
+
authorizationArg: IUploadAuthorization,
|
|
251
|
+
manifestArg: typedrequestInterfaces.INativeByteStreamManifest,
|
|
252
|
+
operationArg: TNativeByteAuthorityOperation,
|
|
253
|
+
): Promise<boolean>;
|
|
254
|
+
declare function openDurableUpload(uploadIdArg: string): Promise<IDurableUpload>;
|
|
255
|
+
|
|
256
|
+
function parseUploadAuthorization(valueArg: unknown): IUploadAuthorization {
|
|
257
|
+
if (typeof valueArg !== 'object' || valueArg === null) {
|
|
258
|
+
throw new Error('Upload authorization is invalid');
|
|
259
|
+
}
|
|
260
|
+
const uploadId = Reflect.get(valueArg, 'uploadId');
|
|
261
|
+
const principalId = Reflect.get(valueArg, 'principalId');
|
|
262
|
+
const credentialRevision = Reflect.get(valueArg, 'credentialRevision');
|
|
263
|
+
const configRevision = Reflect.get(valueArg, 'configRevision');
|
|
264
|
+
const bindingRevision = Reflect.get(valueArg, 'bindingRevision');
|
|
265
|
+
if (
|
|
266
|
+
typeof uploadId !== 'string'
|
|
267
|
+
|| typeof principalId !== 'string'
|
|
268
|
+
|| typeof credentialRevision !== 'string'
|
|
269
|
+
|| typeof configRevision !== 'string'
|
|
270
|
+
|| typeof bindingRevision !== 'string'
|
|
271
|
+
) {
|
|
272
|
+
throw new Error('Upload authorization is invalid');
|
|
273
|
+
}
|
|
274
|
+
return { uploadId, principalId, credentialRevision, configRevision, bindingRevision };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const serverRouter = new typedrequest.TypedRouter();
|
|
278
|
+
let typedSocketServer!: TypedSocket;
|
|
279
|
+
|
|
280
|
+
serverRouter.addTypedHandler(new typedrequest.TypedHandler<IUploadRequest>(
|
|
281
|
+
'uploadBytes',
|
|
282
|
+
async (requestArg, metaArg) => {
|
|
283
|
+
const authorization = bindUploadAuthorization(requestArg.uploadId);
|
|
284
|
+
const connection = typedSocketServer.getServerConnectionForRequest(metaArg);
|
|
285
|
+
const stream = typedSocketServer.createVirtualStream({
|
|
286
|
+
protocol: 'native-byte-v1',
|
|
287
|
+
direction: 'receive',
|
|
288
|
+
target: connection,
|
|
289
|
+
byteLength: requestArg.byteLength,
|
|
290
|
+
sha256: requestArg.sha256,
|
|
291
|
+
contentType: requestArg.contentType,
|
|
292
|
+
authorization,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
void (async () => {
|
|
296
|
+
let durableUpload: IDurableUpload | undefined;
|
|
297
|
+
try {
|
|
298
|
+
durableUpload = await openDurableUpload(authorization.uploadId);
|
|
299
|
+
await stream.writeToWebstream(durableUpload.writable);
|
|
300
|
+
await durableUpload.commit();
|
|
301
|
+
await stream.confirmDurable();
|
|
302
|
+
} catch (errorArg) {
|
|
303
|
+
await durableUpload?.abort(errorArg).catch(() => {});
|
|
304
|
+
await stream.reject(errorArg).catch(() => {});
|
|
305
|
+
}
|
|
306
|
+
})();
|
|
307
|
+
|
|
308
|
+
return { stream };
|
|
309
|
+
},
|
|
310
|
+
));
|
|
311
|
+
|
|
312
|
+
typedSocketServer = TypedSocket.createServer(serverRouter, {
|
|
313
|
+
nativeByteCapabilityMode: 'required',
|
|
314
|
+
nativeByteAuthorizationAdapter: {
|
|
315
|
+
bind: (authorizationArg, contextArg) => {
|
|
316
|
+
const authorization = parseUploadAuthorization(authorizationArg);
|
|
317
|
+
return {
|
|
318
|
+
principalId: authorization.principalId,
|
|
319
|
+
credentialRevision: authorization.credentialRevision,
|
|
320
|
+
configRevision: authorization.configRevision,
|
|
321
|
+
bindingRevision: authorization.bindingRevision,
|
|
322
|
+
revalidate: async (revalidationArg) =>
|
|
323
|
+
revalidationArg.connection.side === 'server'
|
|
324
|
+
&& revalidationArg.connection.peer === contextArg.target
|
|
325
|
+
&& await revalidateUpload(
|
|
326
|
+
authorization,
|
|
327
|
+
contextArg.manifest,
|
|
328
|
+
revalidationArg.operation,
|
|
329
|
+
),
|
|
330
|
+
};
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const smartServe = new SmartServe({
|
|
336
|
+
port: 3000,
|
|
337
|
+
websocket: {
|
|
338
|
+
typedRouter: serverRouter,
|
|
339
|
+
transportOwner: typedSocketServer.webSocketTransportOwner,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
typedSocketServer.attachSmartServe(smartServe);
|
|
343
|
+
await smartServe.start();
|
|
344
|
+
|
|
345
|
+
const client = await TypedSocket.createClient(
|
|
346
|
+
new typedrequest.TypedRouter(),
|
|
347
|
+
'http://127.0.0.1:3000',
|
|
348
|
+
{ nativeByteCapabilityMode: 'required', autoReconnect: false },
|
|
349
|
+
);
|
|
350
|
+
try {
|
|
351
|
+
const payload = new TextEncoder().encode('authorized payload');
|
|
352
|
+
const sha256 = `sha256:${createSha256Hasher().update(payload).digest()}`;
|
|
353
|
+
const response = await client.createTypedRequest<IUploadRequest>('uploadBytes').fire({
|
|
354
|
+
uploadId: 'upload-1',
|
|
355
|
+
byteLength: payload.byteLength,
|
|
356
|
+
sha256,
|
|
357
|
+
contentType: 'text/plain',
|
|
358
|
+
});
|
|
359
|
+
await response.stream.readFromWebstream(new ReadableStream<Uint8Array>({
|
|
360
|
+
start: (controllerArg) => {
|
|
361
|
+
controllerArg.enqueue(payload);
|
|
362
|
+
controllerArg.close();
|
|
363
|
+
},
|
|
364
|
+
}));
|
|
365
|
+
const receipt = await response.stream.completion;
|
|
366
|
+
if (receipt.sha256 !== sha256 || !receipt.durable) {
|
|
367
|
+
throw new Error('Upload receipt did not match the authorized manifest');
|
|
368
|
+
}
|
|
369
|
+
} finally {
|
|
370
|
+
await client.stop();
|
|
371
|
+
await typedSocketServer.stop();
|
|
372
|
+
await smartServe.stop();
|
|
373
|
+
}
|
|
374
|
+
```
|
|
163
375
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
376
|
+
`nativeByteAuthorizationAdapter.bind()` is synchronous so descriptor publication
|
|
377
|
+
cannot race authority capture. Its `revalidate()` callback may be asynchronous;
|
|
378
|
+
OPEN, DATA, FIN, and durable confirmation are followed by an exact
|
|
379
|
+
peer/generation ownership check. Rejection attempts one final best-effort
|
|
380
|
+
revalidation before local cleanup and RESET.
|
|
381
|
+
|
|
382
|
+
#### Advanced Transport API
|
|
383
|
+
|
|
384
|
+
`nativeBytes.createReceiveGrant()`, `nativeBytes.openSender()`, and opaque native
|
|
385
|
+
descriptors are protocol-integration APIs. Normal application DTOs should carry
|
|
386
|
+
the `VirtualStream` facade shown above, not a descriptor. Transport integrations
|
|
387
|
+
that cannot use the facade must still provide a non-negative safe-integer
|
|
388
|
+
`byteLength`, exact `sha256:<64 lowercase hex>`, and a normalized media-type
|
|
389
|
+
`contentType` of at most 255 UTF-8 bytes (`NATIVE_BYTE_MAX_CONTENT_TYPE_BYTES`).
|
|
390
|
+
They must also provide all three
|
|
391
|
+
nonempty authority revisions and mandatory `revalidate(context)`.
|
|
392
|
+
|
|
393
|
+
The application must drain the stream, commit it durably, and only then call
|
|
394
|
+
`confirmDurable()`. The transport can enforce validated FIN, complete drain, and
|
|
395
|
+
explicit confirmation, but the application owns the storage durability claim.
|
|
396
|
+
`FIN_ACK` is not emitted before confirmation. The sender's `close()` and
|
|
397
|
+
`completion` remain pending until then.
|
|
398
|
+
Accepted nonempty DATA calls are copied and admitted against the authorized
|
|
399
|
+
length and queue limits before asynchronous execution, so caller mutation,
|
|
400
|
+
large backing buffers, and non-awaited calls cannot escape transport accounting.
|
|
401
|
+
|
|
402
|
+
Native byte transport deliberately provides no business idempotency, reconnect
|
|
403
|
+
resume, HTTP fallback, or legacy-byte fallback. A disconnect or generation
|
|
404
|
+
change fails all affected grants and streams. Capabilities and stream IDs are
|
|
405
|
+
one-use on one exact physical peer.
|
|
406
|
+
|
|
407
|
+
#### Legacy VirtualStream Removal
|
|
408
|
+
|
|
409
|
+
TypedSocket 6 unconditionally rejects generic `##VirtualStream##` descriptors and
|
|
410
|
+
control requests. There is no client option, server option, fallback, or migration
|
|
411
|
+
opt-in. Byte DTOs must use the exact native facade returned by
|
|
412
|
+
`TypedSocket.createVirtualStream()`.
|
|
413
|
+
|
|
414
|
+
#### Native Limits
|
|
415
|
+
|
|
416
|
+
| Limit | Exported constant | Value |
|
|
417
|
+
|---|---|---:|
|
|
418
|
+
| Complete binary message | `NATIVE_BYTE_MAX_FRAME_BYTES` | 32 KiB |
|
|
419
|
+
| DATA payload per message | `NATIVE_BYTE_MAX_DATA_PAYLOAD_BYTES` | 32,720 bytes |
|
|
420
|
+
| Maximum stream length | `NATIVE_BYTE_MAX_STREAM_BYTES` | 140,531,329,925,120 bytes (127.8125 TiB) |
|
|
421
|
+
| Default receive window | `NATIVE_BYTE_DEFAULT_INITIAL_WINDOW_BYTES` | 256 KiB |
|
|
422
|
+
| Maximum receive window / queued payload per stream | `NATIVE_BYTE_MAX_WINDOW_BYTES` / `NATIVE_BYTE_MAX_QUEUED_PAYLOAD_BYTES_PER_STREAM` | 1 MiB |
|
|
423
|
+
| Queued payload per connection | `NATIVE_BYTE_MAX_QUEUED_PAYLOAD_BYTES_PER_CONNECTION` | 8 MiB |
|
|
424
|
+
| Queued receive chunks per stream | `NATIVE_BYTE_MAX_RECEIVE_QUEUE_CHUNKS` | 4,096 |
|
|
425
|
+
| Grants plus active streams per connection | `NATIVE_BYTE_MAX_STREAMS_PER_CONNECTION` | 32 |
|
|
426
|
+
| Pending admitted DATA operations per connection | `NATIVE_BYTE_MAX_PENDING_DATA_OPERATIONS_PER_CONNECTION` | 64 |
|
|
427
|
+
| Raw inbound queue | `NATIVE_BYTE_MAX_RAW_QUEUE_FRAMES` / `NATIVE_BYTE_MAX_RAW_QUEUE_BYTES` | 64 frames / 2 MiB |
|
|
428
|
+
| Grant and OPEN timeout | `NATIVE_BYTE_GRANT_OPEN_TIMEOUT_MS` | 10 seconds |
|
|
429
|
+
| Progress, ACK, frame settlement, FIN_ACK, durable confirmation | `NATIVE_BYTE_PROGRESS_TIMEOUT_MS` | 30 seconds |
|
|
430
|
+
| Closed-stream tombstones | `NATIVE_BYTE_MAX_TOMBSTONES` / `NATIVE_BYTE_TOMBSTONE_RETENTION_MS` | 64, oldest-first, 60 seconds |
|
|
431
|
+
| Principal and each authority revision | `NATIVE_BYTE_MAX_PRINCIPAL_ID_BYTES` / `NATIVE_BYTE_MAX_AUTHORITY_REVISION_BYTES` | 256 UTF-8 bytes |
|
|
432
|
+
| Retained revalidation callbacks | `NATIVE_BYTE_MAX_REVALIDATIONS_PER_PEER` / `_PER_PRINCIPAL` / `_PER_SERVER` | 4 per peer / 16 per principal / 128 per server |
|
|
433
|
+
| Server connections / streams | `NATIVE_BYTE_MAX_CONNECTIONS_PER_SERVER` / `NATIVE_BYTE_MAX_STREAMS_PER_SERVER` | 1,024 / 1,024 |
|
|
434
|
+
| Server retained bytes / receive reservations | `NATIVE_BYTE_MAX_RETAINED_BYTES_PER_SERVER` | 64 MiB / 64 MiB |
|
|
435
|
+
|
|
436
|
+
SmartServe owner sends prioritize control frames over DATA and pull one binary
|
|
437
|
+
frame per requested turn. Browser clients likewise send one binary frame per
|
|
438
|
+
macrotask, allowing direct JSON text traffic to run before the next binary turn.
|
|
439
|
+
Server frame accounting remains retained after stream or connection cleanup
|
|
440
|
+
until SmartServe settles the exact frame object returned by `pullBinaryFrame()`;
|
|
441
|
+
late, cloned, or duplicate callbacks cannot settle newer work. Tombstone
|
|
442
|
+
admission evicts oldest entries first and closes the connection if the fixed
|
|
443
|
+
retained-byte budget still cannot hold the required replay fence.
|
|
444
|
+
`getStats()` exposes connection/negotiation status, counts, and byte totals;
|
|
445
|
+
descriptors, capability tokens, digests, and payloads are not included.
|
|
168
446
|
|
|
169
447
|
### Client Setup
|
|
170
448
|
|
|
@@ -184,6 +462,76 @@ const client = await TypedSocket.createClient(
|
|
|
184
462
|
);
|
|
185
463
|
```
|
|
186
464
|
|
|
465
|
+
Remote connections must use `https:` or `wss:`. Plain `http:` and `ws:` are
|
|
466
|
+
accepted only for loopback hosts. Credentials and URL fragments are rejected;
|
|
467
|
+
connection logs include only the protocol and authority, never paths or queries.
|
|
468
|
+
|
|
469
|
+
Client options can lower, but never raise, package ceilings for text-frame bytes,
|
|
470
|
+
queued text frames/bytes, concurrent handlers, retained callbacks, pending
|
|
471
|
+
requests, outbound WebSocket buffering, request timeouts, and connection
|
|
472
|
+
restoration timeouts. `restoreConnection(context)` runs after capability
|
|
473
|
+
negotiation and before desired tags are reconciled or `connected` is published.
|
|
474
|
+
Its `context.createTypedRequest<T>(method)` uses the new physical connection and
|
|
475
|
+
inherits the restoration abort signal and remaining deadline. The factory is
|
|
476
|
+
invalidated when restoration succeeds, fails, times out, or is aborted, so it
|
|
477
|
+
must not be retained for later application traffic.
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import * as typedrequestInterfaces from '@api.global/typedrequest-interfaces';
|
|
481
|
+
|
|
482
|
+
interface IRestoreSessionRequest extends typedrequestInterfaces.implementsTR<
|
|
483
|
+
typedrequestInterfaces.ITypedRequest,
|
|
484
|
+
IRestoreSessionRequest
|
|
485
|
+
> {
|
|
486
|
+
method: 'restoreSession';
|
|
487
|
+
request: { token: string; connectionId: string };
|
|
488
|
+
response: { restored: true };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const client = await TypedSocket.createClient(clientRouter, serverUrl, {
|
|
492
|
+
autoReconnect: true,
|
|
493
|
+
restoreConnection: async ({
|
|
494
|
+
connectionId,
|
|
495
|
+
abortSignal,
|
|
496
|
+
deadline,
|
|
497
|
+
createTypedRequest,
|
|
498
|
+
}) => {
|
|
499
|
+
if (abortSignal.aborted || Date.now() >= deadline) {
|
|
500
|
+
throw new Error('Connection restoration expired');
|
|
501
|
+
}
|
|
502
|
+
await createTypedRequest<IRestoreSessionRequest>('restoreSession').fire({
|
|
503
|
+
token: sessionToken,
|
|
504
|
+
connectionId,
|
|
505
|
+
});
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
| Client limit | Exported constant | Package ceiling |
|
|
511
|
+
|---|---|---:|
|
|
512
|
+
| Complete text frame | `TYPEDSOCKET_MAX_TEXT_FRAME_BYTES` | 1 MiB |
|
|
513
|
+
| Queued text frames / bytes | `TYPEDSOCKET_MAX_QUEUED_TEXT_FRAMES` / `TYPEDSOCKET_MAX_QUEUED_TEXT_BYTES` | 64 / 4 MiB |
|
|
514
|
+
| Concurrent handlers / retained callbacks | `TYPEDSOCKET_MAX_CONCURRENT_CLIENT_HANDLERS` / `TYPEDSOCKET_MAX_RETAINED_CLIENT_CALLBACKS` | 16 / 64 |
|
|
515
|
+
| Pending client requests | `TYPEDSOCKET_MAX_PENDING_CLIENT_REQUESTS` | 1,024 |
|
|
516
|
+
| Outbound WebSocket buffered bytes | `TYPEDSOCKET_MAX_OUTBOUND_BUFFERED_BYTES` | 4 MiB |
|
|
517
|
+
| Method name / correlation ID | `TYPEDSOCKET_MAX_METHOD_NAME_BYTES` / `TYPEDSOCKET_MAX_CORRELATION_ID_BYTES` | 256 UTF-8 bytes each |
|
|
518
|
+
| Request timeout | `TYPEDSOCKET_MAX_REQUEST_TIMEOUT_MS` | 5 minutes |
|
|
519
|
+
| Connection restoration timeout | `TYPEDSOCKET_MAX_CONNECTION_RESTORE_TIMEOUT_MS` | 10 seconds |
|
|
520
|
+
| Reconnect attempts | `TYPEDSOCKET_MAX_RECONNECT_RETRIES` | 100 |
|
|
521
|
+
| Initial / maximum reconnect backoff | `TYPEDSOCKET_MAX_RECONNECT_BACKOFF_MS` | 60 seconds |
|
|
522
|
+
|
|
523
|
+
`maxRetries` is a non-negative safe integer no greater than 100.
|
|
524
|
+
`initialBackoffMs` and `maxBackoffMs` are positive safe integers no greater
|
|
525
|
+
than 60,000, and the initial value cannot exceed the maximum.
|
|
526
|
+
|
|
527
|
+
Every ceiling in this document is exported as a named constant from the package
|
|
528
|
+
root, so consumers can compare against the canonical value instead of
|
|
529
|
+
hardcoding numbers. The limits philosophy is uniform and deliberate:
|
|
530
|
+
configuration may lower a ceiling, but nothing may raise one — there is no
|
|
531
|
+
unsafe override option. When a legitimate use case outgrows a ceiling, the
|
|
532
|
+
constant is raised in a reviewed package release, not by per-deployment
|
|
533
|
+
configuration.
|
|
534
|
+
|
|
187
535
|
#### Abortable Startup
|
|
188
536
|
|
|
189
537
|
Pass an `AbortSignal` when startup or reconnect attempts must be cancellable. Aborting stops the in-flight WebSocket and prevents queued reconnect attempts from continuing.
|
|
@@ -237,23 +585,26 @@ console.log(response.message); // "Hello, World! 👋"
|
|
|
237
585
|
|
|
238
586
|
#### Server to Client
|
|
239
587
|
|
|
240
|
-
The server can also initiate requests to connected clients
|
|
588
|
+
The server can also initiate requests to connected clients. Always pass the
|
|
589
|
+
target connection explicitly:
|
|
241
590
|
|
|
242
591
|
```typescript
|
|
243
|
-
// When only one client is connected, it's automatically selected
|
|
244
|
-
const request = server.createTypedRequest<IGreetingRequest>('greet');
|
|
245
|
-
const response = await request.fire({
|
|
246
|
-
name: 'Client',
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// For multiple clients, specify the target connection
|
|
250
592
|
const connection = await server.findTargetConnection(async (conn) => {
|
|
251
593
|
// Your filter logic here
|
|
252
594
|
return true;
|
|
253
595
|
});
|
|
254
596
|
const targetedRequest = server.createTypedRequest<IGreetingRequest>('greet', connection);
|
|
597
|
+
const response = await targetedRequest.fire({ name: 'Client' });
|
|
255
598
|
```
|
|
256
599
|
|
|
600
|
+
> **Deprecated:** omitting the target on a server-side `createTypedRequest()`
|
|
601
|
+
> auto-selects the connection only while exactly one client is attached, and
|
|
602
|
+
> throws as soon as a second client connects. It also silently disables
|
|
603
|
+
> native-byte transport for that request, because no peer was known when the
|
|
604
|
+
> request object was created. Each implicit resolution emits an
|
|
605
|
+
> `implicitTargetingUsed` diagnostic (once per request object); the implicit
|
|
606
|
+
> path will be removed in the next major version.
|
|
607
|
+
|
|
257
608
|
#### Request Deadlines and Cancellation
|
|
258
609
|
|
|
259
610
|
TypedSocket forwards both configured request cancellation and per-`fire()` deadlines to its client
|
|
@@ -281,59 +632,171 @@ const responsePromise = request.fire(
|
|
|
281
632
|
const response = await responsePromise;
|
|
282
633
|
```
|
|
283
634
|
|
|
635
|
+
Server-initiated requests retain at most 64 pending requests per peer and 1,024
|
|
636
|
+
per TypedSocket server (`TYPEDSOCKET_MAX_PENDING_SERVER_REQUESTS_PER_PEER`,
|
|
637
|
+
`TYPEDSOCKET_MAX_PENDING_SERVER_REQUESTS`). Asynchronous `addInterest()`
|
|
638
|
+
registration retains at most 8 operations per peer and 64 per server
|
|
639
|
+
(`TYPEDSOCKET_MAX_RETAINED_SERVER_INTERESTS_PER_PEER`,
|
|
640
|
+
`TYPEDSOCKET_MAX_RETAINED_SERVER_INTERESTS`). Cancellation removes the pending
|
|
641
|
+
request immediately, while a non-settling registration remains charged until
|
|
642
|
+
its underlying promise actually settles.
|
|
643
|
+
|
|
284
644
|
### Connection Tagging
|
|
285
645
|
|
|
286
|
-
|
|
646
|
+
Client tag mutation is disabled by default. A server must opt in each exact name, choose whether
|
|
647
|
+
an accepted proposal remains client-owned or becomes server-owned, and validate both payload shape
|
|
648
|
+
and connection authorization inside the private protocol boundary.
|
|
287
649
|
|
|
288
650
|
```typescript
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
payload: 'admin' | 'user' | 'guest';
|
|
651
|
+
interface IProgressTag extends typedrequestInterfaces.ITag {
|
|
652
|
+
name: 'progressSubscription';
|
|
653
|
+
payload: { channel: 'scan-progress' };
|
|
293
654
|
}
|
|
294
655
|
|
|
295
|
-
|
|
656
|
+
declare const authenticatedAuthorities: ReadonlySet<string>;
|
|
657
|
+
|
|
658
|
+
const server = TypedSocket.createServer(typedRouter, {
|
|
659
|
+
clientTagPolicy: {
|
|
660
|
+
rules: [{
|
|
661
|
+
name: 'progressSubscription',
|
|
662
|
+
owner: 'client',
|
|
663
|
+
validateAndAuthorize: ({ operation, payload, authority, abortSignal }) => {
|
|
664
|
+
// Both IDs are opaque exact-object identities. The callback receives no
|
|
665
|
+
// mutable peer.tags, peer.data, peer, or router object access.
|
|
666
|
+
const authorityKey = `${authority.connectionId}:${authority.routingSurfaceId}`;
|
|
667
|
+
if (abortSignal.aborted || !authenticatedAuthorities.has(authorityKey)) return false;
|
|
668
|
+
return operation === 'remove'
|
|
669
|
+
|| (
|
|
670
|
+
typeof payload === 'object'
|
|
671
|
+
&& payload !== null
|
|
672
|
+
&& Reflect.get(payload, 'channel') === 'scan-progress'
|
|
673
|
+
);
|
|
674
|
+
},
|
|
675
|
+
}],
|
|
676
|
+
},
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
await client.setTag<IProgressTag>(
|
|
680
|
+
'progressSubscription',
|
|
681
|
+
{ channel: 'scan-progress' },
|
|
682
|
+
);
|
|
296
683
|
|
|
297
|
-
// On reconnect,
|
|
298
|
-
// "connected".
|
|
299
|
-
// through the normal reconnect policy.
|
|
684
|
+
// On reconnect, desired tags are reconciled before statusSubject emits
|
|
685
|
+
// "connected". Acknowledgements are scoped to one physical generation.
|
|
300
686
|
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
|
|
687
|
+
// A policy denial discards only that FIFO intent and recomputes desired state
|
|
688
|
+
// from any later pending intent or the stable baseline established by earlier
|
|
689
|
+
// settlements. Transport failure retains the canonical desired removal tombstone;
|
|
690
|
+
// accepted removal, including successful reconnect replay, clears it.
|
|
691
|
+
await client.removeTag('progressSubscription');
|
|
304
692
|
```
|
|
305
693
|
|
|
306
694
|
```typescript
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
'
|
|
310
|
-
'admin'
|
|
695
|
+
const progressConnections = await server.findAllTargetConnectionsByTag<IProgressTag>(
|
|
696
|
+
'progressSubscription',
|
|
697
|
+
{ channel: 'scan-progress' }
|
|
311
698
|
);
|
|
312
699
|
|
|
313
|
-
|
|
314
|
-
for (const conn of adminConnections) {
|
|
700
|
+
for (const conn of progressConnections) {
|
|
315
701
|
const request = server.createTypedRequest<IGreetingRequest>('greet', conn);
|
|
316
|
-
await request.fire({ name: '
|
|
702
|
+
await request.fire({ name: 'subscriber' });
|
|
317
703
|
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Connection wrappers deliberately expose the underlying transport peer as
|
|
707
|
+
`connection.peer` — this is the supported escape hatch for admission-time
|
|
708
|
+
metadata in selection predicates, for example reading
|
|
709
|
+
`connection.peer.context.headers` or server-owned `connection.peer.context.state`
|
|
710
|
+
inside `findAllTargetConnections()`. Reading through `peer` is fine; mutating
|
|
711
|
+
`peer.tags` or TypedSocket's prefixed `peer.data` entries is not — ownership of
|
|
712
|
+
those is reconciled by the protected server tag methods.
|
|
713
|
+
|
|
714
|
+
Authentication, roles, service registration, and other authoritative metadata must be assigned by
|
|
715
|
+
the server after application-level verification:
|
|
716
|
+
|
|
717
|
+
```typescript
|
|
718
|
+
const connection = server.getServerConnectionForRequest(typedToolsArg);
|
|
719
|
+
server.setServerTag(connection, 'authenticatedUser', { userId });
|
|
318
720
|
|
|
319
|
-
//
|
|
320
|
-
|
|
721
|
+
// Clients cannot set, overwrite, or remove this name. Removal remains server-owned.
|
|
722
|
+
server.removeServerTag(connection, 'authenticatedUser');
|
|
321
723
|
```
|
|
322
724
|
|
|
725
|
+
Do not mutate `peer.tags` or TypedSocket's prefixed `peer.data` entries directly. The protected
|
|
726
|
+
server methods reconcile ownership with in-flight client proposals.
|
|
727
|
+
|
|
728
|
+
Fixed limits cannot be raised by policy (each is exported by name from the
|
|
729
|
+
package root):
|
|
730
|
+
|
|
731
|
+
- Tag name: 128 UTF-8 bytes (`TYPEDSOCKET_MAX_TAG_NAME_BYTES`).
|
|
732
|
+
- SmartJSON payload envelope: 4,096 bytes (`TYPEDSOCKET_MAX_TAG_PAYLOAD_BYTES`).
|
|
733
|
+
- Client-originated retained tags: 16 per peer (`TYPEDSOCKET_MAX_CLIENT_TAGS_PER_PEER`).
|
|
734
|
+
- Client-originated cumulative name and payload storage: 16,384 bytes per peer (`TYPEDSOCKET_MAX_CLIENT_TAG_RETAINED_BYTES_PER_PEER`).
|
|
735
|
+
- Client-side retained mutation work: 8 per exact name and 64 per client (`TYPEDSOCKET_MAX_RETAINED_CLIENT_TAG_MUTATIONS_PER_NAME`, `TYPEDSOCKET_MAX_RETAINED_CLIENT_TAG_MUTATIONS`).
|
|
736
|
+
- Retained mutation/authorization work: 8 per peer, 32 per exact rule, and 256 per TypedSocket server (`TYPEDSOCKET_MAX_RETAINED_TAG_MUTATIONS_PER_PEER`, `_PER_RULE`, `_PER_SERVER`).
|
|
737
|
+
- Protected server tag state: 64 names and 65,536 cumulative name/payload bytes per peer (`TYPEDSOCKET_MAX_SERVER_TAG_NAMES_PER_PEER`, `TYPEDSOCKET_MAX_SERVER_TAG_RETAINED_BYTES_PER_PEER`).
|
|
738
|
+
- Authorization callback deadline: at most 5 seconds; policy may only lower it (`TYPEDSOCKET_MAX_TAG_AUTHORIZATION_TIMEOUT_MS`).
|
|
739
|
+
|
|
740
|
+
Same-name mutations execute FIFO. Timed-out callbacks receive an aborted signal and remain charged
|
|
741
|
+
against authorization budgets until they actually settle. Disconnect and server stop abort active
|
|
742
|
+
callbacks and prevent late commits.
|
|
743
|
+
|
|
744
|
+
#### TypedServer Integration Contract
|
|
745
|
+
|
|
746
|
+
TypedServer 9 passes `clientTagPolicy` through, exposes
|
|
747
|
+
`webSocketTransportOwner`, resolves handler-local peers through
|
|
748
|
+
`getServerConnectionForRequest(typedTools)`, and assigns infrastructure tags
|
|
749
|
+
through `setServerTag()`. Authentication, roles, frontend registration, and
|
|
750
|
+
service-worker registration are server-owned application state; v6 provides no
|
|
751
|
+
legacy tag migration mode.
|
|
752
|
+
|
|
323
753
|
### Event Handling
|
|
324
754
|
|
|
325
|
-
|
|
755
|
+
Client instances publish connection status events:
|
|
326
756
|
|
|
327
757
|
```typescript
|
|
328
758
|
client.statusSubject.subscribe((status) => {
|
|
329
759
|
console.log('Connection status:', status);
|
|
330
760
|
});
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Diagnostics
|
|
764
|
+
|
|
765
|
+
Both sides publish structured diagnostics on `diagnosticsSubject` — the *why*
|
|
766
|
+
channel next to `statusSubject` (state transitions) and `nativeBytes.getStats()`
|
|
767
|
+
(counters). Events carry package-defined static strings, bounded identifiers
|
|
768
|
+
(tag names, method names), and bounded codes only; payloads, URLs beyond
|
|
769
|
+
protocol//host, and free-form error messages never appear. Client-received
|
|
770
|
+
denial codes are validated against `TYPEDSOCKET_TAG_DENIAL_CODES` before they
|
|
771
|
+
reach the channel; anything unknown collapses to `MALFORMED_TAG_RESPONSE`.
|
|
331
772
|
|
|
332
|
-
|
|
333
|
-
|
|
773
|
+
```typescript
|
|
774
|
+
import type { TTypedSocketDiagnosticEvent } from '@api.global/typedsocket';
|
|
775
|
+
|
|
776
|
+
client.diagnosticsSubject.subscribe((event: TTypedSocketDiagnosticEvent) => {
|
|
777
|
+
switch (event.kind) {
|
|
778
|
+
case 'connectionClosed': // an invariant close: scope, closeCode, reason
|
|
779
|
+
case 'peerRejected': // server-only: peer rejected before state existed
|
|
780
|
+
case 'reconnectScheduled': // attempt, maxRetries, delayMs, endpoint
|
|
781
|
+
case 'reconnectExhausted': // at most once per exhausted sequence
|
|
782
|
+
case 'tagMutationDenied': // operation, tag name, denial code, side
|
|
783
|
+
case 'implicitTargetingUsed': // server-only: deprecated implicit targeting fired
|
|
784
|
+
console.log(event);
|
|
785
|
+
}
|
|
334
786
|
});
|
|
335
787
|
```
|
|
336
788
|
|
|
789
|
+
`diagnosticsSubject` never completes, mirroring `statusSubject`; subscribers
|
|
790
|
+
own their unsubscription. A `NativeByteManager` used standalone accepts the
|
|
791
|
+
same sink via the `onDiagnostic` option, typed to the narrower
|
|
792
|
+
`TNativeByteDiagnosticEvent` subset.
|
|
793
|
+
|
|
794
|
+
Close codes in `connectionClosed` events are always the semantic protocol
|
|
795
|
+
codes (1002, 1003, 1008, 1009, 1011, 1013). On the wire, client-initiated invariant
|
|
796
|
+
closes mirror them into the application range the WebSocket `close()` API
|
|
797
|
+
permits — 1009 becomes 4009 (`toClientWebSocketCloseCode()`); server-initiated
|
|
798
|
+
closes keep the protocol codes.
|
|
799
|
+
|
|
337
800
|
### Cleanup
|
|
338
801
|
|
|
339
802
|
Properly close connections when done:
|
|
@@ -355,9 +818,9 @@ await smartServe.stop();
|
|
|
355
818
|
|
|
356
819
|
| Method | Description |
|
|
357
820
|
|--------|-------------|
|
|
358
|
-
| `createClient(router, serverUrl, options?)` | Creates a WebSocket client
|
|
359
|
-
| `createServer(routerOrRouters)` | Synchronously composes
|
|
360
|
-
| `fromSmartServe(smartServe, routerOrRouters)` |
|
|
821
|
+
| `createClient(router, serverUrl, options?)` | Creates a WebSocket client. Options include reconnect controls, `abortSignal`, `nativeByteCapabilityMode`, lowering-only `limits`, and `restoreConnection`. |
|
|
822
|
+
| `createServer(routerOrRouters, options?)` | Synchronously composes protocol handling. Options include `nativeByteCapabilityMode`, `nativeByteAuthorizationAdapter`, and the default-deny `clientTagPolicy`. |
|
|
823
|
+
| `fromSmartServe(smartServe, routerOrRouters, options?)` | Creates and attaches a JSON-only server-side TypedSocket to an existing SmartServe instance. |
|
|
361
824
|
| `useWindowLocationOriginUrl()` | Returns the current window location origin (browser only). |
|
|
362
825
|
|
|
363
826
|
#### Instance Properties
|
|
@@ -366,25 +829,42 @@ await smartServe.stop();
|
|
|
366
829
|
|----------|-------------|
|
|
367
830
|
| `side` | Whether this instance is a `'server'` or `'client'`. |
|
|
368
831
|
| `typedrouter` | The TypedRouter instance handling requests. |
|
|
369
|
-
| `
|
|
832
|
+
| `nativeBytes` | Advanced native-byte grant, sender, capability, and statistics API for transport integrations. |
|
|
833
|
+
| `webSocketTransportOwner` | Stable SmartServe 4 raw-frame owner selected during SmartServe construction. |
|
|
834
|
+
| `statusSubject` | RxJS Subject for client connection status events. Server instances do not publish lifecycle transitions here. |
|
|
835
|
+
| `diagnosticsSubject` | RxJS Subject of structured `TTypedSocketDiagnosticEvent` values: invariant closes, peer rejections, reconnect scheduling/exhaustion, tag denials, and deprecated implicit targeting. Never completes. |
|
|
370
836
|
|
|
371
837
|
#### Instance Methods
|
|
372
838
|
|
|
373
839
|
| Method | Description |
|
|
374
840
|
|--------|-------------|
|
|
375
841
|
| `attachSmartServe(smartServe)` | Attaches one SmartServe transport to a composed server-side TypedSocket before listening. |
|
|
842
|
+
| `createVirtualStream(options)` | Creates the preferred exact, authorized native-byte receive facade for one server peer. TypedRequest transfers its descriptor automatically. |
|
|
376
843
|
| `createTypedRequest(method, targetConnection?, options?)` | Creates a typed request. Options include the transport `timeoutMs` and `abortSignal`; per-call `fire()` deadlines are also forwarded to the transport. |
|
|
377
|
-
| `
|
|
378
|
-
| `
|
|
844
|
+
| `getServerConnectionForRequest(typedTools)` | Resolves the exact transport connection for an incoming server handler without assertions. |
|
|
845
|
+
| `getStatus()` | Returns the client connection lifecycle status. Server instances remain in the initial `new` state. |
|
|
846
|
+
| `setTag(name, payload)` | Requests one exact policy-authorized tag and records reconnect state only after an ordered server acknowledgement. |
|
|
847
|
+
| `removeTag(name)` | Requests removal of a client-owned tag. Policy denial discards that FIFO intent and recomputes from any later pending intent or the stable settled baseline. Transport failure retains the canonical desired removal tombstone; accepted removal, including successful reconnect replay, clears it. |
|
|
848
|
+
| `setServerTag(connection, name, payload?)` | Assigns protected server-owned metadata after application verification. |
|
|
849
|
+
| `removeServerTag(connection, name)` | Removes server-owned metadata while keeping the name protected from client mutation for that peer. |
|
|
379
850
|
| `findAllTargetConnections(filterFn)` | Finds all connections matching the filter (server-side only). |
|
|
380
851
|
| `findTargetConnection(filterFn)` | Finds the first connection matching the filter (server-side only). |
|
|
381
852
|
| `findAllTargetConnectionsByTag(key, payload?)` | Finds all connections with the specified tag. |
|
|
382
853
|
| `findTargetConnectionByTag(key, payload?)` | Finds the first connection with the specified tag. |
|
|
383
854
|
| `stop()` | On clients, closes the WebSocket and rejects pending requests. On servers, cancels pending requests, cleans their interests, unsubscribes from SmartServe, and releases protocol-router composition without stopping SmartServe itself. |
|
|
384
855
|
|
|
856
|
+
#### Advanced Native Methods
|
|
857
|
+
|
|
858
|
+
| Method | Description |
|
|
859
|
+
|--------|-------------|
|
|
860
|
+
| `nativeBytes.createReceiveGrant(options)` | Creates an explicit exact-manifest receive grant for a protocol integration. |
|
|
861
|
+
| `nativeBytes.openSender(descriptor, options?)` | Opens an explicit sender for an opaque descriptor on the exact target connection. |
|
|
862
|
+
| `nativeBytes.getCapability(target?)` | Reports negotiated native-byte capability without exposing tokens or descriptors. |
|
|
863
|
+
| `nativeBytes.getStats(target?)` | Reports bounded connection, stream, queue, and tombstone counts. |
|
|
864
|
+
|
|
385
865
|
## License and Legal Information
|
|
386
866
|
|
|
387
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
|
867
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license.md](./license.md) file.
|
|
388
868
|
|
|
389
869
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
390
870
|
|
|
@@ -396,7 +876,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
|
|
396
876
|
|
|
397
877
|
### Company Information
|
|
398
878
|
|
|
399
|
-
Task Venture Capital GmbH
|
|
879
|
+
Task Venture Capital GmbH<br>
|
|
400
880
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
401
881
|
|
|
402
882
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|