@api.global/typedsocket 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.hints.md CHANGED
@@ -20,13 +20,19 @@ is selected for the physical peer at upgrade time.
20
20
  facades require fixed manifests, principal/revision authority, and durable
21
21
  confirmation.
22
22
 
23
- 4. **Lifecycle**: Client text work, native frames, callbacks, pending requests,
23
+ 4. **Messages**: `native-message-v1` is separately negotiated after the primary
24
+ negotiation. Its ordinary one-use descriptor is explicit application JSON;
25
+ grants expose the descriptor, receiver, open state, revocation, and disposal,
26
+ while receivers expose only `receive()`, `closed`, and `reject()`. ACK means
27
+ application dequeue, not raw-frame receipt.
28
+
29
+ 5. **Lifecycle**: Client text work, native frames, callbacks, pending requests,
24
30
  tag mutations, and server request interests have fixed resource ceilings and
25
31
  generation fencing. SmartServe settlements authorize only the exact frame
26
32
  object returned by `pullBinaryFrame()`.
27
33
 
28
- 5. **Restoration**: `restoreConnection(context)` runs after capability
29
- negotiation and before desired tags or `connected`. Use
34
+ 6. **Restoration**: `restoreConnection(context)` runs after both capability
35
+ negotiations and before desired tags or `connected`. Use
30
36
  `context.createTypedRequest()` for authenticated restoration RPCs; it inherits
31
37
  the restoration deadline and abort signal and becomes invalid afterward.
32
38
 
@@ -35,7 +41,8 @@ is selected for the physical peer at upgrade time.
35
41
  `diagnosticsSubject` (and `onDiagnostic` on a standalone `NativeByteManager`)
36
42
  publishes structured invariant-close, peer-rejection, reconnect, tag-denial,
37
43
  and implicit-targeting events. Emission must never disturb transport paths:
38
- every sink call is try/catch-wrapped.
44
+ every sink call is try/catch-wrapped. Native message invariant closes use
45
+ `scope: 'nativeMessage'`.
39
46
 
40
47
  The WebSocket JS `close()` API only permits codes 1000 and 3000-4999. All
41
48
  client-initiated invariant closes therefore go through
@@ -52,7 +59,9 @@ Files are named after their main class: `classes.typedsocket.ts`,
52
59
  `classes.typedsockettagpolicymanager.ts`, `classes.clienttagreconciler.ts`
53
60
  (client desired/acknowledged tag state machine behind a transport adapter),
54
61
  `classes.nativebytemanager.ts`, `classes.nativebyteerror.ts`, plus
55
- `helpers.*`, `interfaces.*`, `constants.*`, and `plugins.ts`.
62
+ `helpers.*`, `interfaces.*`, `constants.*`, and `plugins.ts`. The native message
63
+ codec/types/constants have `nativemessage` module names but intentionally share
64
+ the one manager and transport owner.
56
65
 
57
66
  ### Release Artifact
58
67
 
package/readme.md CHANGED
@@ -14,6 +14,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
14
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
+ - **Ephemeral Binary Messages** - Bounded, acknowledged binary messages up to 4 MiB without JSON/base64 expansion
17
18
 
18
19
  ## Install
19
20
 
@@ -195,6 +196,133 @@ Client and server options accept `nativeByteCapabilityMode`:
195
196
  - `required` fails client startup when negotiation does not return `native-byte-v1`. On servers it closes peers that do not negotiate the capability.
196
197
  - `disabled` advertises no native capability and keeps JSON RPC available.
197
198
 
