@nice-code/action 0.23.0 → 0.25.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.
Files changed (40) hide show
  1. package/README.md +71 -26
  2. package/build/{ActionPayload.types-B-OSg09t.d.mts → AcceptorHandler-BizUtq4u.d.mts} +1267 -1543
  3. package/build/{ActionPayload.types-DIOeVapm.d.cts → AcceptorHandler-CxPfZtIl.d.cts} +1267 -1543
  4. package/build/{ActionDevtoolsCore-BjbhFqc0.d.mts → ActionDevtoolsCore-D9KBBI2V.d.cts} +2 -2
  5. package/build/{ActionDevtoolsCore-kk7oZBv9.d.cts → ActionDevtoolsCore-xZjAtB4H.d.mts} +2 -2
  6. package/build/advanced/index.cjs +115 -0
  7. package/build/advanced/index.cjs.map +1 -0
  8. package/build/advanced/index.d.cts +249 -0
  9. package/build/advanced/index.d.mts +249 -0
  10. package/build/advanced/index.mjs +88 -0
  11. package/build/advanced/index.mjs.map +1 -0
  12. package/build/{httpAcceptorCarrier-DJVxzDVd.mjs → createHibernatableWsServerAdapter-BkjESd01.mjs} +243 -429
  13. package/build/createHibernatableWsServerAdapter-BkjESd01.mjs.map +1 -0
  14. package/build/{httpAcceptorCarrier-hYPuoNuP.cjs → createHibernatableWsServerAdapter-FSDWrxoF.cjs} +268 -478
  15. package/build/createHibernatableWsServerAdapter-FSDWrxoF.cjs.map +1 -0
  16. package/build/devtools/browser/index.d.cts +1 -1
  17. package/build/devtools/browser/index.d.mts +1 -1
  18. package/build/devtools/server/index.d.cts +1 -1
  19. package/build/devtools/server/index.d.mts +1 -1
  20. package/build/httpAcceptorCarrier-BQYaXI9j.cjs +454 -0
  21. package/build/httpAcceptorCarrier-BQYaXI9j.cjs.map +1 -0
  22. package/build/httpAcceptorCarrier-DWqsCz3h.mjs +401 -0
  23. package/build/httpAcceptorCarrier-DWqsCz3h.mjs.map +1 -0
  24. package/build/index.cjs +73 -449
  25. package/build/index.cjs.map +1 -1
  26. package/build/index.d.cts +2 -2
  27. package/build/index.d.mts +2 -2
  28. package/build/index.mjs +13 -365
  29. package/build/index.mjs.map +1 -1
  30. package/build/platform/cloudflare/index.cjs +45 -1
  31. package/build/platform/cloudflare/index.cjs.map +1 -1
  32. package/build/platform/cloudflare/index.d.cts +42 -4
  33. package/build/platform/cloudflare/index.d.mts +42 -4
  34. package/build/platform/cloudflare/index.mjs +45 -2
  35. package/build/platform/cloudflare/index.mjs.map +1 -1
  36. package/build/react-query/index.d.cts +1 -1
  37. package/build/react-query/index.d.mts +1 -1
  38. package/package.json +15 -4
  39. package/build/httpAcceptorCarrier-DJVxzDVd.mjs.map +0 -1
  40. package/build/httpAcceptorCarrier-hYPuoNuP.cjs.map +0 -1
@@ -1403,11 +1403,13 @@ var ActionRuntime = class {
1403
1403
  * Used to locate the return-path channel for dispatching results back to the action origin.
1404
1404
  * Returns `undefined` if no handler matches (score > 0 required, i.e. at least id must match).
1405
1405
  *
1406
- * A handler that currently holds the origin's *live* connection always wins over a mere coordinate
1407
- * matchso with several duplex acceptors (e.g. WS + WebRTC) a result/push routes back over the carrier
1408
- * the client actually connected on, never a same-coordinate sibling that lacks the socket. Only when no
1409
- * handler owns a live connection do we fall back to the plain best-coordinate-score pick (the
1410
- * single-acceptor and connector-only cases, unchanged).
1406
+ * A handler that currently holds the origin's *live* connection always wins, regardless of its
1407
+ * coordinate score owning the live socket bound to the origin's exact coordinate (set from the
1408
+ * handshake) is a strictly more precise match than any env-level `peerClient` score. This lets one
1409
+ * server accept clients of *several* envs over a single acceptor (a multi-role Durable Object): the
1410
+ * result/push routes back over the carrier the client actually connected on even when the handler's
1411
+ * `clientEnv` is unset or names a different env. Only when no handler owns a live connection do we fall
1412
+ * back to the plain best-coordinate-score pick (the offline-return and connector-only cases).
1411
1413
  */
1412
1414
  getReturnHandlerForOrigin(originClient) {
1413
1415
  if (originClient.envId === "_unset_") return void 0;
@@ -1422,12 +1424,12 @@ var ActionRuntime = class {
1422
1424
  bestScore = score;
1423
1425
  bestHandler = handler;
1424
1426
  }
1425
- if (score > bestOwnedScore && handler.ownsLiveConnectionFor(originClient)) {
1427
+ if (handler.ownsLiveConnectionFor(originClient) && score > bestOwnedScore) {
1426
1428
  bestOwnedScore = score;
1427
1429
  bestOwnedHandler = handler;
1428
1430
  }
1429
1431
  }
1430
- if (bestOwnedHandler != null && bestOwnedScore > 0) return bestOwnedHandler;
1432
+ if (bestOwnedHandler != null) return bestOwnedHandler;
1431
1433
  return bestScore > 0 ? bestHandler : void 0;
1432
1434
  }