199
+ ### Ephemeral Binary Message Channels
200
+
201
+ TypedSocket separately negotiates `native-message-v1` with
202
+ `__typedsocket_negotiateNativeMessages` after the existing primary capability
203
+ negotiation. Client and server options accept an independent
204
+ `nativeMessageCapabilityMode` (`disabled`, `optional`, or `required`). Optional
205
+ clients tolerate servers that do not know the new private method. Disabled
206
+ clients still call it and advertise an empty capability list when it exists.
207
+ Required servers close peers that do not complete this negotiation within 10
208
+ seconds.
209
+
210
+ Message channels reuse the one `NativeByteManager`, SmartServe raw-frame owner,
211
+ raw queue, control-first scheduler, exact outbound settlement identity, and
212
+ aggregate connection/server accounting. They use a distinct `TSM1` codec and
213
+ do not alter `native-byte-v1` frame encoding or byte-stream protocol semantics.
214
+
215
+ The preferred server API is `createBinaryMessageChannel()`. It synchronously
216
+ binds application authorization and returns a receive grant with an ordinary
217
+ JSON descriptor. Applications explicitly carry that descriptor in their own
218
+ typed DTO; TypedRequest and `VirtualStream` do not serialize it automatically.
219
+
220
+ ```typescript
221
+ import {
222
+ TypedSocket,
223
+ type INativeMessageDescriptor,
224
+ } from '@api.global/typedsocket';
225
+
226
+ interface ICameraAuthorization {
227
+ principalId: string;
228
+ credentialRevision: string;
229
+ configRevision: string;
230
+ bindingRevision: string;
231
+ }
232
+
233
+ declare const cameraAuthorization: ICameraAuthorization;
234
+ declare function isCameraAuthorizationCurrent(
235
+ authorizationArg: ICameraAuthorization,
236
+ operationArg: 'open' | 'message' | 'close' | 'reject',
237
+ ): boolean | Promise<boolean>;
238
+
239
+ const server = TypedSocket.createServer(typedRouter, {
240
+ nativeMessageCapabilityMode: 'required',
241
+ nativeMessageAuthorizationAdapter: {
242
+ bind: (authorizationArg, contextArg) => {
243
+ const authorization = authorizationArg as ICameraAuthorization;
244
+ return {
245
+ ...authorization,
246
+ revalidate: async (revalidationArg) =>
247
+ revalidationArg.connection.side === 'server'
248
+ && revalidationArg.connection.peer === contextArg.target
249
+ && await isCameraAuthorizationCurrent(
250
+ authorization,
251
+ revalidationArg.operation,
252
+ ),
253
+ };
254
+ },
255
+ },
256
+ });
257
+
258
+ // Inside a server TypedHandler:
259
+ const target = server.getServerConnectionForRequest(typedToolsArg);
260
+ const grant = server.createBinaryMessageChannel({
261
+ direction: 'receive',
262
+ target,
263
+ authorization: cameraAuthorization,
264
+ maxMessageBytes: 4 * 1024 * 1024,
265
+ });
266
+
267
+ // Return grant.descriptor in an application-defined response DTO.
268
+ const descriptor: INativeMessageDescriptor = grant.descriptor;
269
+
270
+ void (async () => {
271
+ while (true) {
272
+ const message = await grant.receiver.receive();
273
+ if (message === undefined) break;
274
+ // Process one complete logical message.
275
+ }
276
+ await grant.receiver.closed;
277
+ })().catch((errorArg) => grant.receiver.reject(errorArg));
278
+
279
+ // On the descriptor recipient:
280
+ const sender = await client.nativeBytes.messages.openSender(descriptor);
281
+ await sender.send(jpegBytes);
282
+ await sender.close();
283
+ await sender.closed;
284
+ ```
285
+
286
+ `INativeMessageReceiver` deliberately exposes only `receive()`, `closed`, and
287
+ `reject()`. It has no `ReadableStream`. A complete message occupies one
288
+ application slot, and `MESSAGE_ACK` is queued only when `receive()` dequeues it.
289
+ `send()` snapshots its `Uint8Array` at admission and remains pending until the
290
+ exact logical sequence is acknowledged. Sends serialize without fragment
291
+ interleaving; `close()` queues after admitted sends and waits for `CLOSE_ACK`.
292
+ Empty messages are valid.
293
+
294
+ The receive grant also exposes `opened`, `revoke(reason?)`, and `dispose()`.
295
+ `opened` settles when the peer successfully opens the channel. Use `revoke()`
296
+ to reject an advertised grant, and use `dispose()` to release a descriptor that
297
+ was never published or opened.
298
+
299
+ Descriptors are single-use, expire after 10 seconds, and are bound to one exact
300
+ physical peer or client generation. They are recreated after reconnect. The
301
+ protocol has no durability claim, content hash, FIN, reconnect resume, or
302
+ automatic business idempotency. `RESET` normally closes only its channel;
303
+ malformed binary framing closes the physical connection because the routing
304
+ identity cannot be trusted.
305
+
306
+ | Message-channel limit | Exported constant | Value |
307
+ |---|---|---:|
308
+ | Complete raw frame | `NATIVE_MESSAGE_MAX_FRAME_BYTES` | 32 KiB |
309
+ | Fragment payload | `NATIVE_MESSAGE_MAX_FRAGMENT_PAYLOAD_BYTES` | 32,720 bytes |
310
+ | Logical message package ceiling/default | `NATIVE_MESSAGE_MAX_LOGICAL_MESSAGE_BYTES` / `NATIVE_MESSAGE_DEFAULT_MAX_LOGICAL_MESSAGE_BYTES` | 4 MiB |
311
+ | Pending messages / bytes per channel | `NATIVE_MESSAGE_MAX_PENDING_MESSAGES_PER_CHANNEL` / `NATIVE_MESSAGE_MAX_PENDING_BYTES_PER_CHANNEL` | 8 / 8 MiB |
312
+ | Message channels per connection | `NATIVE_MESSAGE_MAX_CHANNELS_PER_CONNECTION` | 32, also subject to the shared 32 stream+channel slots |
313
+ | Closed-channel tombstones | `NATIVE_MESSAGE_MAX_TOMBSTONES_PER_CONNECTION` / `NATIVE_MESSAGE_TOMBSTONE_RETENTION_MS` | 64 / 60 seconds |
314
+ | Negotiation and OPEN deadlines | `NATIVE_MESSAGE_NEGOTIATION_TIMEOUT_MS` / `NATIVE_MESSAGE_OPEN_TIMEOUT_MS` | 10 seconds |
315
+ | Fragment, acknowledgement, receive, close, and frame progress | `NATIVE_MESSAGE_PROGRESS_TIMEOUT_MS` | 30 seconds |
316
+
317
+ Pending outbound messages, inbound reassembly/completed slots, native-byte
318
+ stream queues, the raw queue, and server-retained frame/authentication state are
319
+ charged to shared bounded connection and server budgets. Message authorization
320
+ reuses the native transport's principal, three authority revisions, callback
321
+ deadline, and per-peer/principal/server retained-revalidation caps. Revalidation
322
+ callbacks receive operation and authority metadata but never message payloads or
323
+ capability secrets. `nativeBytes.messages.getCapability()` and `getStats()`
324
+ likewise expose only bounded counters and status.
325
+
198
326
  #### Preferred VirtualStream Facade
199
327
 
200
328
  For bounded byte payloads, application code uses
@@ -422,7 +550,7 @@ opt-in. Byte DTOs must use the exact native facade returned by
422
550
  | Maximum receive window / queued payload per stream | `NATIVE_BYTE_MAX_WINDOW_BYTES` / `NATIVE_BYTE_MAX_QUEUED_PAYLOAD_BYTES_PER_STREAM` | 1 MiB |
423
551
  | Queued payload per connection | `NATIVE_BYTE_MAX_QUEUED_PAYLOAD_BYTES_PER_CONNECTION` | 8 MiB |
424
552
  | 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 |
553
+ | Native-byte grants/streams plus native-message grants/channels per connection | `NATIVE_BYTE_MAX_STREAMS_PER_CONNECTION` | 32 shared slots |
426
554
  | Pending admitted DATA operations per connection | `NATIVE_BYTE_MAX_PENDING_DATA_OPERATIONS_PER_CONNECTION` | 64 |
427
555
  | Raw inbound queue | `NATIVE_BYTE_MAX_RAW_QUEUE_FRAMES` / `NATIVE_BYTE_MAX_RAW_QUEUE_BYTES` | 64 frames / 2 MiB |
428
556
  | Grant and OPEN timeout | `NATIVE_BYTE_GRANT_OPEN_TIMEOUT_MS` | 10 seconds |
@@ -430,7 +558,7 @@ opt-in. Byte DTOs must use the exact native facade returned by
430
558
  | Closed-stream tombstones | `NATIVE_BYTE_MAX_TOMBSTONES` / `NATIVE_BYTE_TOMBSTONE_RETENTION_MS` | 64, oldest-first, 60 seconds |
431
559
  | Principal and each authority revision | `NATIVE_BYTE_MAX_PRINCIPAL_ID_BYTES` / `NATIVE_BYTE_MAX_AUTHORITY_REVISION_BYTES` | 256 UTF-8 bytes |
432
560
  | 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 |
561
+ | Server connections / native-byte and native-message endpoints | `NATIVE_BYTE_MAX_CONNECTIONS_PER_SERVER` / `NATIVE_BYTE_MAX_STREAMS_PER_SERVER` | 1,024 / 1,024 shared slots |
434
562
  | Server retained bytes / receive reservations | `NATIVE_BYTE_MAX_RETAINED_BYTES_PER_SERVER` | 64 MiB / 64 MiB |
435
563
 
436
564
  SmartServe owner sends prioritize control frames over DATA and pull one binary