1433
1435
  resetRuntime() {
@@ -2170,7 +2172,7 @@ var AcceptorHandler = class extends PeerLinkHandler {
2170
2172
  _codecByConn = /* @__PURE__ */ new Map();
2171
2173
  _sessionByConn = /* @__PURE__ */ new Map();
2172
2174
  constructor(options) {
2173
- super(options.clientEnv);
2175
+ super(options.clientEnv ?? RuntimeCoordinate.unknown);
2174
2176
  this._formatMessage = options.formatMessage;
2175
2177
  this._createFormatMessage = options.createFormatMessage;
2176
2178
  this._send = options.send;
@@ -2498,7 +2500,7 @@ const createAcceptorHandler = (options) => {
2498
2500
  //#endregion
2499
2501
  //#region src/ActionRuntime/Handler/PeerLink/Acceptor/createSecureActionServer.ts
2500
2502
  /** Default accepted set: negotiate per connection to whatever the client picks. */
2501
- const DEFAULT_SERVER_SECURITY_LEVELS$1 = [
2503
+ const DEFAULT_SERVER_SECURITY_LEVELS = [
2502
2504
  "none",
2503
2505
  "authenticated",
2504
2506
  "encrypted"
@@ -2506,14 +2508,14 @@ const DEFAULT_SERVER_SECURITY_LEVELS$1 = [
2506
2508
  /**
2507
2509
  * Build an {@link AcceptorHandler} for the secure binary channel with the boilerplate folded in:
2508
2510
  * it creates the {@link ClientCryptoKeyLink} and the storage-backed TOFU resolver from a single
2509
- * `storageAdapter`, installs the channel's per-connection codec, and assembles the `security` block
2511
+ * `storage`, installs the channel's per-connection codec, and assembles the `security` block
2510
2512
  * from the runtime coordinate + channel version (accepting all three levels by default).
2511
2513
  *
2512
2514
  * For a hibernatable transport (e.g. a Durable Object), pair it with
2513
2515
  * {@link createHibernatableWsServerAdapter} to wire persistence + replay.
2514
2516
  */
2515
2517
  function createSecureAcceptorHandler(options) {
2516
- const link = options.link ?? new ClientCryptoKeyLink({ storageAdapter: options.storageAdapter });
2518
+ const link = options.link ?? new ClientCryptoKeyLink({ storageAdapter: options.storage });
2517
2519
  return new AcceptorHandler({
2518
2520
  clientEnv: options.clientEnv,
2519
2521
  createFormatMessage: options.channel.createCodec,
@@ -2521,30 +2523,247 @@ function createSecureAcceptorHandler(options) {
2521
2523
  runtime: options.runtime,
2522
2524
  defaultTimeout: options.defaultTimeout,
2523
2525
  security: {
2524
- securityLevel: options.securityLevel ?? DEFAULT_SERVER_SECURITY_LEVELS$1,
2526
+ securityLevel: options.securityLevel ?? DEFAULT_SERVER_SECURITY_LEVELS,
2525
2527
  link,
2526
2528
  localCoordinate: options.runtime.coordinate.toJsonObject(),
2527
2529
  dictionaryVersion: options.channel.dictionaryVersion,
2528
- verifyKeyResolver: options.verifyKeyResolver ?? createStorageTofuVerifyKeyResolver(options.storageAdapter)
2530
+ verifyKeyResolver: options.verifyKeyResolver ?? createStorageTofuVerifyKeyResolver(options.storage)
2529
2531
  }
2530
2532
  });
2531
2533
  }
2532
2534
  //#endregion
2533
- //#region src/ActionRuntime/Transport/Carrier/Carrier.types.ts
2535
+ //#region src/ActionRuntime/Transport/codec/actionWireCodec.ts
2536
+ /**
2537
+ * Tiny integer codes for the payload type, so the verbose `"request"`/`"result"`/`"progress"`
2538
+ * strings never hit the wire. The index in {@link ReversePayloadType} must line up with the value.
2539
+ */
2540
+ const PayloadTypeToInt = {
2541
+ ["request"]: 0,
2542
+ ["result"]: 1,
2543
+ ["progress"]: 2
2544
+ };
2545
+ const ReversePayloadType = [
2546
+ "request",
2547
+ "result",
2548
+ "progress"
2549
+ ];
2550
+ /**
2551
+ * Build the positional `domain:id` ↔ integer dictionary. Both ends of a channel MUST build it from
2552
+ * the same domains in the same order — the mapping is positional, so a mismatch routes to the wrong
2553
+ * action. Add new transported domains to the end of the list.
2554
+ */
2555
+ function buildActionRouteDictionary(domains) {
2556
+ const routeToInt = /* @__PURE__ */ new Map();
2557
+ const intToRoute = [];
2558
+ for (const dom of domains) for (const actionId of Object.keys(dom.actionSchema)) {
2559
+ const routeKey = `${dom.domain}:${actionId}`;
2560
+ if (routeToInt.has(routeKey)) continue;
2561
+ routeToInt.set(routeKey, intToRoute.length);
2562
+ intToRoute.push({
2563
+ domain: dom.domain,
2564
+ id: actionId,
2565
+ allDomains: dom.allDomains
2566
+ });
2567
+ }
2568
+ return {
2569
+ routeToInt,
2570
+ intToRoute
2571
+ };
2572
+ }
2573
+ /** Pull the type-specific payload (`input` / `result` / `progress`) out of a wire JSON object. */
2574
+ function extractWirePayload(json) {
2575
+ if (json.type === "request") return json.input;
2576
+ if (json.type === "result") return json.result;
2577
+ if (json.type === "progress") return json.progress;
2578
+ }
2534
2579
  /**
2535
- * Narrow a carrier source to the exchange shape via its `shape` discriminant — the one branch the
2536
- * transport factories ({@link secureTransport}, {@link plainTransport}) use to pick the duplex vs
2537
- * exchange transport. A duplex source carries no `shape`, so the `else` branch is the duplex one.
2580
+ * Reassemble a full wire JSON object from its decoded parts. `inputHash`/`outputHash` are emitted
2581
+ * empty the hydration constructors recompute them and the result still satisfies
2582
+ * `isActionPayload_Any_JsonObject` so it flows through validation like a JSON frame.
2538
2583
  */
2539
- function isExchangeCarrierSource(carrier) {
2540
- return "shape" in carrier && carrier.shape === "exchange";
2584
+ function assembleWireJson(routeMeta, payloadType, time, context, payloadData) {
2585
+ const base = {
2586
+ form: "data",
2587
+ domain: routeMeta.domain,
2588
+ id: routeMeta.id,
2589
+ allDomains: routeMeta.allDomains,
2590
+ time,
2591
+ context
2592
+ };
2593
+ if (payloadType === "request") return {
2594
+ ...base,
2595
+ type: "request",
2596
+ input: payloadData,
2597
+ inputHash: ""
2598
+ };
2599
+ if (payloadType === "result") return {
2600
+ ...base,
2601
+ type: "result",
2602
+ result: payloadData,
2603
+ outputHash: ""
2604
+ };
2605
+ return {
2606
+ ...base,
2607
+ type: "progress",
2608
+ progress: payloadData
2609
+ };
2610
+ }
2611
+ //#endregion
2612
+ //#region src/ActionRuntime/Transport/codec/createBinaryWireSessionFactory.ts
2613
+ /**
2614
+ * Positional layout of the *session* binary envelope — the leanest frame. Compared to the stateless
2615
+ * adapter it replaces the 21-char `cuid` with a small per-connection integer and only carries
2616
+ * `originClient` on the very first request of each direction (the peer remembers it afterwards).
2617
+ *
2618
+ * [ routeInt, typeInt, corrId, time, originClient?, payloadData ]
2619
+ */
2620
+ const ENVELOPE = {
2621
+ route: 0,
2622
+ type: 1,
2623
+ corr: 2,
2624
+ time: 3,
2625
+ originClient: 4,
2626
+ payload: 5
2627
+ };
2628
+ const ENVELOPE_LENGTH = 6;
2629
+ /**
2630
+ * How long a pending correlation entry is kept before it's swept. A correlation only matters until its
2631
+ * action resolves or times out, so anything older than the longest realistic action timeout can be
2632
+ * dropped — this bounds memory when requests time out or a connection dies mid-flight (their replies
2633
+ * would never arrive, leaving the entry orphaned). Generous default so live correlations are never
2634
+ * pruned (the default transport timeout is 10s).
2635
+ */
2636
+ const DEFAULT_CORRELATION_TTL_MS = 5 * 6e4;
2637
+ function isKnownIdentity(coordinate) {
2638
+ return coordinate != null && coordinate.envId !== "_unset_";
2639
+ }
2640
+ /**
2641
+ * Drop entries older than `ttlMs`. Maps keep insertion order and entries are inserted in time order,
2642
+ * so the oldest are first — stop sweeping at the first live entry.
2643
+ */
2644
+ function pruneExpired(map, now, ttlMs) {
2645
+ for (const [key, entry] of map) {
2646
+ if (now - entry.time <= ttlMs) break;
2647
+ map.delete(key);
2648
+ }
2649
+ }
2650
+ /**
2651
+ * Builds a factory of *stateful, per-connection* codecs for {@link LinkTransport} /
2652
+ * `AcceptorHandler` — the maximally compact binary wire. Call the returned factory once per live
2653
+ * connection (each socket on the client, each accepted connection on the server) so every channel
2654
+ * gets its own correlation + identity state.
2655
+ *
2656
+ * On top of everything {@link createBinaryWireAdapter} drops, a session also drops:
2657
+ * - **`cuid`** — replaced by a per-connection integer correlation id. The initiator maps it to its
2658
+ * real cuid; the responder echoes it; each side reconstructs the cuid from its own map. Correlation
2659
+ * only needs to be unique per socket, so a counter suffices.
2660
+ * - **`originClient` after the first request** — the first request each side sends carries its
2661
+ * identity; the peer remembers it and injects it into later frames. Replies omit it entirely (a
2662
+ * reply carries the initiator's own origin, which the initiator already knows).
2663
+ *
2664
+ * Both ends MUST build the factory from the same domains in the same order (positional dictionary).
2665
+ * Text frames still return `undefined` from `incoming`, so JSON clients remain interoperable.
2666
+ *
2667
+ * Hibernation note: after a server connection is evicted its session resets, so a still-connected
2668
+ * client (whose session persists) will keep omitting `originClient`. The server must therefore restore
2669
+ * the connection→client binding from its own store (see `AcceptorHandler.rehydrateConnection`) and
2670
+ * inject `originClient` from there — the session alone can't recover it.
2671
+ */
2672
+ function createBinaryWireSessionFactory(domains, options) {
2673
+ const { routeToInt, intToRoute } = buildActionRouteDictionary(domains);
2674
+ const unknownIdentity = RuntimeCoordinate.unknown.toJsonObject();
2675
+ const ttlMs = options?.correlationTtlMs ?? DEFAULT_CORRELATION_TTL_MS;
2676
+ return () => {
2677
+ let outCounter = 0;
2678
+ const corrToCuid = /* @__PURE__ */ new Map();
2679
+ const cuidToCorr = /* @__PURE__ */ new Map();
2680
+ let selfIdentity;
2681
+ let peerIdentity;
2682
+ return {
2683
+ outgoing: (input) => {
2684
+ const json = input.action.toJsonObject();
2685
+ const routeKey = `${json.domain}:${json.id}`;
2686
+ const routeInt = routeToInt.get(routeKey);
2687
+ if (routeInt == null) throw new Error(`[binary-wire] Cannot pack unregistered action route: ${routeKey}`);
2688
+ const now = Date.now();
2689
+ pruneExpired(corrToCuid, now, ttlMs);
2690
+ pruneExpired(cuidToCorr, now, ttlMs);
2691
+ let corr;
2692
+ let wireIdentity;
2693
+ if (json.type === "request") {
2694
+ corr = outCounter++;
2695
+ corrToCuid.set(corr, {
2696
+ value: json.context.cuid,
2697
+ time: now
2698
+ });
2699
+ if (selfIdentity == null && isKnownIdentity(json.context.originClient)) {
2700
+ selfIdentity = json.context.originClient;
2701
+ wireIdentity = json.context.originClient;
2702
+ }
2703
+ } else {
2704
+ corr = cuidToCorr.get(json.context.cuid)?.value ?? -1;
2705
+ if (json.type === "result") cuidToCorr.delete(json.context.cuid);
2706
+ }
2707
+ const envelope = new Array(ENVELOPE_LENGTH);
2708
+ envelope[ENVELOPE.route] = routeInt;
2709
+ envelope[ENVELOPE.type] = PayloadTypeToInt[json.type];
2710
+ envelope[ENVELOPE.corr] = corr;
2711
+ envelope[ENVELOPE.time] = json.time;
2712
+ envelope[ENVELOPE.originClient] = wireIdentity;
2713
+ envelope[ENVELOPE.payload] = extractWirePayload(json);
2714
+ return pack(envelope);
2715
+ },
2716
+ incoming: (frame) => {
2717
+ let buffer;
2718
+ if (frame instanceof ArrayBuffer) buffer = new Uint8Array(frame);
2719
+ else if (frame instanceof Uint8Array) buffer = frame;
2720
+ else return;
2721
+ try {
2722
+ const envelope = unpack(buffer);
2723
+ if (!Array.isArray(envelope) || envelope.length !== ENVELOPE_LENGTH) return void 0;
2724
+ const routeMeta = intToRoute[envelope[ENVELOPE.route]];
2725
+ const payloadType = ReversePayloadType[envelope[ENVELOPE.type]];
2726
+ if (routeMeta == null || payloadType == null) return void 0;
2727
+ const now = Date.now();
2728
+ pruneExpired(corrToCuid, now, ttlMs);
2729
+ pruneExpired(cuidToCorr, now, ttlMs);
2730
+ const corr = envelope[ENVELOPE.corr];
2731
+ const time = envelope[ENVELOPE.time];
2732
+ const wireIdentity = envelope[ENVELOPE.originClient];
2733
+ let cuid;
2734
+ let originClient;
2735
+ if (payloadType === "request") {
2736
+ cuid = nanoid();
2737
+ cuidToCorr.set(cuid, {
2738
+ value: corr,
2739
+ time: now
2740
+ });
2741
+ if (isKnownIdentity(wireIdentity)) peerIdentity = wireIdentity;
2742
+ originClient = peerIdentity ?? unknownIdentity;
2743
+ } else {
2744
+ cuid = corrToCuid.get(corr)?.value ?? nanoid();
2745
+ if (payloadType === "result") corrToCuid.delete(corr);
2746
+ originClient = selfIdentity ?? unknownIdentity;
2747
+ }
2748
+ return assembleWireJson(routeMeta, payloadType, time, {
2749
+ cuid,
2750
+ timeCreated: time,
2751
+ routing: [],
2752
+ originClient
2753
+ }, envelope[ENVELOPE.payload]);
2754
+ } catch (e) {
2755
+ console.error("[binary-wire] Failed to unpack binary action session frame", e);
2756
+ return;
2757
+ }
2758
+ }
2759
+ };
2760
+ };
2541
2761
  }
2542
2762
  //#endregion
2543
2763
  //#region src/ActionRuntime/Transport/Transport.ts
2544
2764
  /**
2545
- * Reusable transport definition. Devs construct these (`secureTransport({ carrier: wsCarrier(() => ({ url })) })`,
2546
- * `plainTransport({ carrier: httpCarrier(...) })`, …) and pass them to a
2547
- * `ConnectorHandler`. A single
2765
+ * Reusable transport definition. Built by the internal `transport({ carrier, secure })` factory (which
2766
+ * `connectChannel` / `serveChannel` drive) and passed to a `ConnectorHandler`. A single
2548
2767
  * definition can be shared across multiple handlers — each handler builds its own live
2549
2768
  * {@link TransportConnection} via {@link TransportConnection._createConnection}.
2550
2769
  */
@@ -3159,175 +3378,6 @@ var LinkTransport = class LinkTransport extends Transport {
3159
3378
  }
3160
3379
  };
3161
3380
  //#endregion
3162
- //#region src/ActionRuntime/Transport/plainTransport.ts
3163
- function plainTransport(options) {
3164
- const carrier = options.carrier;
3165
- if (isExchangeCarrierSource(carrier)) return ExchangeTransport.create({
3166
- openCarrier: carrier.open,
3167
- getTransportCacheKey: carrier.getCacheKey,
3168
- available: options.available,
3169
- getRouteInfo: carrier.getRouteInfo,
3170
- label: options.label ?? carrier.carrierLabel,
3171
- updateRunConfig: options.updateRunConfig
3172
- });
3173
- return LinkTransport.create({
3174
- openChannel: carrier.open,
3175
- formatMessage: options.formatMessage,
3176
- createFormatMessage: options.createFormatMessage,
3177
- getTransportCacheKey: carrier.getCacheKey,
3178
- available: options.available,
3179
- getRouteInfo: carrier.getRouteInfo,
3180
- label: options.label ?? carrier.carrierLabel,
3181
- updateRunConfig: options.updateRunConfig
3182
- });
3183
- }
3184
- //#endregion
3185
- //#region src/ActionRuntime/Transport/secureTransport.ts
3186
- function secureTransport(options) {
3187
- const link = options.link ?? (options.storageAdapter != null ? new ClientCryptoKeyLink({ storageAdapter: options.storageAdapter }) : void 0);
3188
- if (link == null) throw new Error("secureTransport: provide `link` or `storageAdapter` for the crypto identity.");
3189
- const security = {
3190
- securityLevel: options.securityLevel,
3191
- link,
3192
- localCoordinate: options.runtime.coordinate.toJsonObject(),
3193
- dictionaryVersion: options.channel.dictionaryVersion
3194
- };
3195
- const carrier = options.carrier;
3196
- if (isExchangeCarrierSource(carrier)) return ExchangeTransport.create({
3197
- openCarrier: carrier.open,
3198
- getTransportCacheKey: carrier.getCacheKey,
3199
- available: options.available,
3200
- getRouteInfo: carrier.getRouteInfo,
3201
- label: carrier.carrierLabel,
3202
- security
3203
- });
3204
- return LinkTransport.create({
3205
- openChannel: carrier.open,
3206
- createFormatMessage: options.channel.createCodec,
3207
- getTransportCacheKey: carrier.getCacheKey,
3208
- available: options.available,
3209
- getRouteInfo: carrier.getRouteInfo,
3210
- label: carrier.carrierLabel,
3211
- security
3212
- });
3213
- }
3214
- //#endregion
3215
- //#region src/ActionRuntime/Channel/ActionChannel.ts
3216
- /**
3217
- * Declare a transport-agnostic channel by role. Use it for HTTP, custom transports, or as the routing
3218
- * half of a richer channel. The order of each list is part of the contract for wire formats that pack
3219
- * positionally (see `defineSecureChannel`) — add new domains to the end of their list. (`domains` is
3220
- * accepted as a legacy alias for `toAcceptor`.)
3221
- */
3222
- function defineChannel(options) {
3223
- return {
3224
- toAcceptorDomains: options.toAcceptor,
3225
- toConnectorDomains: options.toConnector
3226
- };
3227
- }
3228
- /**
3229
- * Open a connection to a peer from a single call — the dial-out dual of `serveChannel`. The channel is
3230
- * the single source of truth for *what* is routed (`toAcceptor` domains forwarded to the peer,
3231
- * `toConnector` pushes handled locally from `onPush`); the call binds the shared facts — the channel's
3232
- * codec/dictionary version, the runtime, and one crypto identity (a {@link ClientCryptoKeyLink} over
3233
- * `storage`) — into every transport in `transports`, so none of them restate the channel or runtime.
3234
- * List several transports to make the path transport-agnostic (secure WS preferred, HTTP fallback):
3235
- * ```ts
3236
- * const handler = connectChannel(runtime, lobbyChannel, {
3237
- * peer: runtime_coordinate_lobby_do,
3238
- * storage,
3239
- * transports: [{ carrier: wsCarrier(() => ({ url })) }, { carrier: httpCarrier(...), secure: false }],
3240
- * onPush: { player_joined: (p) => { … } },
3241
- * });
3242
- * ```
3243
- * Returns the {@link ConnectorHandler} so the caller can later `clearTransportCache()` it.
3244
- */
3245
- function connectChannel(runtime, channel, options) {
3246
- const securityLevel = options.securityLevel ?? "authenticated";
3247
- const anySecure = options.transports.some((transport) => transport.secure ?? true);
3248
- let link = options.link;
3249
- if (anySecure && link == null) {
3250
- if (options.storage == null) throw new Error("connectChannel: a secure transport requires `storage` (or `link`). Pass it, or set `secure: false` on the transport for a plain connection.");
3251
- link = new ClientCryptoKeyLink({ storageAdapter: options.storage });
3252
- }
3253
- const transports = options.transports.map((transport) => {
3254
- const carrier = transport.carrier;
3255
- const secure = transport.secure ?? true;
3256
- if (isExchangeCarrierSource(carrier)) return secure ? secureTransport({
3257
- channel,
3258
- runtime,
3259
- link,
3260
- securityLevel: transport.securityLevel ?? securityLevel,
3261
- available: transport.available,
3262
- carrier
3263
- }) : plainTransport({
3264
- carrier,
3265
- available: transport.available,
3266
- label: transport.label
3267
- });
3268
- return secure ? secureTransport({
3269
- channel,
3270
- runtime,
3271
- link,
3272
- securityLevel: transport.securityLevel ?? securityLevel,
3273
- available: transport.available,
3274
- carrier
3275
- }) : plainTransport({
3276
- carrier,
3277
- createFormatMessage: channel.createCodec,
3278
- available: transport.available,
3279
- label: transport.label
3280
- });
3281
- });
3282
- const pushHandlers = options.onPush != null ? channel.toConnectorDomains.map((domain) => domain.wrapAsPartialLocalHandler(options.onPush)) : [];
3283
- return runtime.connectTo(options.peer, {
3284
- transports,
3285
- domains: [...channel.toAcceptorDomains],
3286
- localHandlers: pushHandlers,
3287
- defaultTimeout: options.defaultTimeout
3288
- });
3289
- }
3290
- /**
3291
- * Register an acceptor handler's execution for a channel straight from its definition: the channel's
3292
- * `toAcceptor` domains are served together with one merged, connection-aware case map (each case gets
3293
- * the primed request + the originating connection, as with
3294
- * {@link AcceptorHandler.forConnectionDomainCases}). The domain list is taken from the channel,
3295
- * never restated. Add the returned handler to the runtime alongside the acceptor handler:
3296
- * ```ts
3297
- * runtime.addHandlers([acceptChannelConnections(serverHandler, channel, { … }), serverHandler]);
3298
- * ```
3299
- *
3300
- * The case's second argument is the raw connection (`TConn | undefined`). For the richer state +
3301
- * broadcast + pushBack context, serve the channel through `serveChannel`'s `channelCases` instead.
3302
- */
3303
- function acceptChannelConnections(serverHandler, channel, cases) {
3304
- return serverHandler.forConnectionDomainCasesMulti(channel.toAcceptorDomains, cases, (connection) => connection);
3305
- }
3306
- /**
3307
- * Build the secure {@link AcceptorHandler} for a channel — the accept-in counterpart to
3308
- * {@link connectChannel}. It folds in the same boilerplate as {@link createSecureAcceptorHandler} (the
3309
- * `ClientCryptoKeyLink` + storage-backed TOFU resolver from one `storageAdapter`, the channel's codec +
3310
- * dictionary version, the `security` block from the runtime coordinate) but takes the `(runtime, channel,
3311
- * options)` shape of the channel family. Pair it with {@link acceptChannelConnections} for execution:
3312
- * ```ts
3313
- * const acceptor = acceptChannel(runtime, gameChannel, { clientEnv, storageAdapter, send });
3314
- * runtime.addHandlers([acceptChannelConnections(acceptor, gameChannel, { … }), acceptor]);
3315
- * ```
3316
- */
3317
- function acceptChannel(runtime, channel, options) {
3318
- return createSecureAcceptorHandler({
3319
- channel,
3320
- runtime,
3321
- clientEnv: options.clientEnv,
3322
- storageAdapter: options.storageAdapter,
3323
- link: options.link,
3324
- send: options.send,
3325
- securityLevel: options.securityLevel,
3326
- verifyKeyResolver: options.verifyKeyResolver,
3327
- defaultTimeout: options.defaultTimeout
3328
- });
3329
- }
3330
- //#endregion
3331
3381
  //#region src/ActionRuntime/Transport/SecureSession/exchangeAcceptor.ts
3332
3382
  const textEncoder = new TextEncoder();
3333
3383
  const textDecoder = new TextDecoder();
@@ -3665,242 +3715,6 @@ function createHibernatableWsServerAdapter(options) {
3665
3715
  };
3666
3716
  }
3667
3717
  //#endregion
3668
- //#region src/ActionRuntime/Transport/Carrier/AcceptorCarrier.types.ts
3669
- /**
3670
- * Build the inert lifecycle slot a duplex carrier factory spreads into its handle: `receive`/`drop` throw
3671
- * (or no-op) until `serveChannel` calls `_activate` with the carrier's live router. Shared by every duplex
3672
- * carrier factory (`wsAcceptorCarrier`, a WebRTC carrier, the Cloudflare DO helper, …) so the
3673
- * stateful-handle wiring lives in exactly one place.
3674
- */
3675
- function createDuplexCarrierLifecycle() {
3676
- let router;
3677
- return {
3678
- receive(connection, frame) {
3679
- if (router == null) throw new Error("acceptor carrier not served yet — pass it to serveChannel() before feeding frames");
3680
- router.receive(connection, frame);
3681
- },
3682
- drop(connection) {
3683
- router?.drop(connection);
3684
- },
3685
- _activate(liveRouter) {
3686
- router = liveRouter;
3687
- }
3688
- };
3689
- }
3690
- /**
3691
- * Narrow an acceptor carrier to the exchange shape via its `shape` discriminant — the one branch
3692
- * `serveChannel` uses to pick the duplex (push-capable) vs exchange (request/reply) wiring. A duplex
3693
- * carrier carries `shape: ETransportShape.duplex`, so the `else` branch is the duplex one.
3694
- */
3695
- function isExchangeAcceptorCarrier(carrier) {
3696
- return carrier.shape === "exchange";
3697
- }
3698
- //#endregion
3699
- //#region src/ActionRuntime/Channel/serveChannel.ts
3700
- /** Default accepted set, shared by every carrier: negotiate per connection to whatever the client picks. */
3701
- const DEFAULT_SERVER_SECURITY_LEVELS = [
3702
- "none",
3703
- "authenticated",
3704
- "encrypted"
3705
- ];
3706
- function serveChannel(runtime, channel, options) {
3707
- const duplexCarriers = options.carriers.filter((carrier) => !isExchangeAcceptorCarrier(carrier));
3708
- const exchangeCarriers = options.carriers.filter(isExchangeAcceptorCarrier);
3709
- if (exchangeCarriers.length > 1) throw new Error("serveChannel: at most one exchange carrier is supported");
3710
- const exchangeCarrier = exchangeCarriers[0];
3711
- const singleDuplex = duplexCarriers.length === 1;
3712
- if (options.connectionState != null && !singleDuplex) throw new Error("serveChannel: `connectionState` requires exactly one duplex carrier");
3713
- if (options.channelCases != null && !singleDuplex) throw new Error("serveChannel: `channelCases` requires exactly one duplex carrier");
3714
- const exchangeSecure = exchangeCarrier != null && (exchangeCarrier.secure ?? true);
3715
- const anyDuplexSecure = duplexCarriers.some((carrier) => carrier.secure ?? true);
3716
- const securityLevel = options.securityLevel ?? DEFAULT_SERVER_SECURITY_LEVELS;
3717
- let secure;
3718
- if (anyDuplexSecure || exchangeSecure) {
3719
- const storage = options.storage;
3720
- if (storage == null) throw new Error("serveChannel: a secure carrier requires `storage`. Pass it, or set `secure: false` on the carrier for a plain endpoint.");
3721
- secure = {
3722
- storage,
3723
- link: options.link ?? new ClientCryptoKeyLink({ storageAdapter: storage }),
3724
- verifyKeyResolver: options.verifyKeyResolver ?? createStorageTofuVerifyKeyResolver(storage)
3725
- };
3726
- }
3727
- const plainRouter = (handler) => ({
3728
- receive: (connection, frame) => handler.receive(connection, frame),
3729
- drop: (connection) => handler.dropConnection(connection)
3730
- });
3731
- const asObject = (value) => typeof value === "object" && value != null ? value : {};
3732
- const handlers = [];
3733
- let connections;
3734
- for (const carrier of duplexCarriers) {
3735
- const handler = (carrier.secure ?? true) && secure != null ? acceptChannel(runtime, channel, {
3736
- clientEnv: options.clientEnv,
3737
- storageAdapter: secure.storage,
3738
- link: secure.link,
3739
- verifyKeyResolver: secure.verifyKeyResolver,
3740
- securityLevel,
3741
- send: carrier.send,
3742
- defaultTimeout: options.defaultTimeout
3743
- }) : createAcceptorHandler({
3744
- clientEnv: options.clientEnv,
3745
- createFormatMessage: channel.createCodec,
3746
- send: carrier.send,
3747
- runtime,
3748
- defaultTimeout: options.defaultTimeout
3749
- });
3750
- const attach = carrier.attachmentStore;
3751
- let router;
3752
- if (attach == null) router = plainRouter(handler);
3753
- else if (options.connectionState != null) {
3754
- connections = createConnectionStateStore(handler, {
3755
- schema: options.connectionState.schema,
3756
- getConnections: attach.getConnections,
3757
- read: attach.read,
3758
- write: attach.write
3759
- });
3760
- router = plainRouter(handler);
3761
- } else router = createHibernatableWsServerAdapter({
3762
- handler,
3763
- getConnections: attach.getConnections,
3764
- getAttachment: (connection) => attach.read(connection)?.binding,
3765
- setAttachment: (connection, binding) => attach.write(connection, {
3766
- ...asObject(attach.read(connection)),
3767
- binding
3768
- })
3769
- });
3770
- carrier._activate(router);
3771
- handlers.push(handler);
3772
- }
3773
- runtime.addHandlers([...options.handlers ?? [], ...handlers]);
3774
- const soleDuplex = singleDuplex ? duplexCarriers[0] : void 0;
3775
- const receive = (connection, frame) => {
3776
- if (soleDuplex == null) throw new Error(duplexCarriers.length === 0 ? "serveChannel: no duplex carrier to receive on (this server has no socket transport)" : "serveChannel: several duplex carriers — feed each carrier handle's receive() directly");
3777
- soleDuplex.receive(connection, frame);
3778
- };
3779
- const drop = (connection) => {
3780
- soleDuplex?.drop(connection);
3781
- };
3782
- const pushToClient = (target, request, pushOptions) => {
3783
- const owner = target instanceof RuntimeCoordinate ? handlers.find((handler) => handler.ownsLiveConnectionFor(target)) : handlers.find((handler) => handler.hasConnection(target));
3784
- if (owner == null) throw new Error("serveChannel: no duplex carrier holds a connection for the push target");
3785
- return owner.pushToClient(runtime, target, request, pushOptions);
3786
- };
3787
- const broadcast = (makeRequest, broadcastOptions) => {
3788
- if (!singleDuplex) throw new Error("serveChannel: broadcast requires exactly one duplex carrier — broadcast over a specific handlers[i] instead");
3789
- handlers[0].broadcast(makeRequest, {
3790
- runtime,
3791
- ...broadcastOptions
3792
- });
3793
- };
3794
- const makeConnectionContext = (connection, request) => ({
3795
- connection: connection ?? null,
3796
- origin: request.context.originClient,
3797
- get state() {
3798
- return connection != null && connections != null ? connections.get(connection) : null;
3799
- },
3800
- setState(value) {
3801
- if (connection != null && connections != null) connections.set(connection, value);
3802
- },
3803
- clearState() {
3804
- if (connection != null && connections != null) connections.clearApp(connection);
3805
- },
3806
- broadcast(makeRequest, contextOptions) {
3807
- broadcast(makeRequest, {
3808
- except: contextOptions?.exceptSelf ? connection ?? null : null,
3809
- where: contextOptions?.where,
3810
- timeout: contextOptions?.timeout,
3811
- onError: contextOptions?.onError
3812
- });
3813
- },
3814
- pushBack(pushRequest, pushOptions) {
3815
- if (connection == null) throw new Error("serveChannel: connection context has no live socket to push back to (HTTP-exchange path)");
3816
- return pushToClient(connection, pushRequest, pushOptions);
3817
- }
3818
- });
3819
- if (options.channelCases != null) runtime.addHandlers([handlers[0].forConnectionDomainCasesMulti(channel.toAcceptorDomains, options.channelCases, makeConnectionContext)]);
3820
- const exchangeSecurity = exchangeSecure && secure != null ? {
3821
- link: secure.link,
3822
- verifyKeyResolver: secure.verifyKeyResolver,
3823
- localCoordinate: runtime.coordinate.toJsonObject(),
3824
- dictionaryVersion: channel.dictionaryVersion,
3825
- securityLevel
3826
- } : void 0;
3827
- const defaultIsUpgrade = (request) => request.headers.get("Upgrade") === "websocket";
3828
- const upgraders = [];
3829
- for (const carrier of duplexCarriers) {
3830
- if (carrier.upgrade == null) continue;
3831
- upgraders.push({
3832
- isUpgrade: carrier.isUpgrade ?? defaultIsUpgrade,
3833
- upgrade: carrier.upgrade
3834
- });
3835
- }
3836
- return {
3837
- handlers,
3838
- fetch: createActionFetchHandler(runtime, {
3839
- cors: exchangeCarrier?.cors,
3840
- onWebSocketUpgrade: upgraders.length === 0 ? void 0 : (request, url) => (upgraders.find((u) => u.isUpgrade(request, url)) ?? upgraders[0]).upgrade(request, url),
3841
- isWebSocketUpgrade: upgraders.length === 0 ? void 0 : (request, url) => upgraders.some((u) => u.isUpgrade(request, url)),
3842
- isActionPath: exchangeCarrier != null ? exchangeCarrier.isActionPath ?? (() => true) : () => false,
3843
- security: exchangeSecurity,
3844
- useErrorStatus: exchangeCarrier?.useErrorStatus
3845
- }),
3846
- receive,
3847
- drop,
3848
- pushToClient,
3849
- broadcast,
3850
- connections
3851
- };
3852
- }
3853
- //#endregion
3854
- //#region src/ActionRuntime/Channel/serveHost.ts
3855
- function serveHost(runtime, channel, host, options) {
3856
- const server = serveChannel(runtime, channel, {
3857
- ...options,
3858
- carriers: host.carriers,
3859
- storage: host.storage
3860
- });
3861
- host.onServed?.(server);
3862
- return server;
3863
- }
3864
- //#endregion
3865
- //#region src/ActionRuntime/Transport/Carrier/duplex/ws/wsAcceptorCarrier.ts
3866
- /**
3867
- * A WebSocket {@link IDuplexAcceptorCarrier}: the accept-in dual of {@link wsCarrier}. It describes how to
3868
- * write frames back to a live socket, how to upgrade an inbound request into one, and (optionally) how to
3869
- * persist bindings across hibernation. Hand it to `serveChannel`'s `carriers` list — the secure session,
3870
- * codec, and crypto identity are supplied centrally there, so this only carries the WS-specific surface.
3871
- */
3872
- function wsAcceptorCarrier(options) {
3873
- return {
3874
- ...createDuplexCarrierLifecycle(),
3875
- shape: "duplex",
3876
- carrierLabel: options.carrierLabel ?? "ws",
3877
- secure: options.secure,
3878
- send: options.send,
3879
- upgrade: options.upgrade,
3880
- isUpgrade: options.isUpgrade ?? ((request) => request.headers.get("Upgrade") === "websocket"),
3881
- attachmentStore: options.attachmentStore
3882
- };
3883
- }
3884
- //#endregion
3885
- //#region src/ActionRuntime/Transport/Carrier/exchange/http/httpAcceptorCarrier.ts
3886
- /**
3887
- * An HTTP {@link IExchangeAcceptorCarrier}: the accept-in dual of {@link httpCarrier}. It serves the
3888
- * secure exchange protocol (handshake → token session → encrypted frames) over web-standard
3889
- * `Request`/`Response`. The crypto identity, runtime coordinate, dictionary version, and accepted security
3890
- * levels are all supplied centrally by `serveChannel`, so this only needs to say which requests carry an
3891
- * action envelope and how to answer CORS.
3892
- */
3893
- function httpAcceptorCarrier(options = {}) {
3894
- return {
3895
- shape: "exchange",
3896
- carrierLabel: options.carrierLabel ?? "http",
3897
- secure: options.secure,
3898
- isActionPath: options.isActionPath,
3899
- cors: options.cors,
3900
- useErrorStatus: options.useErrorStatus
3901
- };
3902
- }
3903
- //#endregion
3904
- export { EErrId_NiceAction as $, createServerHandshake as A, PeerLinkHandler as B, createAcceptorHandler as C, ESecurityLevel as D, EHandshakeMessageType as E, ActionLocalHandler as F, err_nice_external_client as G, ETransportStatus as H, createLocalHandler as I, ActionSchema as J, isActionPayload_Any_JsonObject as K, ActionRuntime as L, decodeHandshakeMessage as M, encodeHandshakeMessage as N, createClientHandshake as O, runtimeLinkId as P, isAction_Base_JsonObject as Q, ConnectorHandler as R, AcceptorHandler as S, decodeActionFrame as T, EErrId_NiceTransport as U, ETransportShape as V, err_nice_transport as W, actionSchema as X, EActionResponseMode as Y, isActionPayload_Result_JsonObject as Z, decodeExchangeReply as _, isExchangeAcceptorCarrier as a, EActionProgressType as at, Transport as b, createConnectionStateStore as c, RuntimeCoordinate as ct, acceptChannel as d, err_nice_action as et, acceptChannelConnections as f, ExchangeTransport as g, LinkTransport as h, serveChannel as i, EActionPayloadType as it, createStorageTofuVerifyKeyResolver as j, createInMemoryTofuVerifyKeyResolver as k, createActionFetchHandler as l, runtimeCoordinateToStringIds as lt, defineChannel as m, wsAcceptorCarrier as n, ActionPayload_Request as nt, createHibernatableWsServerAdapter as o, ActionPayload as ot, connectChannel as p, isActionPayload_Request_JsonObject as q, serveHost as r, ActionPayload_Result as rt, ConnectionStateStore as s, ActionBase as st, httpAcceptorCarrier as t, RunningAction as tt, ExchangeAcceptor as u, UNSET_RUNTIME_ENV_ID as ut, decodeExchangeRequest as v, createActionFrameCrypto as w, createSecureAcceptorHandler as x, encodeExchange as y, createConnectorHandler as z };
3718
+ export { ActionPayload_Request as $, encodeHandshakeMessage as A, EErrId_NiceTransport as B, EHandshakeMessageType as C, createServerHandshake as D, createInMemoryTofuVerifyKeyResolver as E, ConnectorHandler as F, ActionSchema as G, err_nice_external_client as H, createConnectorHandler as I, isActionPayload_Result_JsonObject as J, EActionResponseMode as K, PeerLinkHandler as L, ActionLocalHandler as M, createLocalHandler as N, createStorageTofuVerifyKeyResolver as O, ActionRuntime as P, RunningAction as Q, ETransportShape as R, decodeActionFrame as S, createClientHandshake as T, isActionPayload_Any_JsonObject as U, err_nice_transport as V, isActionPayload_Request_JsonObject as W, EErrId_NiceAction as X, isAction_Base_JsonObject as Y, err_nice_action as Z, extractWirePayload as _, ExchangeAcceptor as a, RuntimeCoordinate as at, createAcceptorHandler as b, decodeExchangeReply as c, Transport as d, ActionPayload_Result as et, createBinaryWireSessionFactory as f, buildActionRouteDictionary as g, assembleWireJson as h, createActionFetchHandler as i, ActionBase as it, runtimeLinkId as j, decodeHandshakeMessage as k, decodeExchangeRequest as l, ReversePayloadType as m, ConnectionStateStore as n, EActionProgressType as nt, LinkTransport as o, runtimeCoordinateToStringIds as ot, PayloadTypeToInt as p, actionSchema as q, createConnectionStateStore as r, ActionPayload as rt, ExchangeTransport as s, createHibernatableWsServerAdapter as t, EActionPayloadType as tt, encodeExchange as u, createSecureAcceptorHandler as v, ESecurityLevel as w, createActionFrameCrypto as x, AcceptorHandler as y, ETransportStatus as z };
3905
3719
 
3906
- //# sourceMappingURL=httpAcceptorCarrier-DJVxzDVd.mjs.map
3720
+ //# sourceMappingURL=createHibernatableWsServerAdapter-BkjESd01.mjs.map