@@ -509,11 +637,11 @@ const client = await TypedSocket.createClient(clientRouter, serverUrl, {
509
637
 
510
638
  | Client limit | Exported constant | Package ceiling |
511
639
  |---|---|---:|
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 |
640
+ | Complete text frame | `TYPEDSOCKET_MAX_TEXT_FRAME_BYTES` | 16 MiB |
641
+ | Queued text frames / bytes | `TYPEDSOCKET_MAX_QUEUED_TEXT_FRAMES` / `TYPEDSOCKET_MAX_QUEUED_TEXT_BYTES` | 64 / 32 MiB |
514
642
  | Concurrent handlers / retained callbacks | `TYPEDSOCKET_MAX_CONCURRENT_CLIENT_HANDLERS` / `TYPEDSOCKET_MAX_RETAINED_CLIENT_CALLBACKS` | 16 / 64 |
515
643
  | Pending client requests | `TYPEDSOCKET_MAX_PENDING_CLIENT_REQUESTS` | 1,024 |
516
- | Outbound WebSocket buffered bytes | `TYPEDSOCKET_MAX_OUTBOUND_BUFFERED_BYTES` | 4 MiB |
644
+ | Outbound WebSocket buffered bytes | `TYPEDSOCKET_MAX_OUTBOUND_BUFFERED_BYTES` | 32 MiB |
517
645
  | Method name / correlation ID | `TYPEDSOCKET_MAX_METHOD_NAME_BYTES` / `TYPEDSOCKET_MAX_CORRELATION_ID_BYTES` | 256 UTF-8 bytes each |
518
646
  | Request timeout | `TYPEDSOCKET_MAX_REQUEST_TIMEOUT_MS` | 5 minutes |
519
647
  | Connection restoration timeout | `TYPEDSOCKET_MAX_CONNECTION_RESTORE_TIMEOUT_MS` | 10 seconds |
@@ -818,8 +946,8 @@ await smartServe.stop();
818
946
 
819
947
  | Method | Description |
820
948
  |--------|-------------|
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`. |
949
+ | `createClient(router, serverUrl, options?)` | Creates a WebSocket client. Options include reconnect controls, `abortSignal`, independent `nativeByteCapabilityMode` and `nativeMessageCapabilityMode`, lowering-only `limits`, and `restoreConnection`. |
950
+ | `createServer(routerOrRouters, options?)` | Synchronously composes protocol handling. Options include independent native byte/message capability modes and authorization adapters, plus the default-deny `clientTagPolicy`. |
823
951
  | `fromSmartServe(smartServe, routerOrRouters, options?)` | Creates and attaches a JSON-only server-side TypedSocket to an existing SmartServe instance. |
824
952
  | `useWindowLocationOriginUrl()` | Returns the current window location origin (browser only). |
825
953
 
@@ -829,10 +957,10 @@ await smartServe.stop();
829
957
  |----------|-------------|
830
958
  | `side` | Whether this instance is a `'server'` or `'client'`. |
831
959
  | `typedrouter` | The TypedRouter instance handling requests. |
832
- | `nativeBytes` | Advanced native-byte grant, sender, capability, and statistics API for transport integrations. |
960
+ | `nativeBytes` | Native transport API. Existing stream methods stay on this object; ephemeral message channels are under `nativeBytes.messages`. |
833
961
  | `webSocketTransportOwner` | Stable SmartServe 4 raw-frame owner selected during SmartServe construction. |
834
962
  | `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. |
963
+ | `diagnosticsSubject` | RxJS Subject of structured `TTypedSocketDiagnosticEvent` values: invariant closes (including `scope: 'nativeMessage'`), peer rejections, reconnect scheduling/exhaustion, tag denials, and deprecated implicit targeting. Never completes. |
836
964
 
837
965
  #### Instance Methods
838
966
 
@@ -840,6 +968,7 @@ await smartServe.stop();
840
968
  |--------|-------------|
841
969
  | `attachSmartServe(smartServe)` | Attaches one SmartServe transport to a composed server-side TypedSocket before listening. |
842
970
  | `createVirtualStream(options)` | Creates the preferred exact, authorized native-byte receive facade for one server peer. TypedRequest transfers its descriptor automatically. |
971
+ | `createBinaryMessageChannel(options)` | Creates an authorized ephemeral binary-message receive grant and ordinary descriptor for one exact server peer. |
843
972
  | `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. |
844
973
  | `getServerConnectionForRequest(typedTools)` | Resolves the exact transport connection for an incoming server handler without assertions. |
845
974
  | `getStatus()` | Returns the client connection lifecycle status. Server instances remain in the initial `new` state. |
@@ -861,6 +990,10 @@ await smartServe.stop();
861
990
  | `nativeBytes.openSender(descriptor, options?)` | Opens an explicit sender for an opaque descriptor on the exact target connection. |
862
991
  | `nativeBytes.getCapability(target?)` | Reports negotiated native-byte capability without exposing tokens or descriptors. |
863
992
  | `nativeBytes.getStats(target?)` | Reports bounded connection, stream, queue, and tombstone counts. |
993
+ | `nativeBytes.messages.createReceiveGrant(options)` | Advanced explicit receive-grant API for one ephemeral message channel. The grant exposes `opened`, `revoke(reason?)`, and `dispose()` in addition to its descriptor and receiver. |
994
+ | `nativeBytes.messages.openSender(descriptor, options?)` | Opens the descriptor on the exact physical client generation or required server peer target. |
995
+ | `nativeBytes.messages.getCapability(target?)` | Reports separately negotiated `native-message-v1` status. |
996
+ | `nativeBytes.messages.getStats(target?)` | Reports bounded channel, pending message, reassembly, queue, and aggregate accounting. |
864
997
 
865
998
  ## License and Legal Information
866
999