@nice-code/action 0.15.0 → 0.17.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 (44) hide show
  1. package/README.md +582 -582
  2. package/build/{ActionDevtoolsCore-9PsnscvK.mjs → ActionDevtoolsCore-BcItqP-C.mjs} +7 -7
  3. package/build/ActionDevtoolsCore-BcItqP-C.mjs.map +1 -0
  4. package/build/{ActionDevtoolsCore-CCRLYASa.d.cts → ActionDevtoolsCore-C5XrQI1K.d.mts} +3 -3
  5. package/build/{ActionDevtoolsCore-DtgXwPBZ.cjs → ActionDevtoolsCore-Cb_QR44N.cjs} +7 -7
  6. package/build/ActionDevtoolsCore-Cb_QR44N.cjs.map +1 -0
  7. package/build/{ActionDevtoolsCore-CYGD2o6C.d.mts → ActionDevtoolsCore-Dd1qJAwK.d.cts} +3 -3
  8. package/build/{ActionPayload.types-BN-rXFBK.d.cts → ActionPayload.types-BchJrBIX.d.mts} +1966 -1139
  9. package/build/{ActionPayload.types-D28ELKXC.d.mts → ActionPayload.types-snDlSIF-.d.cts} +1966 -1139
  10. package/build/devtools/browser/index.cjs +5 -5
  11. package/build/devtools/browser/index.cjs.map +1 -1
  12. package/build/devtools/browser/index.d.cts +1 -1
  13. package/build/devtools/browser/index.d.mts +1 -1
  14. package/build/devtools/browser/index.mjs +5 -5
  15. package/build/devtools/browser/index.mjs.map +1 -1
  16. package/build/devtools/server/index.cjs +1 -1
  17. package/build/devtools/server/index.cjs.map +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/devtools/server/index.mjs +1 -1
  21. package/build/devtools/server/index.mjs.map +1 -1
  22. package/build/index.cjs +2667 -1752
  23. package/build/index.cjs.map +1 -1
  24. package/build/index.d.cts +2 -2
  25. package/build/index.d.mts +2 -2
  26. package/build/index.mjs +2635 -1738
  27. package/build/index.mjs.map +1 -1
  28. package/build/platform/cloudflare/index.cjs +56 -0
  29. package/build/platform/cloudflare/index.cjs.map +1 -0
  30. package/build/platform/cloudflare/index.d.cts +67 -0
  31. package/build/platform/cloudflare/index.d.mts +67 -0
  32. package/build/platform/cloudflare/index.mjs +54 -0
  33. package/build/platform/cloudflare/index.mjs.map +1 -0
  34. package/build/react-query/index.cjs.map +1 -1
  35. package/build/react-query/index.d.cts +1 -1
  36. package/build/react-query/index.d.mts +1 -1
  37. package/build/react-query/index.mjs.map +1 -1
  38. package/build/wsAcceptorCarrier-CXGlQU_f.mjs +80 -0
  39. package/build/wsAcceptorCarrier-CXGlQU_f.mjs.map +1 -0
  40. package/build/wsAcceptorCarrier-DHRbsY1X.cjs +103 -0
  41. package/build/wsAcceptorCarrier-DHRbsY1X.cjs.map +1 -0
  42. package/package.json +15 -4
  43. package/build/ActionDevtoolsCore-9PsnscvK.mjs.map +0 -1
  44. package/build/ActionDevtoolsCore-DtgXwPBZ.cjs.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { INiceErrorDomainProps, InferNiceError, NiceError, NiceErrorDomain, err_cast_not_nice } from "@nice-code/error";
2
- import { StandardSchemaV1 } from "@standard-schema/spec";
3
2
  import { RuntimeName } from "std-env";
4
3
  import { ClientCryptoKeyLink, StorageAdapter, TSerializedCryptoKeyData_Ed25519_Raw, TSerializedCryptoKeyData_X25519_Raw, TTypeAndId } from "@nice-code/util";
5
4
  import * as v from "valibot";
5
+ import { StandardSchemaV1 } from "@standard-schema/spec";
6
6
 
7
7
  //#region src/ActionDefinition/Schema/ActionSchema.types.d.ts
8
8
  type TTransportedValue<RAW_VAL, SERDE_VAL> = [RAW_VAL] | [RAW_VAL, SERDE_VAL];
@@ -35,12 +35,45 @@ type TInferErrorFromDeclaration<D> = D extends IActionErrorDeclaration<infer DEF
35
35
  type TInferDeclaredErrors<DECLS extends readonly IActionErrorDeclaration[]> = TInferErrorFromDeclaration<DECLS[number]>;
36
36
  //#endregion
37
37
  //#region src/ActionDefinition/Schema/ActionSchema.d.ts
38
+ /**
39
+ * What a sender should expect back from an action — declared on its schema so both ends agree without
40
+ * any wire flag (each derives the mode from the shared `domain:id`).
41
+ *
42
+ * - `payload` — a typed output (the action has `.output(...)`); the sender awaits it.
43
+ * - `ack` — an empty success confirming receipt (no output); the sender may await it to know the
44
+ * receiver handled it (or to surface an error). This is the default for an action with no output.
45
+ * - `none` — fire-and-forget: the receiver sends no reply and the sender doesn't wait. The sender's
46
+ * running action completes as soon as the frame is on the wire (no pending reply, no timeout).
47
+ */
48
+ declare enum EActionResponseMode {
49
+ payload = "payload",
50
+ ack = "ack",
51
+ none = "none"
52
+ }
38
53
  declare class ActionSchema<INPUT extends TTransportedValue<any, any> = never, OUTPUT extends TTransportedValue<any, any> = never, ERRORS extends readonly IActionErrorDeclaration<any, any>[] = readonly []> {
39
54
  private _errorDeclarations;
40
55
  private inputOptions;
41
56
  private outputOptions;
57
+ private _responseMode;
42
58
  get inputSchema(): StandardSchemaV1 | undefined;
43
59
  get outputSchema(): StandardSchemaV1 | undefined;
60
+ /**
61
+ * The response contract for this action. Defaults are inferred — `payload` when an output schema is
62
+ * declared, otherwise `ack` — and made explicit by {@link ack} / {@link fireAndForget}.
63
+ */
64
+ get responseMode(): EActionResponseMode;
65
+ /**
66
+ * Mark this action as expecting only an acknowledgment (an empty success). Mostly for clarity — an
67
+ * output-less action already acks by default — but it documents intent and reads as the deliberate
68
+ * counterpart to {@link fireAndForget}.
69
+ */
70
+ ack(): this;
71
+ /**
72
+ * Mark this action as fire-and-forget: the receiver sends no reply, and the sender's running action
73
+ * completes the moment the frame is sent (no awaited reply, no timeout). Ideal for high-frequency
74
+ * server→client pushes (presence, ticks) where an ack would only add wire chatter.
75
+ */
76
+ fireAndForget(): this;
44
77
  /**
45
78
  * Declare the input schema (JSON-native or with explicit SERDE type param).
46
79
  * For non-JSON-native inputs, prefer the 3-argument form below to avoid
@@ -501,20 +534,20 @@ declare class ActionRouter<DATA> {
501
534
  //#endregion
502
535
  //#region src/ActionRuntime/Handler/ActionHandler.types.d.ts
503
536
  declare enum EActionHandlerType {
504
- external = "external",
537
+ peer = "peer",
505
538
  local = "local"
506
539
  }
507
540
  interface IActionHandler_Json<T extends EActionHandlerType> {
508
541
  type: T;
509
542
  }
510
- interface IActionHandler_ExternalClient_Json extends IActionHandler_Json<EActionHandlerType.external> {
543
+ interface IActionHandler_Peer_Json extends IActionHandler_Json<EActionHandlerType.peer> {
511
544
  client: IRuntimeCoordinate;
512
545
  }
513
546
  interface IActionHandler_Local_Json extends IActionHandler_Json<EActionHandlerType.local> {}
514
- type TActionHandler_Json = IActionHandler_Local_Json | IActionHandler_ExternalClient_Json;
547
+ type TActionHandler_Json = IActionHandler_Local_Json | IActionHandler_Peer_Json;
515
548
  interface IHandleActionOptions {
516
549
  timeout?: number;
517
- targetExternalClient?: RuntimeCoordinate;
550
+ targetPeer?: RuntimeCoordinate;
518
551
  targetLocalRuntime?: ActionRuntime;
519
552
  }
520
553
  interface IExecuteActionOptions<DOM extends IActionDomain = IActionDomain, ID extends keyof DOM["actionSchema"] & string = keyof DOM["actionSchema"] & string> extends IHandleActionOptions {
@@ -536,13 +569,13 @@ interface IActionHandler_Local extends IActionHandler_Base<EActionHandlerType.lo
536
569
  }
537
570
  /**
538
571
  *
539
- * EXTERNAL CLIENT ACTION HANDLER
572
+ * PEER-LINK ACTION HANDLER
540
573
  *
541
574
  */
542
- interface IHandleActionOptions_External extends IHandleActionOptions {}
543
- interface IActionHandler_ExternalClient extends IActionHandler_Base<EActionHandlerType.external> {
544
- externalClient: RuntimeCoordinate;
545
- handleActionRequest: <DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: ActionPayload_Request<DOM, ID>, config?: IHandleActionOptions_External) => Promise<RunningAction<DOM, ID>>;
575
+ interface IHandleActionOptions_Peer extends IHandleActionOptions {}
576
+ interface IActionHandler_Peer extends IActionHandler_Base<EActionHandlerType.peer> {
577
+ peerClient: RuntimeCoordinate;
578
+ handleActionRequest: <DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: ActionPayload_Request<DOM, ID>, config?: IHandleActionOptions_Peer) => Promise<RunningAction<DOM, ID>>;
546
579
  _setIncomingActionDataListener(listener: (json: TActionPayload_Any_JsonObject<any>) => void): void;
547
580
  }
548
581
  /**
@@ -550,7 +583,7 @@ interface IActionHandler_ExternalClient extends IActionHandler_Base<EActionHandl
550
583
  * COMBINED
551
584
  *
552
585
  */
553
- type TActionHandler = IActionHandler_Local | IActionHandler_ExternalClient;
586
+ type TActionHandler = IActionHandler_Local | IActionHandler_Peer;
554
587
  //#endregion
555
588
  //#region src/ActionDefinition/Action/Payload/ActionPayload_Request.d.ts
556
589
  declare class ActionPayload_Request<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string = keyof DOM["actionSchema"] & string> extends ActionPayload<EActionPayloadType.request, DOM, ID> {
@@ -624,229 +657,6 @@ declare abstract class ActionHandler<T extends EActionHandlerType> implements IA
624
657
  abstract toHandlerRouteItem(...args: any[]): IActionRouteItemHandler;
625
658
  }
626
659
  //#endregion
627
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/TransportConnection.d.ts
628
- /**
629
- * Live, per-handler transport runtime built from a reusable {@link Transport} definition. Holds the
630
- * connection-scoped state (ordinal, initialized config, sockets / abort sets) that must not be shared
631
- * across handlers. Construct these via `definition._createConnection(...)`, never directly.
632
- */
633
- declare abstract class TransportConnection<T extends ETransportType = ETransportType, RP extends ITransportRouteActionParams = ITransportRouteActionParams, RD extends IActionTransportReadyData_Base = IActionTransportReadyData_Base, I extends IActionTransportInitialized<RP, RD> = IActionTransportInitialized<RP, RD>, DEF extends IActionTransportDef<T, I> = IActionTransportDef<T, I>> {
634
- readonly def: DEF;
635
- readonly transOrd: number;
636
- readonly type: T;
637
- readonly initialized: I;
638
- /** Backref to the public definition that created this connection (used for devtools route info). */
639
- definition?: Transport<T>;
640
- constructor(def: DEF);
641
- /**
642
- * Devtools route info for an action routed through this live connection. Defaults to the stateless
643
- * {@link definition}'s info; connections override to enrich it from live state (e.g. the actual
644
- * resolved socket URL) when the definition couldn't resolve it on its own.
645
- */
646
- getRouteInfo(input: RP): ITransportRouteInfo | undefined;
647
- protected abstract _finalizeTransportMethods(inputs: RD): IActionTransportReadyData_Methods;
648
- protected _getCacheKey(input: RP): string | null;
649
- getCacheKey(input: RP): string | null;
650
- getTransport(input: RP): TTransportStatusInfo<IActionTransportReadyData_Methods>;
651
- protected _processTransportStatus(input: RP): TTransportStatusInfo<IActionTransportReadyData_Methods>;
652
- }
653
- //#endregion
654
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/Transport.types.d.ts
655
- declare enum ETransportType {
656
- ws = "ws",
657
- http = "http",
658
- custom = "custom"
659
- }
660
- /**
661
- * Serializable, display-only description of how an action was routed through a transport. Stored on
662
- * the action's route items and shown in the devtools external-handler chips. Keep this free of
663
- * sensitive data (e.g. auth headers) — route items travel over the wire.
664
- */
665
- interface ITransportRouteInfo {
666
- type: ETransportType;
667
- /** Short label for chips, e.g. "POST /resolve_action" or "ws host/resolve_action/ws". */
668
- summary?: string;
669
- url?: string;
670
- method?: string;
671
- detail?: Record<string, string | number | boolean>;
672
- }
673
- interface IUpdateActionRunConfig_Output {
674
- timeout?: number;
675
- }
676
- type TUpdateActionRunConfig = (input: ITransportRouteActionParams & {
677
- timeout: number;
678
- }) => IUpdateActionRunConfig_Output;
679
- interface IActionTransportReadyData_Base {
680
- updateRunConfig?: TUpdateActionRunConfig;
681
- }
682
- /**
683
- *
684
- * TRANSPORT READINESS RESPONSE
685
- *
686
- */
687
- declare enum ETransportStatus {
688
- uninitialized = "uninitialized",
689
- unsupported = "unsupported",
690
- initializing = "initializing",
691
- ready = "ready",
692
- failed = "failed"
693
- }
694
- interface ITransportStatusInfo_Base<S extends ETransportStatus> {
695
- status: S;
696
- }
697
- interface ITransportStatusInfo_Failed extends ITransportStatusInfo_Base<ETransportStatus.failed> {
698
- error: NiceError;
699
- timeFailed: number;
700
- }
701
- interface ITransportStatusInfo_Unsupported extends ITransportStatusInfo_Base<ETransportStatus.unsupported> {}
702
- interface ITransportStatusInfo_Ready<READY extends IActionTransportReadyData_Base> extends ITransportStatusInfo_Base<ETransportStatus.ready> {
703
- readyData: READY;
704
- }
705
- type TTransportInitializationFinishedInfo<READY extends IActionTransportReadyData_Base> = ITransportStatusInfo_Ready<READY> | ITransportStatusInfo_Failed | ITransportStatusInfo_Unsupported;
706
- interface ITransportStatusInfo_Initializing<READY extends IActionTransportReadyData_Base> extends ITransportStatusInfo_Base<ETransportStatus.initializing> {
707
- timeStarted: number;
708
- initializationPromise: Promise<TTransportInitializationFinishedInfo<READY>>;
709
- }
710
- type TTransportStatusInfo<READY extends IActionTransportReadyData_Base> = ITransportStatusInfo_Base<ETransportStatus.uninitialized> | ITransportStatusInfo_Unsupported | ITransportStatusInfo_Initializing<READY> | ITransportStatusInfo_Ready<READY> | ITransportStatusInfo_Failed;
711
- type TTransportStatusInfo_GetTransport_Output<READY extends IActionTransportReadyData_Base> = ITransportStatusInfo_Base<ETransportStatus.uninitialized> | ITransportStatusInfo_Unsupported | Omit<ITransportStatusInfo_Initializing<READY>, "timeStarted"> | ITransportStatusInfo_Ready<READY> | Omit<ITransportStatusInfo_Failed, "timeFailed">;
712
- /**
713
- *
714
- * TRANSPORT ROUTING
715
- *
716
- */
717
- interface ITransportRouteClientParams {
718
- localClient: RuntimeCoordinate;
719
- externalClient: RuntimeCoordinate;
720
- }
721
- interface ITransportRouteActionParams extends ITransportRouteClientParams {
722
- action: TActionPayload_Any_Instance<any, any>;
723
- }
724
- interface ITransportMethod_SendActionData_Input extends ITransportRouteActionParams {
725
- runningAction: RunningAction<any, any>;
726
- timeout: number;
727
- }
728
- interface ITransportDispatchAction<P> extends ITransportMethod_SendActionData_Input {
729
- params: P;
730
- }
731
- type TSendActionDataMethod = (input: ITransportMethod_SendActionData_Input) => void;
732
- type TSendReturnDataMethod = (payload: TActionPayload_Any_Instance<any, any>,
733
- /**
734
- * The local/external client pair this payload is being returned over. Bidirectional transports use
735
- * it to build the full route params their outgoing formatter expects (e.g. binary packing). Optional
736
- * so existing return-data implementations keep type-checking.
737
- */
738
-
739
- clients?: ITransportRouteClientParams) => void;
740
- interface IActionTransportReadyData_Methods extends IActionTransportReadyData_Base {
741
- sendActionData: TSendActionDataMethod;
742
- /**
743
- * Optional — implement on bidirectional transports (WebSocket, Custom) to enable return-path
744
- * routing. When present, the runtime uses this to dispatch results and progress payloads directly
745
- * back to `originClient` without going through the original request transport.
746
- */
747
- sendReturnData?: TSendReturnDataMethod;
748
- addOnDisconnectListener?: (callback: () => void) => void;
749
- /**
750
- * Optional — implement on transports holding a long-lived connection (WebSocket, Custom) to close it
751
- * deliberately. Called by `ActionExternalClientHandler.clearTransportCache()` so a teardown actually
752
- * releases the underlying socket instead of leaving it open until GC.
753
- */
754
- disconnect?: () => void;
755
- }
756
- interface IActionTransportReady {
757
- methods: IActionTransportReadyData_Methods;
758
- transport: TransportConnection;
759
- }
760
- type TTransportCache = Map<string, IActionTransportReady | Promise<IActionTransportReady>>;
761
- type TOnResolveIncomingRequest = (request: ActionPayload_Request<any>) => void;
762
- type TOnResolveIncomingRequestJson = (request: IActionPayload_Request_JsonObject<any>) => void;
763
- type TOnResolveIncomingResponse = (response: ActionPayload_Result<any>) => void;
764
- type TOnResolveIncomingResponseJson = (response: IActionPayload_Result_JsonObject<any>) => void;
765
- type TOnResolveAnyIncomingActionData = (actionData: ActionPayload_Request<any> | ActionPayload_Result<any>) => void;
766
- type TOnResolveAnyIncomingActionData_Json = (actionData: TActionPayload_Any_JsonObject<any>) => void;
767
- interface IActionTransportResolvers {
768
- onIncomingActionDataJson: TOnResolveAnyIncomingActionData_Json;
769
- }
770
- type TGetTransportFn<IN extends ITransportRouteActionParams, READY extends IActionTransportReadyData_Base> = (input: IN) => TTransportStatusInfo_GetTransport_Output<READY>;
771
- interface IActionTransportInitialized<IN extends ITransportRouteActionParams, READY extends IActionTransportReadyData_Base> {
772
- getTransportCacheKey?: (input: IN) => string[];
773
- getTransport: TGetTransportFn<IN, READY>;
774
- }
775
- interface IActionTransportDef<TYPE extends ETransportType, INIT extends IActionTransportInitialized<any, any>> {
776
- type: TYPE;
777
- initialize: () => INIT;
778
- }
779
- //#endregion
780
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/Transport.d.ts
781
- /**
782
- * Context handed to a {@link Transport} definition when a handler builds a live connection from it.
783
- * Only bidirectional transports (WebSocket / Custom) make use of `resolvers`.
784
- */
785
- interface ITransportConnectionContext {
786
- resolvers?: IActionTransportResolvers;
787
- }
788
- /**
789
- * Reusable transport definition. Devs construct these (`HttpTransport.create({ createRequest })`,
790
- * `WebSocketTransport.create({ createWebSocket })`, …) and pass them to an
791
- * `ActionExternalClientHandler`. A single
792
- * definition can be shared across multiple handlers — each handler builds its own live
793
- * {@link TransportConnection} via {@link TransportConnection._createConnection}.
794
- */
795
- declare abstract class Transport<T extends ETransportType = ETransportType> {
796
- abstract readonly type: T;
797
- /** Internal: build a fresh, per-handler live connection from this definition. */
798
- abstract _createConnection(ctx: ITransportConnectionContext): TransportConnection<T>;
799
- /**
800
- * Resolve human-readable info about how a specific action would be routed through this transport
801
- * (e.g. the request URL/method, or the WebSocket endpoint). Surfaced in the action devtools.
802
- */
803
- abstract getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
804
- }
805
- //#endregion
806
- //#region src/ActionRuntime/Handler/ExternalClient/ActionExternalClientHandler.types.d.ts
807
- interface IActionExternalClientRequestHandlerConfig {
808
- defaultTimeout?: number;
809
- runtimeCoordinate: RuntimeCoordinate;
810
- transports: Transport[];
811
- }
812
- //#endregion
813
- //#region src/ActionRuntime/Handler/ExternalClient/ActionExternalClientHandler.d.ts
814
- declare class ActionExternalClientHandler extends ActionHandler<EActionHandlerType.external> implements IActionHandler_ExternalClient {
815
- readonly externalClient: RuntimeCoordinate;
816
- readonly handlerType = EActionHandlerType.external;
817
- readonly cuid: string;
818
- private _defaultTimeout;
819
- private _transportCache;
820
- private transportManager;
821
- private _incomingActionDataListeners;
822
- readonly actionRouter: ActionRouter<true>;
823
- constructor({
824
- runtimeCoordinate: externalClientSpecifier,
825
- transports,
826
- defaultTimeout
827
- }: IActionExternalClientRequestHandlerConfig);
828
- forDomain<FOR_DOM extends IActionDomain>(domain: ActionDomain<FOR_DOM>): this;
829
- forAction<ACT_DOM extends IActionDomain, ID extends keyof ACT_DOM["actionSchema"] & string>(action: ActionCore<ACT_DOM, ID>): this;
830
- forActionIds<ACT_DOM extends IActionDomain, IDS extends ReadonlyArray<keyof ACT_DOM["actionSchema"] & string>>(domain: ActionDomain<ACT_DOM>, ids: IDS): this;
831
- _setIncomingActionDataListener(listener: (json: TActionPayload_Any_JsonObject<any, any>) => void): void;
832
- handleActionRequest<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: ActionPayload_Request<DOM, ID>, config?: IHandleActionOptions): Promise<RunningAction<DOM, ID>>;
833
- private _dispatchWhenTransportReady;
834
- /**
835
- * Dispatch a result or progress payload directly back to the external client via the best
836
- * available bidirectional transport (WebSocket / Custom). Used for return-path routing when the
837
- * local runtime recognises that it has a direct channel to the action's originClient.
838
- *
839
- * Returns `true` if the payload was sent, `false` if no suitable transport was available.
840
- */
841
- sendReturnPayload(payload: TActionPayload_Any_Instance<any, any>, config: {
842
- targetLocalRuntime: ActionRuntime;
843
- }): Promise<boolean>;
844
- toJsonObject(): IActionHandler_ExternalClient_Json;
845
- toHandlerRouteItem(transport: TransportConnection, input: ITransportRouteActionParams): IActionRouteItemHandler;
846
- clearTransportCache(): void;
847
- }
848
- declare const createExternalClientHandler: (config: IActionExternalClientRequestHandlerConfig) => ActionExternalClientHandler;
849
- //#endregion
850
660
  //#region src/utils/typescript/MaybePromise.d.ts
851
661
  type MaybePromise<T> = T | Promise<T>;
852
662
  //#endregion
@@ -898,6 +708,62 @@ declare class ActionLocalHandler extends ActionHandler<EActionHandlerType.local>
898
708
  }
899
709
  declare const createLocalHandler: () => ActionLocalHandler;
900
710
  //#endregion
711
+ //#region src/ActionRuntime/Handler/PeerLink/PeerLinkHandler.d.ts
712
+ /**
713
+ * Shared base for every handler that routes a domain set to/from *another runtime* (a "peer") — the
714
+ * unified peer-link concept. Both specializations extend this as siblings, differing only in *who
715
+ * establishes the connection*, which is a transport trait, not a routing one:
716
+ *
717
+ * - {@link ConnectorHandler} — **dial-out**: this runtime opens connection(s) to one peer
718
+ * over a transport stack (with caching + fallback). The classic "client → backend" link.
719
+ * - {@link AcceptorHandler} — **accept-in**: connections are accepted from many peers and fed in
720
+ * via `receive()`; it keeps a per-connection registry and can push to any of them.
721
+ *
722
+ * To the runtime there is no "client" vs "server" — both are peer-link handlers (`handlerType =
723
+ * external`) keyed to a peer coordinate, chosen by the return-path dispatch via {@link sendReturnPayload}.
724
+ */
725
+ declare abstract class PeerLinkHandler extends ActionHandler<EActionHandlerType.peer> implements IActionHandler_Peer {
726
+ /** The peer runtime this handler links to (an env-only coordinate for an accept-in handler). */
727
+ readonly peerClient: RuntimeCoordinate;
728
+ readonly handlerType = EActionHandlerType.peer;
729
+ /**
730
+ * Whether this link can deliver an *unsolicited* frame to the peer (a result/progress pushed back on
731
+ * the return path, or a `broadcast`). A duplex carrier (WebSocket/WebRTC/…) can; an exchange-only
732
+ * carrier (HTTP) cannot — its reply must ride the response to its own request. The runtime's
733
+ * return-path dispatch ({@link ActionRuntime.getReturnHandlerForOrigin}) skips handlers that can't
734
+ * push, so an exchange-only handler is never asked to deliver one.
735
+ */
736
+ abstract readonly canPush: boolean;
737
+ readonly actionRouter: ActionRouter<true>;
738
+ /** Listeners installed by the runtime (`resolveIncomingActionPayload`) for inbound peer frames. */
739
+ private readonly _incomingActionDataListeners;
740
+ constructor(peerCoordinate: RuntimeCoordinate);
741
+ forDomain<FOR_DOM extends IActionDomain>(domain: ActionDomain<FOR_DOM>): this;
742
+ forAction<ACT_DOM extends IActionDomain, ID extends keyof ACT_DOM["actionSchema"] & string>(action: ActionCore<ACT_DOM, ID>): this;
743
+ forActionIds<ACT_DOM extends IActionDomain, IDS extends ReadonlyArray<keyof ACT_DOM["actionSchema"] & string>>(domain: ActionDomain<ACT_DOM>, ids: IDS): this;
744
+ _setIncomingActionDataListener(listener: (json: TActionPayload_Any_JsonObject<any, any>) => void): void;
745
+ /** Hand a decoded inbound frame to the runtime (called by each specialization's receive path). */
746
+ protected _emitIncoming(json: TActionPayload_Any_JsonObject<any, any>): void;
747
+ /**
748
+ * Dispatch a result/progress payload back to the action's origin peer over this link. The runtime's
749
+ * return-path dispatch calls it on whichever peer-link handler best reaches `originClient`. Returns
750
+ * `true` if it was sent, `false` if no channel was available.
751
+ */
752
+ abstract sendReturnPayload(payload: TActionPayload_Any_Instance<any, any>, config: {
753
+ targetLocalRuntime: ActionRuntime;
754
+ }): Promise<boolean>;
755
+ /**
756
+ * Whether this handler currently holds a *live* connection bound to `origin`. The runtime's return-path
757
+ * dispatch ({@link ActionRuntime.getReturnHandlerForOrigin}) prefers a handler that owns the origin's
758
+ * connection over a mere coordinate match, so with several duplex acceptors a result/push routes back
759
+ * over the carrier the client connected on. Defaults to `false`; an acceptor overrides it from its
760
+ * connection registry.
761
+ */
762
+ ownsLiveConnectionFor(_origin: RuntimeCoordinate): boolean;
763
+ /** Release any long-lived connections this handler owns (a teardown). No-op by default. */
764
+ clearTransportCache(): void;
765
+ }
766
+ //#endregion
901
767
  //#region src/ActionRuntime/ActionRuntime.types.d.ts
902
768
  interface IRuntimeMeta {
903
769
  assumed: boolean;
@@ -906,34 +772,486 @@ interface IRuntimeMeta {
906
772
  interface IActionRuntimeManagerContext {
907
773
  domain?: string;
908
774
  }
909
- type TActionRuntimeHandler = ActionLocalHandler | ActionExternalClientHandler;
775
+ type TActionRuntimeHandler = ActionLocalHandler | PeerLinkHandler;
910
776
  //#endregion
911
- //#region src/ActionRuntime/ActionRuntime.d.ts
912
- declare class ActionRuntime {
913
- private _coordinate;
914
- readonly timeCreated: number;
915
- readonly runtimeInfo: IRuntimeMeta;
916
- private readonly actionRouter;
917
- private readonly _pendingRunningActions;
918
- private readonly _registeredExternalHandlers;
919
- private _applied;
920
- static getDefault(): ActionRuntime;
921
- constructor(coordinate: RuntimeCoordinate);
922
- get coordinate(): RuntimeCoordinate;
923
- specifyRuntimeCoordinate(specifics: IRuntimeCoordinateSpecifics & {
924
- envId?: string;
925
- }): void;
926
- registerRunningAction(ra: RunningAction<any, any>): void;
927
- resolveIncomingActionPayload(json: TActionPayload_Any_JsonObject<any, any>): void;
928
- /**
929
- * Handle an incoming action wire (e.g. from a transport layer), route it to
930
- * the correct handler, and return the response. The most specific handler
931
- * match is chosen (action-ID-specific beats domain-wildcard).
932
- */
933
- handleActionPayloadWire<D extends IActionDomain, ID extends keyof D["actionSchema"] & string>(wire: TActionPayload_Any_JsonObject<D, ID>): Promise<RunningAction<D, ID>>;
934
- handleActionPayloadWire(wire: unknown): Promise<RunningAction<any, any>>;
935
- handleActionPayload<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: TActionPayload_Any_Instance<DOM, ID>, options?: Omit<IHandleActionOptions, "targetLocalRuntime">): Promise<RunningAction<DOM, ID>>;
936
- /**
777
+ //#region src/ActionRuntime/Transport/crypto/actionHandshake.d.ts
778
+ /** How much the channel protects after the handshake — chosen by the consumer (perf vs security). */
779
+ declare enum ESecurityLevel {
780
+ /** No handshake; identity is self-asserted (fastest, dev / trusted networks). */
781
+ none = "none",
782
+ /** Handshake authenticates identity (sign/verify + key pin); frames stay plaintext over TLS. */
783
+ authenticated = "authenticated",
784
+ /** Authenticated handshake + every frame AES-GCM encrypted with the derived shared key. */
785
+ encrypted = "encrypted"
786
+ }
787
+ declare enum EHandshakeMessageType {
788
+ hello = "hello",
789
+ welcome = "welcome",
790
+ prove = "prove",
791
+ accept = "accept",
792
+ reject = "reject"
793
+ }
794
+ declare const vHsHello: v.ObjectSchema<{
795
+ readonly t: v.LiteralSchema<EHandshakeMessageType.hello, undefined>;
796
+ readonly protocol: v.StringSchema<undefined>;
797
+ readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
798
+ readonly dictionaryVersion: v.StringSchema<undefined>;
799
+ readonly client: v.ObjectSchema<{
800
+ readonly envId: v.StringSchema<undefined>;
801
+ readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
802
+ readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
803
+ }, undefined>;
804
+ readonly clientNonce: v.StringSchema<undefined>;
805
+ readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
806
+ readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
807
+ }, undefined>;
808
+ declare const vHsWelcome: v.ObjectSchema<{
809
+ readonly t: v.LiteralSchema<EHandshakeMessageType.welcome, undefined>;
810
+ readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
811
+ readonly dictionaryVersion: v.StringSchema<undefined>;
812
+ readonly server: v.ObjectSchema<{
813
+ readonly envId: v.StringSchema<undefined>;
814
+ readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
815
+ readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
816
+ }, undefined>;
817
+ readonly serverNonce: v.StringSchema<undefined>;
818
+ readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
819
+ readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
820
+ }, undefined>;
821
+ declare const vHsProve: v.ObjectSchema<{
822
+ readonly t: v.LiteralSchema<EHandshakeMessageType.prove, undefined>;
823
+ readonly signatureBase64: v.StringSchema<undefined>;
824
+ }, undefined>;
825
+ declare const vHsAccept: v.ObjectSchema<{
826
+ readonly t: v.LiteralSchema<EHandshakeMessageType.accept, undefined>;
827
+ readonly signatureBase64: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
828
+ }, undefined>;
829
+ declare const vHsReject: v.ObjectSchema<{
830
+ readonly t: v.LiteralSchema<EHandshakeMessageType.reject, undefined>;
831
+ readonly reason: v.StringSchema<undefined>;
832
+ }, undefined>;
833
+ declare const vHandshakeMessage: v.VariantSchema<"t", [v.ObjectSchema<{
834
+ readonly t: v.LiteralSchema<EHandshakeMessageType.hello, undefined>;
835
+ readonly protocol: v.StringSchema<undefined>;
836
+ readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
837
+ readonly dictionaryVersion: v.StringSchema<undefined>;
838
+ readonly client: v.ObjectSchema<{
839
+ readonly envId: v.StringSchema<undefined>;
840
+ readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
841
+ readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
842
+ }, undefined>;
843
+ readonly clientNonce: v.StringSchema<undefined>;
844
+ readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
845
+ readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
846
+ }, undefined>, v.ObjectSchema<{
847
+ readonly t: v.LiteralSchema<EHandshakeMessageType.welcome, undefined>;
848
+ readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
849
+ readonly dictionaryVersion: v.StringSchema<undefined>;
850
+ readonly server: v.ObjectSchema<{
851
+ readonly envId: v.StringSchema<undefined>;
852
+ readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
853
+ readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
854
+ }, undefined>;
855
+ readonly serverNonce: v.StringSchema<undefined>;
856
+ readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
857
+ readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
858
+ }, undefined>, v.ObjectSchema<{
859
+ readonly t: v.LiteralSchema<EHandshakeMessageType.prove, undefined>;
860
+ readonly signatureBase64: v.StringSchema<undefined>;
861
+ }, undefined>, v.ObjectSchema<{
862
+ readonly t: v.LiteralSchema<EHandshakeMessageType.accept, undefined>;
863
+ readonly signatureBase64: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
864
+ }, undefined>, v.ObjectSchema<{
865
+ readonly t: v.LiteralSchema<EHandshakeMessageType.reject, undefined>;
866
+ readonly reason: v.StringSchema<undefined>;
867
+ }, undefined>], undefined>;
868
+ type THsHello = v.InferOutput<typeof vHsHello>;
869
+ type THsWelcome = v.InferOutput<typeof vHsWelcome>;
870
+ type THsProve = v.InferOutput<typeof vHsProve>;
871
+ type THsAccept = v.InferOutput<typeof vHsAccept>;
872
+ type THsReject = v.InferOutput<typeof vHsReject>;
873
+ type THandshakeMessage = v.InferOutput<typeof vHandshakeMessage>;
874
+ /** Serialize a handshake message for the wire (handshake frames are JSON — they aren't the hot path). */
875
+ declare function encodeHandshakeMessage(message: THandshakeMessage): string;
876
+ /** Parse + structurally validate an incoming handshake frame; `undefined` if it isn't one. */
877
+ declare function decodeHandshakeMessage(raw: string): THandshakeMessage | undefined;
878
+ /** Stable link id for a runtime coordinate — the key both the crypto link and the connection use. */
879
+ declare function runtimeLinkId(coordinate: IRuntimeCoordinate): TTypeAndId;
880
+ /**
881
+ * Everything needed to re-derive the shared AES-GCM key for an `encrypted` link after a restart —
882
+ * the remote's public keys + the HKDF salt/info used at handshake time. The local (server) key pair is
883
+ * recovered from its own persisted `ClientCryptoKeyLink` storage, so re-linking with this material
884
+ * yields the identical shared key without a fresh handshake.
885
+ */
886
+ interface IHandshakeEncryptionKeyMaterial {
887
+ verifyPublicKey: TSerializedCryptoKeyData_Ed25519_Raw;
888
+ exchangePublicKey: TSerializedCryptoKeyData_X25519_Raw;
889
+ saltString: string;
890
+ infoString: string;
891
+ bindVerifyKeysIntoDerivation: boolean;
892
+ }
893
+ /** Outcome of a completed handshake — what the transport/handler needs to wire the channel. */
894
+ interface IHandshakeResult {
895
+ /** The crypto-link id (and connection-registry key) for the authenticated remote. */
896
+ linkedClientId: TTypeAndId;
897
+ /** The remote's authenticated coordinate. */
898
+ remote: IRuntimeCoordinate;
899
+ securityLevel: ESecurityLevel;
900
+ /** For the `encrypted` level: material to restore the shared key after eviction (persist it). */
901
+ encryptionKeyMaterial?: IHandshakeEncryptionKeyMaterial;
902
+ }
903
+ interface IClientVerifyKeyResolveInput {
904
+ client: IRuntimeCoordinate;
905
+ verifyPublicKey: TSerializedCryptoKeyData_Ed25519_Raw;
906
+ }
907
+ interface IClientVerifyKeyResolver {
908
+ /**
909
+ * Decide whether a presented verify key is trusted for a client identity. The signature is already
910
+ * verified by the time this runs, so this is purely the identity-pinning decision. Swap in a
911
+ * persistent / pre-provisioned implementation without touching the protocol.
912
+ */
913
+ resolve(input: IClientVerifyKeyResolveInput): Promise<{
914
+ trusted: boolean;
915
+ reason?: string;
916
+ }>;
917
+ }
918
+ /**
919
+ * In-memory trust-on-first-use resolver: trusts (and pins) the first verify key seen for a client
920
+ * identity, then rejects a different key for that identity. The default; replace with a storage-backed
921
+ * resolver for cross-restart pinning (see Step 5).
922
+ */
923
+ declare function createInMemoryTofuVerifyKeyResolver(): IClientVerifyKeyResolver;
924
+ /**
925
+ * Storage-backed trust-on-first-use resolver: pins survive process restarts / Durable Object eviction
926
+ * (e.g. back it with `createDurableObjectStorageAdapter`). Same policy as the in-memory variant — trust
927
+ * + pin the first verify key per client identity, reject a different one thereafter.
928
+ */
929
+ declare function createStorageTofuVerifyKeyResolver(storageAdapter: StorageAdapter): IClientVerifyKeyResolver;
930
+ interface IClientHandshakeConfig {
931
+ link: ClientCryptoKeyLink;
932
+ localCoordinate: IRuntimeCoordinate;
933
+ dictionaryVersion: string;
934
+ securityLevel: ESecurityLevel;
935
+ }
936
+ declare function createClientHandshake(config: IClientHandshakeConfig): {
937
+ createHello(): Promise<THsHello>;
938
+ onWelcome(welcome: THsWelcome): Promise<THsProve>;
939
+ onAccept(accept: THsAccept): Promise<IHandshakeResult>;
940
+ };
941
+ interface IServerHandshakeConfig {
942
+ link: ClientCryptoKeyLink;
943
+ localCoordinate: IRuntimeCoordinate;
944
+ dictionaryVersion: string;
945
+ /**
946
+ * The level(s) this server accepts. A single level is strict (the client must match). An array is a
947
+ * negotiable allowed set — the server adopts whichever level the client requests, as long as it's in
948
+ * the set (lets one backend serve `authenticated` and `encrypted` clients at once). `none` in the set
949
+ * is handled by the transport/handler (a `none` client never reaches the handshake).
950
+ */
951
+ securityLevel: ESecurityLevel | readonly ESecurityLevel[];
952
+ /** Trust decision for a client's verify key. Defaults to in-memory TOFU. */
953
+ verifyKeyResolver?: IClientVerifyKeyResolver;
954
+ }
955
+ declare function createServerHandshake(config: IServerHandshakeConfig): {
956
+ onHello(hello: THsHello): Promise<THsWelcome | THsReject>;
957
+ onProve(prove: THsProve): Promise<THsAccept | THsReject>; /** The completed handshake result once `onProve` has accepted, else `undefined`. */
958
+ getResult(): IHandshakeResult | undefined;
959
+ };
960
+ //#endregion
961
+ //#region src/ActionRuntime/Transport/Transport.d.ts
962
+ /**
963
+ * Context handed to a {@link Transport} definition when a handler builds a live connection from it.
964
+ * Only bidirectional transports (WebSocket / Custom) make use of `resolvers`.
965
+ */
966
+ interface ITransportConnectionContext {
967
+ resolvers?: IActionTransportResolvers;
968
+ }
969
+ /**
970
+ * Reusable transport definition. Devs construct these (`secureTransport({ carrier: wsCarrier(url) })`,
971
+ * `plainTransport({ carrier: httpCarrier(...) })`, …) and pass them to a
972
+ * `ConnectorHandler`. A single
973
+ * definition can be shared across multiple handlers — each handler builds its own live
974
+ * {@link TransportConnection} via {@link TransportConnection._createConnection}.
975
+ */
976
+ declare abstract class Transport<T extends ETransportShape = ETransportShape> {
977
+ abstract readonly type: T;
978
+ /** Internal: build a fresh, per-handler live connection from this definition. */
979
+ abstract _createConnection(ctx: ITransportConnectionContext): TransportConnection<T>;
980
+ /**
981
+ * Resolve human-readable info about how a specific action would be routed through this transport
982
+ * (e.g. the request URL/method, or the WebSocket endpoint). Surfaced in the action devtools.
983
+ */
984
+ abstract getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
985
+ }
986
+ //#endregion
987
+ //#region src/ActionRuntime/Transport/TransportConnection.d.ts
988
+ /**
989
+ * Live, per-handler transport runtime built from a reusable {@link Transport} definition. Holds the
990
+ * connection-scoped state (ordinal, initialized config, sockets / abort sets) that must not be shared
991
+ * across handlers. Construct these via `definition._createConnection(...)`, never directly.
992
+ */
993
+ declare abstract class TransportConnection<T extends ETransportShape = ETransportShape, RP extends ITransportRouteActionParams = ITransportRouteActionParams, RD extends IActionTransportReadyData_Base = IActionTransportReadyData_Base, I extends IActionTransportInitialized<RP, RD> = IActionTransportInitialized<RP, RD>, DEF extends IActionTransportDef<T, I> = IActionTransportDef<T, I>> {
994
+ readonly def: DEF;
995
+ readonly transOrd: number;
996
+ readonly type: T;
997
+ readonly initialized: I;
998
+ /** Backref to the public definition that created this connection (used for devtools route info). */
999
+ definition?: Transport<T>;
1000
+ constructor(def: DEF);
1001
+ /**
1002
+ * Devtools route info for an action routed through this live connection. Defaults to the stateless
1003
+ * {@link definition}'s info; connections override to enrich it from live state (e.g. the actual
1004
+ * resolved socket URL) when the definition couldn't resolve it on its own.
1005
+ */
1006
+ getRouteInfo(input: RP): ITransportRouteInfo | undefined;
1007
+ /**
1008
+ * Build the live send/receive methods from a `ready` readyData, synchronously. This is the plain
1009
+ * (no-handshake) path and the only required per-transport hook. Stream carriers that must await the
1010
+ * carrier opening and/or run a handshake additionally override {@link _needsAsyncBringUp},
1011
+ * {@link _awaitCarrierReady}, and {@link _finalizeReady}.
1012
+ */
1013
+ protected abstract _finalizeTransportMethods(inputs: RD): IActionTransportReadyData_Methods;
1014
+ /**
1015
+ * Whether a `ready`-status transport still needs asynchronous bring-up before its methods exist —
1016
+ * awaiting the carrier to open and/or running a handshake. Default `false`: a stateless transport
1017
+ * (HTTP) is usable the instant `getTransport` reports `ready`, so it stays a terminal *synchronous*
1018
+ * fallback in {@link ConnectionTransportManager}. Stream carriers (Link/WS) override to `true`.
1019
+ */
1020
+ protected _needsAsyncBringUp(_readyData: RD): boolean;
1021
+ /** Await the carrier becoming ready to send (e.g. a socket `open`). Default: nothing to await. */
1022
+ protected _awaitCarrierReady(_readyData: RD): Promise<void>;
1023
+ /**
1024
+ * Finalize during async bring-up — may run a handshake, so it can be async. Defaults to the
1025
+ * synchronous {@link _finalizeTransportMethods}; secure stream carriers override to branch plain/secure.
1026
+ */
1027
+ protected _finalizeReady(readyData: RD): IActionTransportReadyData_Methods | Promise<IActionTransportReadyData_Methods>;
1028
+ protected _getCacheKey(input: RP): string | null;
1029
+ getCacheKey(input: RP): string | null;
1030
+ getTransport(input: RP): TTransportStatusInfo<IActionTransportReadyData_Methods>;
1031
+ protected _processTransportStatus(input: RP): TTransportStatusInfo<IActionTransportReadyData_Methods>;
1032
+ /** Await carrier readiness, then finalize (possibly running a handshake) into the live methods. */
1033
+ private _bringUp;
1034
+ }
1035
+ //#endregion
1036
+ //#region src/ActionRuntime/Transport/Transport.types.d.ts
1037
+ /**
1038
+ * Carrier-shape class identity: the one carrier-invariant trait the selection layer reasons about —
1039
+ * whether a carrier can push unsolicited frames (`duplex`: WS/WebRTC/BLE/in-memory) or only answers a
1040
+ * request with a single correlated reply (`exchange`: HTTP). The concrete carrier kind ("ws", "webrtc",
1041
+ * "http", …) is a free-form {@link ITransportRouteInfo.carrierLabel} for display, not a class tag.
1042
+ */
1043
+ declare enum ETransportShape {
1044
+ duplex = "duplex",
1045
+ exchange = "exchange"
1046
+ }
1047
+ /**
1048
+ * Serializable, display-only description of how an action was routed through a transport. Stored on
1049
+ * the action's route items and shown in the devtools external-handler chips. Keep this free of
1050
+ * sensitive data (e.g. auth headers) — route items travel over the wire.
1051
+ */
1052
+ interface ITransportRouteInfo {
1053
+ /** Free-form carrier kind for the devtools chip — e.g. "ws", "webrtc", "http". */
1054
+ carrierLabel: string;
1055
+ /** Short label for chips, e.g. "POST /resolve_action" or "ws host/resolve_action/ws". */
1056
+ summary?: string;
1057
+ url?: string;
1058
+ method?: string;
1059
+ detail?: Record<string, string | number | boolean>;
1060
+ }
1061
+ interface IUpdateActionRunConfig_Output {
1062
+ timeout?: number;
1063
+ }
1064
+ type TUpdateActionRunConfig = (input: ITransportRouteActionParams & {
1065
+ timeout: number;
1066
+ }) => IUpdateActionRunConfig_Output;
1067
+ interface IActionTransportReadyData_Base {
1068
+ updateRunConfig?: TUpdateActionRunConfig;
1069
+ }
1070
+ /**
1071
+ * Client-side secure-channel config for a connector link — carrier-neutral (the secure session never
1072
+ * cared about the carrier). When present (and `securityLevel !== none`), the connection runs the
1073
+ * handshake during initialization and, at the `encrypted` level, encrypts every frame. The acceptor
1074
+ * side adds a negotiable level set + verify-key resolver on top of this (see Phase 3's `ISecureConfig`).
1075
+ */
1076
+ interface ISecureClientConfig {
1077
+ /** The level this client requests; the peer must allow it. */
1078
+ securityLevel: ESecurityLevel;
1079
+ /** This client's crypto identity (verify + exchange key pairs, optionally persisted). */
1080
+ link: ClientCryptoKeyLink;
1081
+ /** This client's runtime coordinate — its authenticated identity to the peer. */
1082
+ localCoordinate: IRuntimeCoordinate;
1083
+ /** Wire dictionary version; the peer rejects the handshake on a mismatch. */
1084
+ dictionaryVersion: string;
1085
+ }
1086
+ /**
1087
+ *
1088
+ * TRANSPORT READINESS RESPONSE
1089
+ *
1090
+ */
1091
+ declare enum ETransportStatus {
1092
+ uninitialized = "uninitialized",
1093
+ unsupported = "unsupported",
1094
+ initializing = "initializing",
1095
+ ready = "ready",
1096
+ failed = "failed"
1097
+ }
1098
+ interface ITransportStatusInfo_Base<S extends ETransportStatus> {
1099
+ status: S;
1100
+ }
1101
+ interface ITransportStatusInfo_Failed extends ITransportStatusInfo_Base<ETransportStatus.failed> {
1102
+ error: NiceError;
1103
+ timeFailed: number;
1104
+ }
1105
+ interface ITransportStatusInfo_Unsupported extends ITransportStatusInfo_Base<ETransportStatus.unsupported> {}
1106
+ interface ITransportStatusInfo_Ready<READY extends IActionTransportReadyData_Base> extends ITransportStatusInfo_Base<ETransportStatus.ready> {
1107
+ readyData: READY;
1108
+ }
1109
+ type TTransportInitializationFinishedInfo<READY extends IActionTransportReadyData_Base> = ITransportStatusInfo_Ready<READY> | ITransportStatusInfo_Failed | ITransportStatusInfo_Unsupported;
1110
+ interface ITransportStatusInfo_Initializing<READY extends IActionTransportReadyData_Base> extends ITransportStatusInfo_Base<ETransportStatus.initializing> {
1111
+ timeStarted: number;
1112
+ initializationPromise: Promise<TTransportInitializationFinishedInfo<READY>>;
1113
+ }
1114
+ type TTransportStatusInfo<READY extends IActionTransportReadyData_Base> = ITransportStatusInfo_Base<ETransportStatus.uninitialized> | ITransportStatusInfo_Unsupported | ITransportStatusInfo_Initializing<READY> | ITransportStatusInfo_Ready<READY> | ITransportStatusInfo_Failed;
1115
+ type TTransportStatusInfo_GetTransport_Output<READY extends IActionTransportReadyData_Base> = ITransportStatusInfo_Base<ETransportStatus.uninitialized> | ITransportStatusInfo_Unsupported | Omit<ITransportStatusInfo_Initializing<READY>, "timeStarted"> | ITransportStatusInfo_Ready<READY> | Omit<ITransportStatusInfo_Failed, "timeFailed">;
1116
+ /**
1117
+ *
1118
+ * TRANSPORT ROUTING
1119
+ *
1120
+ */
1121
+ interface ITransportRouteClientParams {
1122
+ localClient: RuntimeCoordinate;
1123
+ externalClient: RuntimeCoordinate;
1124
+ }
1125
+ interface ITransportRouteActionParams extends ITransportRouteClientParams {
1126
+ action: TActionPayload_Any_Instance<any, any>;
1127
+ }
1128
+ interface ITransportMethod_SendActionData_Input extends ITransportRouteActionParams {
1129
+ runningAction: RunningAction<any, any>;
1130
+ timeout: number;
1131
+ }
1132
+ interface ITransportDispatchAction<P> extends ITransportMethod_SendActionData_Input {
1133
+ params: P;
1134
+ }
1135
+ type TSendActionDataMethod = (input: ITransportMethod_SendActionData_Input) => void;
1136
+ type TSendReturnDataMethod = (payload: TActionPayload_Any_Instance<any, any>,
1137
+ /**
1138
+ * The local/external client pair this payload is being returned over. Bidirectional transports use
1139
+ * it to build the full route params their outgoing formatter expects (e.g. binary packing). Optional
1140
+ * so existing return-data implementations keep type-checking.
1141
+ */
1142
+
1143
+ clients?: ITransportRouteClientParams) => void;
1144
+ interface IActionTransportReadyData_Methods extends IActionTransportReadyData_Base {
1145
+ sendActionData: TSendActionDataMethod;
1146
+ /**
1147
+ * Optional — implement on bidirectional transports (WebSocket, Custom) to enable return-path
1148
+ * routing. When present, the runtime uses this to dispatch results and progress payloads directly
1149
+ * back to `originClient` without going through the original request transport.
1150
+ */
1151
+ sendReturnData?: TSendReturnDataMethod;
1152
+ addOnDisconnectListener?: (callback: () => void) => void;
1153
+ /**
1154
+ * Optional — implement on transports holding a long-lived connection (WebSocket, Custom) to close it
1155
+ * deliberately. Called by `ConnectorHandler.clearTransportCache()` so a teardown actually
1156
+ * releases the underlying socket instead of leaving it open until GC.
1157
+ */
1158
+ disconnect?: () => void;
1159
+ }
1160
+ interface IActionTransportReady {
1161
+ methods: IActionTransportReadyData_Methods;
1162
+ transport: TransportConnection;
1163
+ }
1164
+ type TTransportCache = Map<string, IActionTransportReady | Promise<IActionTransportReady>>;
1165
+ type TOnResolveIncomingRequest = (request: ActionPayload_Request<any>) => void;
1166
+ type TOnResolveIncomingRequestJson = (request: IActionPayload_Request_JsonObject<any>) => void;
1167
+ type TOnResolveIncomingResponse = (response: ActionPayload_Result<any>) => void;
1168
+ type TOnResolveIncomingResponseJson = (response: IActionPayload_Result_JsonObject<any>) => void;
1169
+ type TOnResolveAnyIncomingActionData = (actionData: ActionPayload_Request<any> | ActionPayload_Result<any>) => void;
1170
+ type TOnResolveAnyIncomingActionData_Json = (actionData: TActionPayload_Any_JsonObject<any>) => void;
1171
+ interface IActionTransportResolvers {
1172
+ onIncomingActionDataJson: TOnResolveAnyIncomingActionData_Json;
1173
+ }
1174
+ type TGetTransportFn<IN extends ITransportRouteActionParams, READY extends IActionTransportReadyData_Base> = (input: IN) => TTransportStatusInfo_GetTransport_Output<READY>;
1175
+ interface IActionTransportInitialized<IN extends ITransportRouteActionParams, READY extends IActionTransportReadyData_Base> {
1176
+ getTransportCacheKey?: (input: IN) => string[];
1177
+ getTransport: TGetTransportFn<IN, READY>;
1178
+ }
1179
+ interface IActionTransportDef<TYPE extends ETransportShape, INIT extends IActionTransportInitialized<any, any>> {
1180
+ type: TYPE;
1181
+ initialize: () => INIT;
1182
+ }
1183
+ //#endregion
1184
+ //#region src/ActionRuntime/Handler/PeerLink/Connector/ConnectorHandler.types.d.ts
1185
+ interface IConnectorHandlerConfig {
1186
+ defaultTimeout?: number;
1187
+ runtimeCoordinate: RuntimeCoordinate;
1188
+ transports: Transport[];
1189
+ }
1190
+ //#endregion
1191
+ //#region src/ActionRuntime/Handler/PeerLink/Connector/ConnectorHandler.d.ts
1192
+ /**
1193
+ * Dial-out peer link: this runtime opens connection(s) to one peer over a transport stack (cached, with
1194
+ * preference-ordered fallback). The classic "client → backend" handler — but to the runtime it's just a
1195
+ * {@link PeerLinkHandler} like the accept-in server one.
1196
+ */
1197
+ declare class ConnectorHandler extends PeerLinkHandler {
1198
+ /**
1199
+ * Dial-out can receive (and so return) an unsolicited push only over a duplex transport. With every
1200
+ * transport exchange-only (HTTP), the peer can never push to us — so this link can't deliver one back.
1201
+ */
1202
+ readonly canPush: boolean;
1203
+ private _defaultTimeout;
1204
+ private _transportCache;
1205
+ private transportManager;
1206
+ constructor({
1207
+ runtimeCoordinate: peerSpecifier,
1208
+ transports,
1209
+ defaultTimeout
1210
+ }: IConnectorHandlerConfig);
1211
+ handleActionRequest<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: ActionPayload_Request<DOM, ID>, config?: IHandleActionOptions): Promise<RunningAction<DOM, ID>>;
1212
+ private _dispatchWhenTransportReady;
1213
+ /**
1214
+ * Dispatch a result or progress payload directly back to the external client via the best
1215
+ * available bidirectional transport (WebSocket / Custom). Used for return-path routing when the
1216
+ * local runtime recognises that it has a direct channel to the action's originClient.
1217
+ *
1218
+ * Returns `true` if the payload was sent, `false` if no suitable transport was available.
1219
+ */
1220
+ sendReturnPayload(payload: TActionPayload_Any_Instance<any, any>, config: {
1221
+ targetLocalRuntime: ActionRuntime;
1222
+ }): Promise<boolean>;
1223
+ toJsonObject(): IActionHandler_Peer_Json;
1224
+ toHandlerRouteItem(transport: TransportConnection, input: ITransportRouteActionParams): IActionRouteItemHandler;
1225
+ clearTransportCache(): void;
1226
+ }
1227
+ declare const createConnectorHandler: (config: IConnectorHandlerConfig) => ConnectorHandler;
1228
+ //#endregion
1229
+ //#region src/ActionRuntime/ActionRuntime.d.ts
1230
+ declare class ActionRuntime {
1231
+ private _coordinate;
1232
+ readonly timeCreated: number;
1233
+ readonly runtimeInfo: IRuntimeMeta;
1234
+ private readonly actionRouter;
1235
+ private readonly _pendingRunningActions;
1236
+ private readonly _registeredPeerHandlers;
1237
+ private _applied;
1238
+ static getDefault(): ActionRuntime;
1239
+ constructor(coordinate: RuntimeCoordinate);
1240
+ get coordinate(): RuntimeCoordinate;
1241
+ specifyRuntimeCoordinate(specifics: IRuntimeCoordinateSpecifics & {
1242
+ envId?: string;
1243
+ }): void;
1244
+ registerRunningAction(ra: RunningAction<any, any>): void;
1245
+ resolveIncomingActionPayload(json: TActionPayload_Any_JsonObject<any, any>): void;
1246
+ /**
1247
+ * Handle an incoming action wire (e.g. from a transport layer), route it to
1248
+ * the correct handler, and return the response. The most specific handler
1249
+ * match is chosen (action-ID-specific beats domain-wildcard).
1250
+ */
1251
+ handleActionPayloadWire<D extends IActionDomain, ID extends keyof D["actionSchema"] & string>(wire: TActionPayload_Any_JsonObject<D, ID>): Promise<RunningAction<D, ID>>;
1252
+ handleActionPayloadWire(wire: unknown): Promise<RunningAction<any, any>>;
1253
+ handleActionPayload<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: TActionPayload_Any_Instance<DOM, ID>, options?: Omit<IHandleActionOptions, "targetLocalRuntime">): Promise<RunningAction<DOM, ID>>;
1254
+ /**
937
1255
  * @internal
938
1256
  *
939
1257
  * Return the first handler registered for the given action, or `undefined`
@@ -950,13 +1268,13 @@ declare class ActionRuntime {
950
1268
  addHandlers(handlers: TActionRuntimeHandler[]): this;
951
1269
  /**
952
1270
  * Declare an external "backend client" in one call: build an
953
- * {@link ActionExternalClientHandler} for `externalCoordinate` carrying the given
1271
+ * {@link ConnectorHandler} for `externalCoordinate` carrying the given
954
1272
  * `transports`, route the listed `domains`/`actions` to it, register it (plus any
955
1273
  * `localHandlers` — e.g. server→client push handlers that share the same channel)
956
1274
  * on this runtime, and `apply()`. Returns the external handler so the caller can
957
1275
  * later `clearTransportCache()` it.
958
1276
  *
959
- * Sugar over `new ActionExternalClientHandler(...).forDomain(...)` + `addHandlers([...])`,
1277
+ * Sugar over `new ConnectorHandler(...).forDomain(...)` + `addHandlers([...])`,
960
1278
  * so a single runtime can host one handler per backend target with its transports
961
1279
  * declared once and reused across every action routed to that backend.
962
1280
  */
@@ -966,7 +1284,7 @@ declare class ActionRuntime {
966
1284
  actions?: ActionCore<any, any>[];
967
1285
  localHandlers?: TActionRuntimeHandler[];
968
1286
  defaultTimeout?: number;
969
- }): ActionExternalClientHandler;
1287
+ }): ConnectorHandler;
970
1288
  private applyRuntimeForDomain;
971
1289
  /**
972
1290
  * Register this runtime with all root domains covered by its currently-added handlers,
@@ -978,8 +1296,14 @@ declare class ActionRuntime {
978
1296
  * Find the best registered external handler that can reach `originClient` directly.
979
1297
  * Used to locate the return-path channel for dispatching results back to the action origin.
980
1298
  * Returns `undefined` if no handler matches (score > 0 required, i.e. at least id must match).
1299
+ *
1300
+ * A handler that currently holds the origin's *live* connection always wins over a mere coordinate
1301
+ * match — so with several duplex acceptors (e.g. WS + WebRTC) a result/push routes back over the carrier
1302
+ * the client actually connected on, never a same-coordinate sibling that lacks the socket. Only when no
1303
+ * handler owns a live connection do we fall back to the plain best-coordinate-score pick (the
1304
+ * single-acceptor and connector-only cases, unchanged).
981
1305
  */
982
- getReturnHandlerForOrigin(originClient: RuntimeCoordinate): ActionExternalClientHandler | undefined;
1306
+ getReturnHandlerForOrigin(originClient: RuntimeCoordinate): PeerLinkHandler | undefined;
983
1307
  resetRuntime(): void;
984
1308
  private _trySetupReturnDispatch;
985
1309
  }
@@ -1076,640 +1400,978 @@ declare const createActionRootDomain: <ID extends string>(definition: {
1076
1400
  domain: ID;
1077
1401
  }) => ActionRootDomain<IActionRootDomain<ID>>;
1078
1402
  //#endregion
1079
- //#region src/ActionRuntime/Handler/ExternalClient/err_nice_external_client.d.ts
1080
- declare const err_nice_external_client: import("@nice-code/error").NiceErrorDomain<{
1081
- domain: string;
1082
- allDomains: [string, string, "err_nice"];
1083
- schema: {};
1084
- }>;
1085
- //#endregion
1086
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/Custom/TransportCustom.types.d.ts
1087
- interface IActionTransportReadyData_Custom extends IActionTransportReadyData_Base {
1088
- sendActionData: (input: ITransportMethod_SendActionData_Input) => void;
1089
- sendReturnData?: TSendReturnDataMethod;
1090
- closeTransport: (input?: ITransportRouteActionParams) => void;
1403
+ //#region src/ActionRuntime/Transport/codec/actionWireCodec.d.ts
1404
+ /**
1405
+ * Shared building blocks for the binary action codecs (the stateless {@link createBinaryWireAdapter} and
1406
+ * the per-connection `createBinaryWireSessionFactory`). Both map a `domain:id` route to a tiny integer
1407
+ * and reduce the verbose JSON wire to a positional tuple — they only differ in how much context they
1408
+ * carry per frame, so the dictionary + payload (de)assembly live here.
1409
+ */
1410
+ /**
1411
+ * The carrier-neutral codec a Link connection uses to (de)serialize action payloads on the wire — the
1412
+ * same shape every duplex carrier (WS/WebRTC/in-memory) shares.
1413
+ */
1414
+ interface IActionWireFormat {
1415
+ /**
1416
+ * Pack an outgoing action payload. Return a `string` for text frames (JSON) or a binary
1417
+ * `Uint8Array`/`ArrayBuffer` for optimized binary frames (e.g. msgpackr).
1418
+ */
1419
+ outgoing: (input: ITransportRouteActionParams) => string | Uint8Array | ArrayBuffer;
1420
+ /**
1421
+ * Unpack an incoming frame back into the wire JSON object the runtime hydrates + validates. Return
1422
+ * `undefined` to defer to the connection's built-in JSON parser — this is how binary adapters stay
1423
+ * backward compatible with plain-JSON clients on the same socket.
1424
+ */
1425
+ incoming?: (input: string | ArrayBuffer | Uint8Array | Blob) => TActionPayload_Any_JsonObject<any, any> | undefined;
1091
1426
  }
1092
- interface IActionTransportInitialized_Custom extends IActionTransportInitialized<ITransportRouteActionParams, IActionTransportReadyData_Custom> {}
1093
- interface IActionTransportDef_Custom extends IActionTransportDef<ETransportType.custom, IActionTransportInitialized_Custom> {}
1094
- type TActionTransportDef_Custom_NoType = Omit<IActionTransportDef_Custom, "type">;
1095
1427
  //#endregion
1096
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/Custom/CustomConnection.d.ts
1097
- declare class CustomConnection extends TransportConnection<ETransportType.custom, ITransportRouteActionParams, IActionTransportReadyData_Custom, IActionTransportInitialized_Custom, IActionTransportDef_Custom> {
1098
- constructor(def: TActionTransportDef_Custom_NoType);
1099
- protected _finalizeTransportMethods(inputs: IActionTransportReadyData_Custom): IActionTransportReadyData_Methods;
1428
+ //#region src/ActionRuntime/Handler/PeerLink/Acceptor/AcceptorHandler.d.ts
1429
+ /** The codec shape `AcceptorHandler` uses to pack/unpack frames — same as the Link transport's. */
1430
+ type TActionChannelFormatMessage = IActionWireFormat;
1431
+ /** How a connection encodes its frames, remembered so we answer each client in its own dialect. */
1432
+ type TActionConnectionEncoding = "json" | "binary";
1433
+ /** A connection's restorable identity — what to persist so a binding survives transport eviction. */
1434
+ interface IAcceptorConnectionBinding {
1435
+ /** Full client coordinate, so `originClient` can be re-injected into frames that omit it. */
1436
+ client: IRuntimeCoordinate;
1437
+ encoding: TActionConnectionEncoding;
1438
+ /**
1439
+ * Secure-session state (set once a connection's handshake completes). Persist it alongside the
1440
+ * binding so an authenticated/encrypted connection resumes after eviction without re-handshaking —
1441
+ * the `keyMaterial` lets the server re-derive the shared key from its own persisted identity.
1442
+ */
1443
+ secure?: {
1444
+ securityLevel: ESecurityLevel;
1445
+ linkedClientId: TTypeAndId;
1446
+ keyMaterial?: IHandshakeEncryptionKeyMaterial;
1447
+ };
1100
1448
  }
1101
- //#endregion
1102
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/Custom/CustomTransport.d.ts
1103
- interface ICustomTransportSharedOptions {
1104
- updateRunConfig?: TUpdateActionRunConfig;
1105
- getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
1106
- /** Short label shown in the devtools chip (defaults to "custom"). */
1107
- label?: string;
1108
- /** Override the devtools route info for a specific action. */
1109
- getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
1449
+ /**
1450
+ * Server-side secure-channel config. When set, each connection negotiates a level from
1451
+ * {@link securityLevel}: an `authenticated`/`encrypted` client must complete the handshake (and is then
1452
+ * bound to its *authenticated* coordinate) before any action frame is accepted. A `none` client (only
1453
+ * when `none` is in the allowed set) is accepted as-is with a self-asserted identity. For the
1454
+ * `encrypted` level the codec source should be a session factory (`createFormatMessage`).
1455
+ */
1456
+ interface IAcceptorSecurity {
1457
+ /**
1458
+ * Accepted level(s). A single level is strict; an array is a negotiable allowed set — the server
1459
+ * adopts whichever level each client requests (e.g. `[none, authenticated, encrypted]` serves all
1460
+ * three over one endpoint).
1461
+ */
1462
+ securityLevel: ESecurityLevel | readonly ESecurityLevel[];
1463
+ /** This server's crypto identity (verify + exchange key pairs, optionally persisted). */
1464
+ link: ClientCryptoKeyLink;
1465
+ /** This server's coordinate — its identity to clients during the handshake. */
1466
+ localCoordinate: IRuntimeCoordinate;
1467
+ /** Wire dictionary version; the handshake rejects a client on a mismatch. */
1468
+ dictionaryVersion: string;
1469
+ /** Trust decision for a client's verify key (defaults to in-memory TOFU inside the handshake). */
1470
+ verifyKeyResolver?: IClientVerifyKeyResolver;
1110
1471
  }
1111
- interface ICustomTransportSendOptions extends ICustomTransportSharedOptions {
1112
- /** Send a request/progress payload to the external client. */
1113
- sendActionData: (input: ITransportMethod_SendActionData_Input) => void;
1114
- /** Optional return-path dispatch for bidirectional channels. */
1115
- sendReturnData?: TSendReturnDataMethod;
1116
- closeTransport?: (input?: ITransportRouteActionParams) => void;
1472
+ interface IAcceptorHandlerBaseOptions<TConn> {
1473
+ /**
1474
+ * Coordinate of the *connecting clients* (typically env-only, e.g. `RuntimeCoordinate.env("web_app")`).
1475
+ * The runtime's return-path dispatch scores incoming actions' `originClient` against this to pick
1476
+ * this handler for sending results/pushes back over the right channel.
1477
+ */
1478
+ clientEnv: RuntimeCoordinate;
1479
+ /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
1480
+ send: (connection: TConn, frame: string | Uint8Array | ArrayBuffer) => void;
1481
+ /**
1482
+ * The runtime this handler belongs to. When set, {@link AcceptorHandler.broadcast} can be called
1483
+ * without threading a runtime through each call. Optional — `pushToClient` still takes one explicitly.
1484
+ */
1485
+ runtime?: ActionRuntime;
1486
+ /** Timeout (ms) applied to server-initiated actions awaiting a client response. */
1487
+ defaultTimeout?: number;
1488
+ /**
1489
+ * Called once when a connection is first bound to a client identity. Use it to persist the binding
1490
+ * for transports that can resume after eviction — e.g. a Durable Object's hibernatable WebSocket:
1491
+ * `(ws, binding) => ws.serializeAttachment(binding)` — then replay it via {@link AcceptorHandler.rehydrateConnection}
1492
+ * when the channel comes back.
1493
+ */
1494
+ onConnectionBound?: (connection: TConn, binding: IAcceptorConnectionBinding) => void;
1495
+ /**
1496
+ * Enable the authenticated (optionally encrypted) handshake. When omitted, connections are trusted
1497
+ * as-is (identity self-asserted) — fine for dev / trusted networks.
1498
+ */
1499
+ security?: IAcceptorSecurity;
1500
+ }
1501
+ /**
1502
+ * Provide exactly one codec source:
1503
+ * - `formatMessage` — a single shared codec for every connection (stateless, e.g. `createBinaryWireAdapter`).
1504
+ * - `createFormatMessage` — a per-connection factory for stateful codecs (e.g.
1505
+ * `createBinaryWireSessionFactory`, whose sessions hold correlation + identity state). Required for the
1506
+ * leanest binary wire; the handler creates and caches one codec per connection.
1507
+ */
1508
+ type IAcceptorHandlerOptions<TConn> = IAcceptorHandlerBaseOptions<TConn> & ({
1509
+ formatMessage: TActionChannelFormatMessage;
1510
+ createFormatMessage?: never;
1511
+ } | {
1512
+ createFormatMessage: () => TActionChannelFormatMessage;
1513
+ formatMessage?: never;
1514
+ });
1515
+ /**
1516
+ * A connection-aware execution case (see {@link AcceptorHandler.forConnectionDomainCases}). It
1517
+ * receives the primed request plus the originating client's live connection (already resolved from the
1518
+ * request's `originClient`, `undefined` if the socket is gone), and may return the action's raw output,
1519
+ * a result payload, or nothing (auto-wrapped as an empty success) — exactly like a local handler case.
1520
+ */
1521
+ type TAcceptorConnectionCaseFn<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string, TConn> = (action: TDistributeActionPayload_Request<DOM, ID>, connection: TConn | undefined) => ReturnType<THandleActionExecutionFn<DOM, ID>> | void;
1522
+ /**
1523
+ * Server-side handler for backends that accept many client connections over a single open channel
1524
+ * (WebSockets, Durable Objects, …). It is transport-agnostic: you feed it inbound frames with
1525
+ * {@link receive} and tell it how to write outbound frames via the `send` option.
1526
+ *
1527
+ * Add it alongside your local execution handler:
1528
+ * ```ts
1529
+ * const serverHandler = createAcceptorHandler({ clientEnv, formatMessage, send: (ws, f) => ws.send(f) });
1530
+ * runtime.addHandlers([localHandler, serverHandler]);
1531
+ * // per inbound message (e.g. a Durable Object's webSocketMessage):
1532
+ * serverHandler.receive(ws, message);
1533
+ * ```
1534
+ *
1535
+ * Inbound requests route to your local handler; the runtime's return dispatch then calls this
1536
+ * handler back (it is an external handler keyed to `clientEnv`) to send the result to the originating
1537
+ * connection. The handler keeps a per-connection identity registry so each result lands on the right
1538
+ * socket, and remembers each connection's encoding so binary and JSON clients can share the channel.
1539
+ *
1540
+ * It registers an empty action router, so it is never chosen to *execute* an inbound request — only
1541
+ * to ferry results/pushes back out.
1542
+ */
1543
+ declare class AcceptorHandler<TConn = unknown> extends PeerLinkHandler {
1544
+ /** Accept-in over a live (duplex) connection registry — it pushes results/broadcasts to bound sockets. */
1545
+ readonly canPush = true;
1546
+ private readonly _formatMessage?;
1547
+ private readonly _createFormatMessage?;
1548
+ private readonly _send;
1549
+ private readonly _runtime?;
1550
+ private readonly _serverTimeout;
1551
+ private _onConnectionBound?;
1552
+ private readonly _security?;
1553
+ /** Normalized accepted levels; whether `none` (plain) is allowed; whether any level needs a handshake. */
1554
+ private readonly _allowedLevels;
1555
+ private readonly _noneAllowed;
1556
+ private readonly _handshakeMode;
1557
+ private readonly _connByClient;
1558
+ private readonly _clientByConn;
1559
+ private readonly _connEncoding;
1560
+ private readonly _codecByConn;
1561
+ private readonly _sessionByConn;
1562
+ constructor(options: IAcceptorHandlerOptions<TConn>);
1563
+ /**
1564
+ * The codec for a connection: a per-connection session (cached) when a factory was provided, else
1565
+ * the single shared `formatMessage`.
1566
+ */
1567
+ private _codecFor;
1568
+ /**
1569
+ * Register (or replace) the connection-bound persistence callback after construction. Used by
1570
+ * lifecycle helpers like {@link createHibernatableWsServerAdapter} so persistence and replay are
1571
+ * owned by one place instead of being split across the constructor options.
1572
+ */
1573
+ setOnConnectionBound(onConnectionBound: (connection: TConn, binding: IAcceptorConnectionBinding) => void): void;
1574
+ /**
1575
+ * Feed one inbound frame from a connection into the runtime. Decodes text or binary, binds the
1576
+ * connection to the requesting client's identity, then routes it (requests execute locally;
1577
+ * results/progress resolve pending server-initiated actions).
1578
+ */
1579
+ receive(connection: TConn, frame: string | ArrayBuffer | Uint8Array): void;
1580
+ private _receivePlain;
1581
+ /**
1582
+ * The secure session for a connection (built lazily on its first secure-mode frame), with the
1583
+ * handler-owned effects — raw send, identity binding + persistence, and inbound routing — wired in as
1584
+ * callbacks. The session owns all crypto/handshake/chain state; the handler keeps only the registry.
1585
+ */
1586
+ private _sessionFor;
1587
+ /** Bind + persist a connection's authenticated identity once its handshake completes. */
1588
+ private _onConnectionAuthenticated;
1589
+ /** Decode a decrypted authenticated frame, inject the *authenticated* identity, and route it. */
1590
+ private _routeAuthedActionBytes;
1591
+ /**
1592
+ * Ensure an inbound request carries the client's identity and that this connection is bound to it,
1593
+ * so its result can be routed back. A session codec omits `originClient` after the first request, so
1594
+ * when it's missing we restore it from the (possibly rehydrated) binding instead. (Plain mode only;
1595
+ * secure mode binds the authenticated coordinate at handshake time.)
1596
+ */
1597
+ private _resolveRequestIdentity;
1598
+ /**
1599
+ * Restore a connection→client binding without an inbound frame — for transports that resume after
1600
+ * eviction. Pair it with the {@link IAcceptorHandlerOptions.onConnectionBound} hook: persist
1601
+ * the binding there, then replay each live connection here when the channel comes back (e.g. a
1602
+ * Durable Object iterating `ctx.getWebSockets()` as it wakes from hibernation).
1603
+ */
1604
+ rehydrateConnection(connection: TConn, binding: IAcceptorConnectionBinding): void;
1605
+ toJsonObject(): IActionHandler_Peer_Json;
1606
+ toHandlerRouteItem(): IActionRouteItemHandler;
1607
+ /** Forget a connection (call on socket close) so stale entries don't misroute later results. */
1608
+ dropConnection(connection: TConn): void;
1609
+ /** Live connection for a client coordinate, if currently registered. */
1610
+ getConnectionForClient(client: RuntimeCoordinate): TConn | undefined;
1611
+ /** This acceptor owns the origin's return path when it currently holds a live connection bound to it. */
1612
+ ownsLiveConnectionFor(origin: RuntimeCoordinate): boolean;
1613
+ /** Whether this acceptor currently tracks `connection` — used to pick the owning handler among several. */
1614
+ hasConnection(connection: TConn): boolean;
1615
+ /**
1616
+ * Send (and optionally await) a server-initiated action to a specific connected client. Pass the
1617
+ * connection token directly (e.g. the `ws`) or a client `RuntimeCoordinate` to look one up.
1618
+ */
1619
+ pushToClient<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(runtime: ActionRuntime, target: TConn | RuntimeCoordinate, request: ActionPayload_Request<DOM, ID>, options?: {
1620
+ timeout?: number;
1621
+ }): RunningAction<DOM, ID>;
1622
+ /**
1623
+ * Build a local handler whose cases are connection-aware: each case receives the primed request and
1624
+ * the originating client's live connection (resolved from `originClient`), so handlers don't repeat
1625
+ * the `getConnectionForClient(action.context.originClient)` lookup. Cases may return raw output or
1626
+ * nothing, just like {@link ActionLocalHandler.forDomainActionCases}. Add the returned handler to the
1627
+ * runtime alongside this server handler:
1628
+ * ```ts
1629
+ * runtime.addHandlers([serverHandler.forConnectionDomainCases(domain, { … }), serverHandler]);
1630
+ * ```
1631
+ */
1632
+ forConnectionDomainCases<FOR_DOM extends IActionDomain>(domain: ActionDomain<FOR_DOM>, cases: { [ID in keyof FOR_DOM["actionSchema"] & string]?: TAcceptorConnectionCaseFn<FOR_DOM, ID, TConn> }): ActionLocalHandler;
1633
+ /**
1634
+ * Like {@link forConnectionDomainCases} but spanning several domains with one merged case map — used
1635
+ * by channel-derived wiring (`acceptChannelConnections`) where the channel's `toAcceptor` domains are
1636
+ * served together. Each domain takes only the cases whose ids it owns, so a single map can cover
1637
+ * several domains and unrelated ids are ignored.
1638
+ */
1639
+ forConnectionDomainCasesMulti(domains: readonly ActionDomain<any>[], cases: Record<string, TAcceptorConnectionCaseFn<any, any, TConn> | undefined>): ActionLocalHandler;
1640
+ /**
1641
+ * Fan a server-initiated request out to every currently-bound connection. A fresh request is built
1642
+ * per connection (each push mutates its own action context) and dispatched fire-and-forget. Pass
1643
+ * `except` to skip the originating socket and `where` to filter by connection (e.g. read its
1644
+ * attachment for a role). Iterating bound connections (rather than every accepted socket) skips
1645
+ * sockets that are still mid-handshake and so can't yet receive a frame.
1646
+ */
1647
+ broadcast<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(makeRequest: () => ActionPayload_Request<DOM, ID>, options?: {
1648
+ runtime?: ActionRuntime;
1649
+ except?: TConn | null;
1650
+ where?: (connection: TConn) => boolean;
1651
+ timeout?: number;
1652
+ onError?: (error: unknown, connection: TConn) => void;
1653
+ }): void;
1654
+ sendReturnPayload(payload: TActionPayload_Any_Instance<any, any>, config: {
1655
+ targetLocalRuntime: ActionRuntime;
1656
+ }): Promise<boolean>;
1657
+ handleActionRequest<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: ActionPayload_Request<DOM, ID>, config?: IHandleActionOptions): Promise<RunningAction<DOM, ID>>;
1658
+ private _dispatch;
1659
+ private _sendPayload;
1660
+ private _bindConnection;
1661
+ private _resolveConnection;
1662
+ private _resolveSingleConnection;
1117
1663
  }
1118
- interface ICustomTransportAdvancedOptions extends ICustomTransportSharedOptions {
1119
- /** Full control over readiness/initialization for the custom channel. */
1120
- getTransport: IActionTransportInitialized_Custom["getTransport"];
1664
+ declare const createAcceptorHandler: <TConn = unknown>(options: IAcceptorHandlerOptions<TConn>) => AcceptorHandler<TConn>;
1665
+ //#endregion
1666
+ //#region src/ActionRuntime/Transport/codec/createBinaryWireSessionFactory.d.ts
1667
+ type TFormatMessage = IActionWireFormat;
1668
+ interface IBinaryWireSessionOptions {
1669
+ /** Override how long an unresolved correlation is retained before being swept (ms). */
1670
+ correlationTtlMs?: number;
1121
1671
  }
1122
- type TCustomTransportOptions = (ICustomTransportSendOptions & {
1123
- mode: "send";
1124
- }) | (ICustomTransportAdvancedOptions & {
1125
- mode: "advanced";
1126
- });
1127
1672
  /**
1128
- * Reusable custom transport definition for channels nice-action doesn't model natively. Create one
1129
- * with `CustomTransport.create({ sendActionData })` for the simple case, or
1130
- * `CustomTransport.createAdvanced({ getTransport })` for full control over the lifecycle.
1673
+ * Builds a factory of *stateful, per-connection* codecs for {@link LinkTransport} /
1674
+ * `AcceptorHandler` the maximally compact binary wire. Call the returned factory once per live
1675
+ * connection (each socket on the client, each accepted connection on the server) so every channel
1676
+ * gets its own correlation + identity state.
1677
+ *
1678
+ * On top of everything {@link createBinaryWireAdapter} drops, a session also drops:
1679
+ * - **`cuid`** — replaced by a per-connection integer correlation id. The initiator maps it to its
1680
+ * real cuid; the responder echoes it; each side reconstructs the cuid from its own map. Correlation
1681
+ * only needs to be unique per socket, so a counter suffices.
1682
+ * - **`originClient` after the first request** — the first request each side sends carries its
1683
+ * identity; the peer remembers it and injects it into later frames. Replies omit it entirely (a
1684
+ * reply carries the initiator's own origin, which the initiator already knows).
1685
+ *
1686
+ * Both ends MUST build the factory from the same domains in the same order (positional dictionary).
1687
+ * Text frames still return `undefined` from `incoming`, so JSON clients remain interoperable.
1688
+ *
1689
+ * Hibernation note: after a server connection is evicted its session resets, so a still-connected
1690
+ * client (whose session persists) will keep omitting `originClient`. The server must therefore restore
1691
+ * the connection→client binding from its own store (see `AcceptorHandler.rehydrateConnection`) and
1692
+ * inject `originClient` from there — the session alone can't recover it.
1131
1693
  */
1132
- declare class CustomTransport extends Transport<ETransportType.custom> {
1133
- private readonly options;
1134
- readonly type = ETransportType.custom;
1135
- constructor(options: TCustomTransportOptions);
1136
- static create(options: ICustomTransportSendOptions): CustomTransport;
1137
- static createAdvanced(options: ICustomTransportAdvancedOptions): CustomTransport;
1138
- _createConnection(_ctx: ITransportConnectionContext): CustomConnection;
1139
- getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
1140
- }
1141
- //#endregion
1142
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/err_nice_transport.d.ts
1143
- declare enum EErrId_NiceTransport {
1144
- timeout = "timeout",
1145
- not_found = "not_found",
1146
- unsupported = "unsupported",
1147
- initialization_failed = "initialization_failed",
1148
- send_failed = "send_failed",
1149
- invalid_action_response = "invalid_action_response"
1150
- }
1151
- declare const err_nice_transport: import("@nice-code/error").NiceErrorDomain<{
1152
- domain: string;
1153
- allDomains: [string, string, string, "err_nice"];
1154
- schema: {
1155
- timeout: import("@nice-code/error").INiceErrorIdMetadata<{
1156
- timeout: number;
1157
- }, import("@nice-code/error").JSONSerializableValue>;
1158
- not_found: import("@nice-code/error").INiceErrorIdMetadata<{
1159
- actionId: string;
1160
- }, import("@nice-code/error").JSONSerializableValue>;
1161
- unsupported: import("@nice-code/error").INiceErrorIdMetadata<{
1162
- transportTypes: ETransportType[];
1163
- }, import("@nice-code/error").JSONSerializableValue>;
1164
- initialization_failed: import("@nice-code/error").INiceErrorIdMetadata<{
1165
- actionId: string;
1166
- }, import("@nice-code/error").JSONSerializableValue>;
1167
- send_failed: import("@nice-code/error").INiceErrorIdMetadata<{
1168
- actionState: string;
1169
- actionId: string;
1170
- httpStatusCode?: number;
1171
- message?: string;
1172
- }, import("@nice-code/error").JSONSerializableValue>;
1173
- invalid_action_response: import("@nice-code/error").INiceErrorIdMetadata<{
1174
- actionId: string;
1175
- }, import("@nice-code/error").JSONSerializableValue>;
1176
- };
1177
- }>;
1178
- //#endregion
1179
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/err_nice_transport_ws.d.ts
1180
- declare enum EErrId_NiceTransport_WebSocket {
1181
- ws_disconnected = "ws_disconnected",
1182
- ws_create_failed = "ws_create_failed",
1183
- ws_error = "ws_error"
1184
- }
1185
- declare const err_nice_transport_ws: import("@nice-code/error").NiceErrorDomain<{
1186
- domain: string;
1187
- allDomains: [string, string, string, string, "err_nice"];
1188
- schema: {
1189
- ws_disconnected: import("@nice-code/error").INiceErrorIdMetadata<Record<string, never>, import("@nice-code/error").JSONSerializableValue>;
1190
- ws_create_failed: import("@nice-code/error").INiceErrorIdMetadata<{
1191
- originalError?: Error;
1192
- }, import("@nice-code/error").JSONSerializableValue>;
1193
- ws_error: import("@nice-code/error").INiceErrorIdMetadata<{
1194
- originalError?: Error;
1195
- }, import("@nice-code/error").JSONSerializableValue>;
1196
- };
1197
- }>;
1198
- //#endregion
1199
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/Http/TransportHttp.types.d.ts
1200
- interface IHttpRequestParams {
1201
- url: string;
1202
- headers?: Record<string, string>;
1203
- body?: string;
1204
- }
1205
- interface IActionTransportReadyParams_Http {
1206
- request: IHttpRequestParams;
1207
- }
1208
- interface IActionTransportReadyData_Http extends IActionTransportReadyData_Base {
1209
- createRequest: (input: ITransportRouteActionParams) => IHttpRequestParams;
1210
- }
1211
- interface IActionTransportInitialized_Http extends IActionTransportInitialized<ITransportRouteActionParams, IActionTransportReadyData_Http> {}
1212
- interface IActionTransportDef_Http extends IActionTransportDef<ETransportType.http, IActionTransportInitialized_Http> {}
1694
+ declare function createBinaryWireSessionFactory(domains: ActionDomain<any>[], options?: IBinaryWireSessionOptions): () => TFormatMessage;
1213
1695
  //#endregion
1214
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/Http/HttpConnection.d.ts
1215
- declare class HttpConnection extends TransportConnection<ETransportType.http, ITransportRouteActionParams, IActionTransportReadyData_Http, IActionTransportInitialized_Http, IActionTransportDef_Http> {
1216
- constructor(def: Omit<IActionTransportDef_Http, "type">);
1217
- _finalizeTransportMethods(methods: IActionTransportReadyData_Http): IActionTransportReadyData_Methods;
1218
- protected send(input: ITransportDispatchAction<IActionTransportReadyParams_Http>): Promise<void>;
1696
+ //#region src/ActionRuntime/Channel/secureChannel.d.ts
1697
+ /** The per-connection binary session codec built once per socket from the channel's domains. */
1698
+ type TChannelCodec = IActionWireFormat;
1699
+ /**
1700
+ * The shared identity of a secure channel (carrier-agnostic — WS, WebRTC, HTTP, …): the wire
1701
+ * dictionary version both ends check during the handshake, plus the per-connection codec factory both
1702
+ * ends build from the *same* domain list. Define it once (typically in code shared by both peers) and
1703
+ * hand it to `secureTransport` on the connector and `serveChannel` (or the lower-level `acceptChannel` /
1704
+ * `createSecureAcceptorHandler`) on the acceptor, so the codec and version can never drift apart.
1705
+ */
1706
+ interface ISecureChannel<TO_ACCEPTOR extends readonly ActionDomain<any>[] = readonly ActionDomain<any>[], TO_CONNECTOR extends readonly ActionDomain<any>[] = readonly ActionDomain<any>[]> extends IActionChannel<TO_ACCEPTOR, TO_CONNECTOR> {
1707
+ /** Wire dictionary version — derived from the domains by default; the handshake rejects a mismatch. */
1708
+ dictionaryVersion: string;
1709
+ /** Per-connection session codec factory (call once per live connection). */
1710
+ createCodec: () => TChannelCodec;
1219
1711
  }
1712
+ /**
1713
+ * Bundle a secure channel's shared identity from its transported domains. Both ends MUST call this
1714
+ * with the same domains in the same order (the binary wire dictionary is positional). The
1715
+ * `dictionaryVersion` is derived from those domains unless you pin an explicit one.
1716
+ *
1717
+ * Declare the domains *by role* — `toAcceptor` (connector→acceptor requests) and `toConnector`
1718
+ * (acceptor→connector pushes) — so the routing for both ends is derived from the channel (see
1719
+ * {@link connectChannel} and `acceptChannelConnections`) instead of being restated at each end. The
1720
+ * wire dictionary spans `[...toAcceptor, ...toConnector]` in that order; add new domains to the end of
1721
+ * their list to keep older peers compatible. (`domains` is still accepted as a legacy alias for
1722
+ * `toAcceptor`.)
1723
+ */
1724
+ declare function defineSecureChannel<const TO_ACCEPTOR extends readonly ActionDomain<any>[] = [], const TO_CONNECTOR extends readonly ActionDomain<any>[] = []>(options: {
1725
+ /** Domains the connector sends to the acceptor (connector→acceptor requests), in a stable order. */toAcceptor: TO_ACCEPTOR; /** Domains the acceptor pushes to the connector (acceptor→connector), in a stable order. */
1726
+ toConnector: TO_CONNECTOR; /** Pin a human-readable version instead of the derived hash (must match on both ends). */
1727
+ dictionaryVersion?: string; /** Tuning for the per-connection binary session (e.g. correlation TTL). */
1728
+ sessionOptions?: IBinaryWireSessionOptions;
1729
+ }): ISecureChannel<TO_ACCEPTOR, TO_CONNECTOR>;
1220
1730
  //#endregion
1221
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/Http/HttpTransport.d.ts
1222
- interface IHttpTransportOptions {
1731
+ //#region src/ActionRuntime/Channel/ActionChannel.d.ts
1732
+ /**
1733
+ * A transport-agnostic routing contract between two runtimes, declared *by role* rather than by
1734
+ * "client"/"server". The two ends are named for the only asymmetry that survives every carrier (WS,
1735
+ * WebRTC, BLE, raw TCP): which side dials and which side accepts.
1736
+ *
1737
+ * - The **connector** dials out and opens the link ({@link connectChannel}).
1738
+ * - The **acceptor** accepts incoming links and can push back ({@link acceptChannelConnections}).
1739
+ *
1740
+ * `toAcceptor` domains flow connector→acceptor (the classic "request"); `toConnector` domains flow
1741
+ * acceptor→connector (the classic "push"). Both ends derive their routing from the same channel instead
1742
+ * of restating domain lists — and because the contract is independent of how bytes move, the very same
1743
+ * channel can be carried over HTTP, secure WebSockets, or a mix (WS preferred, HTTP fallback).
1744
+ *
1745
+ * Build a plain one with {@link defineChannel}; layer wire-specific identity on top with
1746
+ * `defineSecureChannel` (which returns an `ISecureChannel` — still an `IActionChannel`, so it works
1747
+ * everywhere a channel is expected).
1748
+ */
1749
+ interface IActionChannel<TO_ACCEPTOR extends readonly ActionDomain<any>[] = readonly ActionDomain<any>[], TO_CONNECTOR extends readonly ActionDomain<any>[] = readonly ActionDomain<any>[]> {
1223
1750
  /**
1224
- * Build the HTTP request for an action. Return a static request for the simple case, or derive the
1225
- * url / headers / body from the action params for full control.
1751
+ * Domains the connector *sends to the acceptor* (connector→acceptor requests). The connector forwards
1752
+ * them over its transport(s); the acceptor executes them.
1226
1753
  */
1227
- createRequest: (input: ITransportRouteActionParams) => IHttpRequestParams;
1228
- updateRunConfig?: TUpdateActionRunConfig;
1229
- getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
1754
+ toAcceptorDomains: TO_ACCEPTOR;
1755
+ /**
1756
+ * Domains the acceptor *pushes to the connector* (acceptor→connector). The connector registers local
1757
+ * handlers for them ({@link connectChannel}'s `onPush`); the acceptor broadcasts them. Pushes need a
1758
+ * bidirectional transport (e.g. a WebSocket) — over a request-only transport like HTTP they simply
1759
+ * never flow.
1760
+ */
1761
+ toConnectorDomains: TO_CONNECTOR;
1230
1762
  }
1231
1763
  /**
1232
- * Reusable HTTP transport definition. Create one with `HttpTransport.create({ createRequest })` the
1233
- * single `createRequest` function lets you keep it simple (`() => ({ url })`) or derive the request
1234
- * per action.
1764
+ * Declare a transport-agnostic channel by role. Use it for HTTP, custom transports, or as the routing
1765
+ * half of a richer channel. The order of each list is part of the contract for wire formats that pack
1766
+ * positionally (see `defineSecureChannel`) — add new domains to the end of their list. (`domains` is
1767
+ * accepted as a legacy alias for `toAcceptor`.)
1235
1768
  */
1236
- declare class HttpTransport extends Transport<ETransportType.http> {
1237
- private readonly options;
1238
- readonly type = ETransportType.http;
1239
- constructor(options: IHttpTransportOptions);
1240
- static create(options: IHttpTransportOptions): HttpTransport;
1241
- _createConnection(_ctx: ITransportConnectionContext): HttpConnection;
1242
- getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
1769
+ declare function defineChannel<const TO_ACCEPTOR extends readonly ActionDomain<any>[] = [], const TO_CONNECTOR extends readonly ActionDomain<any>[] = []>(options: {
1770
+ toAcceptor: TO_ACCEPTOR;
1771
+ toConnector: TO_CONNECTOR;
1772
+ }): IActionChannel<TO_ACCEPTOR, TO_CONNECTOR>;
1773
+ type TUnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
1774
+ type TDomainPushHandlers<D> = D extends ActionDomain<infer DEF> ? Partial<TWrappableDomainActionHandler<DEF>> : never;
1775
+ /**
1776
+ * The `onPush` map for a channel: the merged set of every acceptor→connector (`toConnector`) action
1777
+ * handler, each receiving the pushed action's input. Derived from the channel's `toConnectorDomains`, so
1778
+ * the keys and input types follow the channel definition.
1779
+ */
1780
+ type TChannelPushHandlers<TO_CONNECTOR extends readonly ActionDomain<any>[]> = TUnionToIntersection<TDomainPushHandlers<TO_CONNECTOR[number]>>;
1781
+ interface IConnectChannelOptions<TO_ACCEPTOR extends readonly ActionDomain<any>[], TO_CONNECTOR extends readonly ActionDomain<any>[]> {
1782
+ /** The shared channel — its `toAcceptorDomains`/`toConnectorDomains` drive all routing. */
1783
+ channel: IActionChannel<TO_ACCEPTOR, TO_CONNECTOR>;
1784
+ /**
1785
+ * Transports to the acceptor, in preference order (e.g. `[wsTransport, httpFallback]`). They all carry
1786
+ * the same `toAcceptor` domains; the manager prefers the first that's ready and falls through on
1787
+ * failure — so connector→acceptor works over whatever transport is available, WS or HTTP.
1788
+ */
1789
+ transports: Transport[];
1790
+ /** Handlers for the channel's acceptor→connector pushes. Optional — omit for a send-only connection. */
1791
+ onPush?: TChannelPushHandlers<TO_CONNECTOR>;
1792
+ /** Default per-action timeout for this connection. */
1793
+ defaultTimeout?: number;
1243
1794
  }
1244
- //#endregion
1245
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionFrameCrypto.d.ts
1246
1795
  /**
1247
- * Async AES-GCM transform for the `encrypted` security level. It wraps the opaque binary frame a
1248
- * session codec produces (it does NOT look inside it), encrypting on the way out and decrypting on the
1249
- * way in with the shared key established by the handshake.
1250
- *
1251
- * It is deliberately separate from the (synchronous) session `formatMessage`: WebCrypto is always
1252
- * Promise-based, so encryption has to happen at the transport's async I/O boundary — the connection
1253
- * encrypts after `session.outgoing()` and decrypts before `session.incoming()`. The `authenticated`
1254
- * and `none` levels use no crypto transform at all (frames go out as the session produced them).
1796
+ * Wire a connection to the acceptor straight from a channel: route the channel's `toAcceptor` domains to
1797
+ * the acceptor over `transports`, and register local handlers for its `toConnector` pushes from
1798
+ * `onPush`. The channel is the single source of truth for *what* is routed in each direction — the
1799
+ * caller only supplies the transport(s) and the push handlers, never restated domain lists. Pass several
1800
+ * transports to make the connector→acceptor path transport-agnostic (e.g. secure WS preferred, HTTP
1801
+ * fallback).
1255
1802
  *
1256
- * Wire shape of an encrypted frame: `pack([nonceBytes, ciphertextBytes])` msgpack carries the two
1257
- * binary fields with a couple of bytes of overhead, no base64 inflation.
1803
+ * Sugar over {@link ActionRuntime.connectTo}. Returns the acceptor handler so the caller can later
1804
+ * `clearTransportCache()` it.
1258
1805
  */
1259
- interface IActionFrameCrypto {
1260
- /** Encrypt one session frame for sending. */
1261
- encryptFrame(frame: Uint8Array): Promise<Uint8Array>;
1262
- /** Decrypt one received frame back to the session frame. Throws on a non-binary / malformed /
1263
- * tampered frame — the caller (transport) decides how to react (drop / close). */
1264
- decryptFrame(frame: string | ArrayBuffer | Uint8Array): Promise<Uint8Array>;
1265
- }
1266
- interface IActionFrameCryptoConfig {
1267
- link: ClientCryptoKeyLink;
1268
- /** The handshake-established link id for the remote (key + connection-registry id). */
1269
- linkedClientId: TTypeAndId;
1270
- }
1806
+ declare function connectChannel<TO_ACCEPTOR extends readonly ActionDomain<any>[], TO_CONNECTOR extends readonly ActionDomain<any>[]>(runtime: ActionRuntime, acceptorCoordinate: RuntimeCoordinate, options: IConnectChannelOptions<TO_ACCEPTOR, TO_CONNECTOR>): ConnectorHandler;
1807
+ type TDomainAcceptorCases<D, TConn> = D extends ActionDomain<infer DEF> ? { [ID in keyof DEF["actionSchema"] & string]?: TAcceptorConnectionCaseFn<DEF, ID, TConn> } : never;
1271
1808
  /**
1272
- * Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
1273
- * level. Keyed by the link + `linkedClientId`, so it reuses the cached shared AES-GCM key.
1809
+ * The connection-aware case map for a channel's acceptor side: the merged set of every
1810
+ * connector→acceptor (`toAcceptor`) action handler, each receiving the primed request plus the
1811
+ * originating connection. Derived from the channel's `toAcceptorDomains`, so the keys and input/output
1812
+ * types follow the channel.
1274
1813
  */
1275
- declare function createActionFrameCrypto({
1276
- link,
1277
- linkedClientId
1278
- }: IActionFrameCryptoConfig): IActionFrameCrypto;
1279
- //#endregion
1280
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/actionWsHandshake.d.ts
1281
- /** How much the channel protects after the handshake chosen by the consumer (perf vs security). */
1282
- declare enum ESecurityLevel {
1283
- /** No handshake; identity is self-asserted (fastest, dev / trusted networks). */
1284
- none = "none",
1285
- /** Handshake authenticates identity (sign/verify + key pin); frames stay plaintext over TLS. */
1286
- authenticated = "authenticated",
1287
- /** Authenticated handshake + every frame AES-GCM encrypted with the derived shared key. */
1288
- encrypted = "encrypted"
1289
- }
1290
- declare enum EHandshakeMessageType {
1291
- hello = "hello",
1292
- welcome = "welcome",
1293
- prove = "prove",
1294
- accept = "accept",
1295
- reject = "reject"
1296
- }
1297
- declare const vHsHello: v.ObjectSchema<{
1298
- readonly t: v.LiteralSchema<EHandshakeMessageType.hello, undefined>;
1299
- readonly protocol: v.StringSchema<undefined>;
1300
- readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
1301
- readonly dictionaryVersion: v.StringSchema<undefined>;
1302
- readonly client: v.ObjectSchema<{
1303
- readonly envId: v.StringSchema<undefined>;
1304
- readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1305
- readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1306
- }, undefined>;
1307
- readonly clientNonce: v.StringSchema<undefined>;
1308
- readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
1309
- readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
1310
- }, undefined>;
1311
- declare const vHsWelcome: v.ObjectSchema<{
1312
- readonly t: v.LiteralSchema<EHandshakeMessageType.welcome, undefined>;
1313
- readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
1314
- readonly dictionaryVersion: v.StringSchema<undefined>;
1315
- readonly server: v.ObjectSchema<{
1316
- readonly envId: v.StringSchema<undefined>;
1317
- readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1318
- readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1319
- }, undefined>;
1320
- readonly serverNonce: v.StringSchema<undefined>;
1321
- readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
1322
- readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
1323
- }, undefined>;
1324
- declare const vHsProve: v.ObjectSchema<{
1325
- readonly t: v.LiteralSchema<EHandshakeMessageType.prove, undefined>;
1326
- readonly signatureBase64: v.StringSchema<undefined>;
1327
- }, undefined>;
1328
- declare const vHsAccept: v.ObjectSchema<{
1329
- readonly t: v.LiteralSchema<EHandshakeMessageType.accept, undefined>;
1330
- readonly signatureBase64: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1331
- }, undefined>;
1332
- declare const vHsReject: v.ObjectSchema<{
1333
- readonly t: v.LiteralSchema<EHandshakeMessageType.reject, undefined>;
1334
- readonly reason: v.StringSchema<undefined>;
1335
- }, undefined>;
1336
- declare const vHandshakeMessage: v.VariantSchema<"t", [v.ObjectSchema<{
1337
- readonly t: v.LiteralSchema<EHandshakeMessageType.hello, undefined>;
1338
- readonly protocol: v.StringSchema<undefined>;
1339
- readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
1340
- readonly dictionaryVersion: v.StringSchema<undefined>;
1341
- readonly client: v.ObjectSchema<{
1342
- readonly envId: v.StringSchema<undefined>;
1343
- readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1344
- readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1345
- }, undefined>;
1346
- readonly clientNonce: v.StringSchema<undefined>;
1347
- readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
1348
- readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
1349
- }, undefined>, v.ObjectSchema<{
1350
- readonly t: v.LiteralSchema<EHandshakeMessageType.welcome, undefined>;
1351
- readonly securityLevel: v.PicklistSchema<[ESecurityLevel.none, ESecurityLevel.authenticated, ESecurityLevel.encrypted], undefined>;
1352
- readonly dictionaryVersion: v.StringSchema<undefined>;
1353
- readonly server: v.ObjectSchema<{
1354
- readonly envId: v.StringSchema<undefined>;
1355
- readonly perId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1356
- readonly insId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1357
- }, undefined>;
1358
- readonly serverNonce: v.StringSchema<undefined>;
1359
- readonly verifyPublicKey: v.CustomSchema<`ed25519::raw_base64::${string}`, undefined>;
1360
- readonly exchangePublicKey: v.OptionalSchema<v.CustomSchema<`x25519::raw_base64::${string}`, undefined>, undefined>;
1361
- }, undefined>, v.ObjectSchema<{
1362
- readonly t: v.LiteralSchema<EHandshakeMessageType.prove, undefined>;
1363
- readonly signatureBase64: v.StringSchema<undefined>;
1364
- }, undefined>, v.ObjectSchema<{
1365
- readonly t: v.LiteralSchema<EHandshakeMessageType.accept, undefined>;
1366
- readonly signatureBase64: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
1367
- }, undefined>, v.ObjectSchema<{
1368
- readonly t: v.LiteralSchema<EHandshakeMessageType.reject, undefined>;
1369
- readonly reason: v.StringSchema<undefined>;
1370
- }, undefined>], undefined>;
1371
- type THsHello = v.InferOutput<typeof vHsHello>;
1372
- type THsWelcome = v.InferOutput<typeof vHsWelcome>;
1373
- type THsProve = v.InferOutput<typeof vHsProve>;
1374
- type THsAccept = v.InferOutput<typeof vHsAccept>;
1375
- type THsReject = v.InferOutput<typeof vHsReject>;
1376
- type THandshakeMessage = v.InferOutput<typeof vHandshakeMessage>;
1377
- /** Serialize a handshake message for the wire (handshake frames are JSON — they aren't the hot path). */
1378
- declare function encodeHandshakeMessage(message: THandshakeMessage): string;
1379
- /** Parse + structurally validate an incoming handshake frame; `undefined` if it isn't one. */
1380
- declare function decodeHandshakeMessage(raw: string): THandshakeMessage | undefined;
1381
- /** Stable link id for a runtime coordinate — the key both the crypto link and the connection use. */
1382
- declare function runtimeLinkId(coordinate: IRuntimeCoordinate): TTypeAndId;
1814
+ type TChannelAcceptorCases<TO_ACCEPTOR extends readonly ActionDomain<any>[], TConn> = TUnionToIntersection<TDomainAcceptorCases<TO_ACCEPTOR[number], TConn>>;
1815
+ /**
1816
+ * Register an acceptor handler's execution for a channel straight from its definition: the channel's
1817
+ * `toAcceptor` domains are served together with one merged, connection-aware case map (each case gets
1818
+ * the primed request + the originating connection, as with
1819
+ * {@link AcceptorHandler.forConnectionDomainCases}). The domain list is taken from the channel,
1820
+ * never restated. Add the returned handler to the runtime alongside the acceptor handler:
1821
+ * ```ts
1822
+ * runtime.addHandlers([acceptChannelConnections(serverHandler, channel, { }), serverHandler]);
1823
+ * ```
1824
+ */
1825
+ declare function acceptChannelConnections<TO_ACCEPTOR extends readonly ActionDomain<any>[], TConn>(serverHandler: AcceptorHandler<TConn>, channel: IActionChannel<TO_ACCEPTOR, any>, cases: TChannelAcceptorCases<TO_ACCEPTOR, TConn>): ActionLocalHandler;
1826
+ interface IAcceptChannelOptions<TConn> {
1827
+ /**
1828
+ * Coordinate of the *connecting clients* (typically env-only, e.g. `RuntimeCoordinate.env("web_app")`),
1829
+ * used to score return-path dispatch back to the right connection.
1830
+ */
1831
+ clientEnv: RuntimeCoordinate;
1832
+ /**
1833
+ * One backing store for the acceptor's crypto identity *and* its trust-on-first-use verify-key pins
1834
+ * (their keys don't collide). Back it with persistent storage so identity + pins survive eviction.
1835
+ */
1836
+ storageAdapter: StorageAdapter;
1837
+ /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
1838
+ send: (connection: TConn, frame: string | Uint8Array | ArrayBuffer) => void;
1839
+ /**
1840
+ * The acceptor's crypto identity. Defaults to a fresh `ClientCryptoKeyLink` over `storageAdapter`.
1841
+ * Pass an existing link to share one identity across several acceptors on the same server (e.g. this
1842
+ * WebSocket acceptor and a secure-HTTP `createActionFetchHandler`), so they present the same keys.
1843
+ */
1844
+ link?: ClientCryptoKeyLink;
1845
+ /** Accepted level(s); defaults to negotiating any of none/authenticated/encrypted. */
1846
+ securityLevel?: ESecurityLevel | readonly ESecurityLevel[];
1847
+ /** Trust decision for a client's verify key; defaults to storage-backed TOFU over `storageAdapter`. */
1848
+ verifyKeyResolver?: IClientVerifyKeyResolver;
1849
+ /** Timeout (ms) applied to acceptor-initiated actions awaiting a client response. */
1850
+ defaultTimeout?: number;
1851
+ }
1383
1852
  /**
1384
- * Everything needed to re-derive the shared AES-GCM key for an `encrypted` link after a restart
1385
- * the remote's public keys + the HKDF salt/info used at handshake time. The local (server) key pair is
1386
- * recovered from its own persisted `ClientCryptoKeyLink` storage, so re-linking with this material
1387
- * yields the identical shared key without a fresh handshake.
1853
+ * Build the secure {@link AcceptorHandler} for a channel the accept-in counterpart to
1854
+ * {@link connectChannel}. It folds in the same boilerplate as {@link createSecureAcceptorHandler} (the
1855
+ * `ClientCryptoKeyLink` + storage-backed TOFU resolver from one `storageAdapter`, the channel's codec +
1856
+ * dictionary version, the `security` block from the runtime coordinate) but takes the `(runtime, channel,
1857
+ * options)` shape of the channel family. Pair it with {@link acceptChannelConnections} for execution:
1858
+ * ```ts
1859
+ * const acceptor = acceptChannel(runtime, gameChannel, { clientEnv, storageAdapter, send });
1860
+ * runtime.addHandlers([acceptChannelConnections(acceptor, gameChannel, { … }), acceptor]);
1861
+ * ```
1388
1862
  */
1389
- interface IHandshakeEncryptionKeyMaterial {
1390
- verifyPublicKey: TSerializedCryptoKeyData_Ed25519_Raw;
1391
- exchangePublicKey: TSerializedCryptoKeyData_X25519_Raw;
1392
- saltString: string;
1393
- infoString: string;
1394
- bindVerifyKeysIntoDerivation: boolean;
1395
- }
1396
- /** Outcome of a completed handshake — what the transport/handler needs to wire the channel. */
1397
- interface IHandshakeResult {
1398
- /** The crypto-link id (and connection-registry key) for the authenticated remote. */
1399
- linkedClientId: TTypeAndId;
1400
- /** The remote's authenticated coordinate. */
1401
- remote: IRuntimeCoordinate;
1402
- securityLevel: ESecurityLevel;
1403
- /** For the `encrypted` level: material to restore the shared key after eviction (persist it). */
1404
- encryptionKeyMaterial?: IHandshakeEncryptionKeyMaterial;
1863
+ declare function acceptChannel<TO_ACCEPTOR extends readonly ActionDomain<any>[], TO_CONNECTOR extends readonly ActionDomain<any>[], TConn = unknown>(runtime: ActionRuntime, channel: ISecureChannel<TO_ACCEPTOR, TO_CONNECTOR>, options: IAcceptChannelOptions<TConn>): AcceptorHandler<TConn>;
1864
+ //#endregion
1865
+ //#region src/ActionRuntime/Handler/PeerLink/Acceptor/Hibernation/createHibernatableWsServerAdapter.d.ts
1866
+ interface IHibernatableWsServerAdapterOptions<TConn> {
1867
+ /** The handler to drive (from `createSecureAcceptorHandler` or `createAcceptorHandler`). */
1868
+ handler: AcceptorHandler<TConn>;
1869
+ /** All currently-live connections — replayed on construction to rebuild bindings after a wake. */
1870
+ getConnections: () => TConn[];
1871
+ /** Read a connection's persisted binding (e.g. `(ws) => ws.deserializeAttachment()`). */
1872
+ getAttachment: (connection: TConn) => IAcceptorConnectionBinding | undefined;
1873
+ /** Persist a connection's binding when it is bound (e.g. `(ws, b) => ws.serializeAttachment(b)`). */
1874
+ setAttachment: (connection: TConn, binding: IAcceptorConnectionBinding) => void;
1405
1875
  }
1406
- interface IClientVerifyKeyResolveInput {
1407
- client: IRuntimeCoordinate;
1408
- verifyPublicKey: TSerializedCryptoKeyData_Ed25519_Raw;
1876
+ /**
1877
+ * The neutral lifecycle surface for a duplex (push-capable) acceptor: feed it each inbound frame and tell
1878
+ * it when a connection goes away. Carrier-agnostic — a WebSocket, a WebRTC data channel, or any other
1879
+ * duplex connection drives the same two methods.
1880
+ */
1881
+ interface IDuplexConnectionRouter<TConn> {
1882
+ /** Feed one inbound frame from a connection into the handler. */
1883
+ receive: (connection: TConn, frame: string | ArrayBuffer | Uint8Array) => void;
1884
+ /** Forget a connection (call on socket close/error). */
1885
+ drop: (connection: TConn) => void;
1409
1886
  }
1410
- interface IClientVerifyKeyResolver {
1887
+ /**
1888
+ * Wire the hibernation lifecycle for an acceptor handler on a transport whose connections outlive process
1889
+ * eviction (e.g. a Durable Object's hibernatable WebSockets). It owns persistence end to end:
1890
+ * registers `setAttachment` as the handler's connection-bound callback and immediately replays every
1891
+ * live connection's stored binding via `getAttachment`, so results/pushes still route after a wake.
1892
+ *
1893
+ * Layered on top of the generic {@link AcceptorHandler} — it touches only the handler's neutral
1894
+ * `setOnConnectionBound` / `rehydrateConnection` / `receive` / `dropConnection` surface, so no
1895
+ * hibernation concern leaks into the handler itself.
1896
+ *
1897
+ * Construct it once when the handler is built, then forward connection events:
1898
+ * ```ts
1899
+ * const duplex = createHibernatableWsServerAdapter({ handler, getConnections, getAttachment, setAttachment });
1900
+ * // webSocketMessage(ws, msg) => duplex.receive(ws, msg);
1901
+ * // webSocketClose/Error(ws) => duplex.drop(ws);
1902
+ * ```
1903
+ */
1904
+ declare function createHibernatableWsServerAdapter<TConn>(options: IHibernatableWsServerAdapterOptions<TConn>): IDuplexConnectionRouter<TConn>;
1905
+ //#endregion
1906
+ //#region src/ActionRuntime/Transport/Carrier/Carrier.types.d.ts
1907
+ /**
1908
+ * Carrier shapes — the only transport-specific surface a new protocol must implement. The secure
1909
+ * session (handshake + frame crypto + codec) and the action routing on top of it are carrier-agnostic;
1910
+ * a carrier just moves frames. Two shapes capture every carrier:
1911
+ *
1912
+ * - {@link IDuplexCarrier} — a persistent, push-capable byte stream (WebSocket, WebRTC `RTCDataChannel`,
1913
+ * Bluetooth GATT, an in-memory pipe). Either side can send at any time, so it supports server→client
1914
+ * pushes (the return path + broadcast).
1915
+ * - {@link IExchangeCarrier} — a request → single-correlated-reply carrier with no unsolicited push
1916
+ * (HTTP, and anything request/response-shaped). The reply rides the response to its own request.
1917
+ *
1918
+ * Frames are `string` (text — handshake control messages and JSON action frames) or binary
1919
+ * (`Uint8Array`/`ArrayBuffer` — the optimized binary wire / encrypted frames).
1920
+ */
1921
+ type TFrame$1 = string | Uint8Array | ArrayBuffer;
1922
+ /**
1923
+ * A bidirectional, push-capable byte stream. Reduces every duplex carrier to "open, send bytes, receive
1924
+ * bytes, close" — a WebSocket, a WebRTC data channel, a Bluetooth characteristic, or an in-memory pipe
1925
+ * all satisfy this, so the identical secure session runs over each.
1926
+ */
1927
+ interface IDuplexCarrier {
1928
+ /** Resolves once the carrier is open and ready to send; rejects if it closes/errors before opening. */
1929
+ readonly ready: Promise<void>;
1930
+ /** Whether the carrier is currently open (a synchronous guard before `send`). */
1931
+ isOpen(): boolean;
1932
+ /** Write one frame to the peer. */
1933
+ send(frame: TFrame$1): void;
1411
1934
  /**
1412
- * Decide whether a presented verify key is trusted for a client identity. The signature is already
1413
- * verified by the time this runs, so this is purely the identity-pinning decision. Swap in a
1414
- * persistent / pre-provisioned implementation without touching the protocol.
1935
+ * Register the carrier's handlers. Called exactly once by the session after `ready`. `onMessage`
1936
+ * receives every inbound frame; `onClose` fires when the carrier goes away.
1415
1937
  */
1416
- resolve(input: IClientVerifyKeyResolveInput): Promise<{
1417
- trusted: boolean;
1418
- reason?: string;
1419
- }>;
1938
+ attach(handlers: {
1939
+ onMessage: (frame: TFrame$1) => void;
1940
+ onClose: () => void;
1941
+ onError?: (error: unknown) => void;
1942
+ }): void;
1943
+ /** Close the carrier deliberately (a teardown). */
1944
+ close(): void;
1945
+ /** Optional human-readable endpoint for the devtools route chip. */
1946
+ readonly label?: string;
1420
1947
  }
1421
1948
  /**
1422
- * In-memory trust-on-first-use resolver: trusts (and pins) the first verify key seen for a client
1423
- * identity, then rejects a different key for that identity. The default; replace with a storage-backed
1424
- * resolver for cross-restart pinning (see Step 5).
1949
+ * A request → single-correlated-reply carrier with no unsolicited push (HTTP). One `exchange` sends a
1950
+ * frame and resolves with exactly the one reply frame for it; the carrier itself correlates them (the
1951
+ * HTTP transaction), so no correlation id is needed on the wire.
1425
1952
  */
1426
- declare function createInMemoryTofuVerifyKeyResolver(): IClientVerifyKeyResolver;
1953
+ interface IExchangeCarrier {
1954
+ /** Send one frame, await the single correlated reply frame. */
1955
+ exchange(frame: TFrame$1, opts?: {
1956
+ signal?: AbortSignal;
1957
+ }): Promise<TFrame$1>;
1958
+ /** Optional human-readable endpoint for the devtools route chip. */
1959
+ readonly label?: string;
1960
+ }
1961
+ type TCarrier = IDuplexCarrier | IExchangeCarrier;
1427
1962
  /**
1428
- * Storage-backed trust-on-first-use resolver: pins survive process restarts / Durable Object eviction
1429
- * (e.g. back it with `createDurableObjectStorageAdapter`). Same policy as the in-memory variant — trust
1430
- * + pin the first verify key per client identity, reject a different one thereafter.
1963
+ * A reusable opener for a {@link IDuplexCarrier} plus the per-action metadata a duplex transport needs.
1964
+ * Built by the small carrier factories (`wsCarrier`, `rtcCarrier`, `inMemoryCarrier`) and handed to
1965
+ * {@link secureTransport} / `LinkTransport` so adding a new carrier is "write one of these", nothing
1966
+ * else.
1431
1967
  */
1432
- declare function createStorageTofuVerifyKeyResolver(storageAdapter: StorageAdapter): IClientVerifyKeyResolver;
1433
- interface IClientHandshakeConfig {
1434
- link: ClientCryptoKeyLink;
1435
- localCoordinate: IRuntimeCoordinate;
1436
- dictionaryVersion: string;
1437
- securityLevel: ESecurityLevel;
1968
+ interface IDuplexCarrierSource {
1969
+ /** Open (or reuse) the carrier for an action. */
1970
+ open: (input: ITransportRouteActionParams) => IDuplexCarrier;
1971
+ /** Keys identifying a reusable carrier, so one carrier is shared across actions to the same peer. */
1972
+ getCacheKey?: (input: ITransportRouteActionParams) => string[];
1973
+ /** Devtools route info for an action routed over this carrier. */
1974
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
1975
+ /** Short carrier-kind label for the devtools chip (e.g. `"ws"`, `"webrtc"`, `"memory"`). */
1976
+ readonly carrierLabel: string;
1438
1977
  }
1439
- declare function createClientHandshake(config: IClientHandshakeConfig): {
1440
- createHello(): Promise<THsHello>;
1441
- onWelcome(welcome: THsWelcome): Promise<THsProve>;
1442
- onAccept(accept: THsAccept): Promise<IHandshakeResult>;
1443
- };
1444
- interface IServerHandshakeConfig {
1445
- link: ClientCryptoKeyLink;
1446
- localCoordinate: IRuntimeCoordinate;
1447
- dictionaryVersion: string;
1978
+ /**
1979
+ * The exchange-shape counterpart to {@link IDuplexCarrierSource}: a reusable opener for an
1980
+ * {@link IExchangeCarrier} plus the per-action metadata an exchange transport needs. Built by
1981
+ * `httpCarrier` and handed to {@link secureTransport} — adding a new request/reply protocol is "write
1982
+ * one of these". The `shape` tag lets {@link secureTransport} pick the duplex vs exchange transport.
1983
+ */
1984
+ interface IExchangeCarrierSource {
1985
+ /** Discriminant so a generic factory can tell an exchange source from a duplex one. */
1986
+ readonly shape: "exchange";
1987
+ /** Open (or reuse) the carrier for an action. */
1988
+ open: (input: ITransportRouteActionParams) => IExchangeCarrier;
1989
+ /** Keys identifying a reusable carrier, so one carrier is shared across actions to the same peer. */
1990
+ getCacheKey?: (input: ITransportRouteActionParams) => string[];
1991
+ /** Devtools route info for an action routed over this carrier. */
1992
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
1993
+ /** Short carrier-kind label for the devtools chip (e.g. `"http"`). */
1994
+ readonly carrierLabel: string;
1995
+ }
1996
+ //#endregion
1997
+ //#region src/ActionRuntime/Transport/Carrier/AcceptorCarrier.types.d.ts
1998
+ /**
1999
+ * Acceptor-side carrier descriptors — the accept-in dual of the connector's {@link IDuplexCarrierSource}
2000
+ * / {@link IExchangeCarrierSource}. Where a connector source knows how to *open* a carrier to a peer, an
2001
+ * acceptor carrier knows how to *serve* one peer's traffic on this server. Both shapes are carrier-neutral
2002
+ * about security: `serveChannel` builds the crypto identity (link + TOFU resolver) and the security block
2003
+ * once from `(runtime, channel)` and fans it across every carrier, so a carrier descriptor never restates
2004
+ * it.
2005
+ *
2006
+ * Two shapes mirror the connector side:
2007
+ *
2008
+ * - {@link IDuplexAcceptorCarrier} — a persistent, push-capable byte stream (WebSocket, WebRTC, …). It can
2009
+ * push acceptor→connector, so it carries the return path and broadcasts, and it may need an upgrade step
2010
+ * (e.g. a Durable Object's `WebSocketPair`) and optional hibernation persistence.
2011
+ * - {@link IExchangeAcceptorCarrier} — a request → single-correlated-reply carrier with no unsolicited push
2012
+ * (HTTP). The reply rides the response to its own request; there is nothing to push and nothing to
2013
+ * upgrade.
2014
+ */
2015
+ /**
2016
+ * Persistence + replay hooks for a duplex carrier whose connections outlive process eviction (e.g. a
2017
+ * Durable Object's hibernatable WebSockets). Optional — omit for a transport that never hibernates. The
2018
+ * same surface {@link createHibernatableWsServerAdapter} consumes, minus the handler it's bound to.
2019
+ */
2020
+ interface IAcceptorHibernation<TConn> {
2021
+ /** All currently-live connections — replayed on build to rebuild bindings after a wake. */
2022
+ getConnections: () => TConn[];
2023
+ /** Read a connection's persisted binding (e.g. `(ws) => ws.deserializeAttachment()`). */
2024
+ getAttachment: (connection: TConn) => IAcceptorConnectionBinding | undefined;
2025
+ /** Persist a connection's binding when it is bound (e.g. `(ws, b) => ws.serializeAttachment(b)`). */
2026
+ setAttachment: (connection: TConn, binding: IAcceptorConnectionBinding) => void;
2027
+ }
2028
+ /**
2029
+ * A duplex carrier is also its own lifecycle handle: once it has been passed to `serveChannel`, feed each
2030
+ * inbound frame to {@link receive} and forget a connection on close/error with {@link drop}. This is how a
2031
+ * server with *several* duplex carriers routes each connection's traffic to the right one — you hold the
2032
+ * carrier you created and feed it directly, so no per-carrier router lookup is needed. The methods throw if
2033
+ * called before the carrier is served. (`serveChannel` binds the live router via {@link _activate}.)
2034
+ */
2035
+ interface IDuplexCarrierLifecycle<TConn> {
2036
+ /** Feed one inbound frame from a live connection into the server. Throws until the carrier is served. */
2037
+ receive(connection: TConn, frame: TFrame$1): void;
2038
+ /** Forget a connection on close/error. No-op until the carrier is served. */
2039
+ drop(connection: TConn): void;
2040
+ /** @internal `serveChannel` binds this carrier's live connection router here. */
2041
+ _activate(router: IDuplexConnectionRouter<TConn>): void;
2042
+ }
2043
+ /**
2044
+ * A duplex (push-capable) carrier on the acceptor side. Describes how to write a frame back to a live
2045
+ * connection, how to perform the transport-specific upgrade that admits one, which requests are such
2046
+ * upgrades, and (optionally) how to persist bindings across hibernation. Built by `wsAcceptorCarrier` and
2047
+ * handed to `serveChannel`'s `carriers` list — the returned carrier is also its own lifecycle handle (see
2048
+ * {@link IDuplexCarrierLifecycle}).
2049
+ */
2050
+ interface IDuplexAcceptorCarrier<TConn = unknown> extends IDuplexCarrierLifecycle<TConn> {
2051
+ /** Discriminant so `serveChannel` can tell a duplex carrier from an exchange one. */
2052
+ readonly shape: ETransportShape.duplex;
1448
2053
  /**
1449
- * The level(s) this server accepts. A single level is strict (the client must match). An array is a
1450
- * negotiable allowed set the server adopts whichever level the client requests, as long as it's in
1451
- * the set (lets one backend serve `authenticated` and `encrypted` clients at once). `none` in the set
1452
- * is handled by the transport/handler (a `none` client never reaches the handshake).
2054
+ * Whether each connection runs the secure handshake (default `true`). `false` makes it a plain duplex
2055
+ * carrier: connections speak the channel's wire codec directly with a self-asserted identity no
2056
+ * handshake, pins, or encryption (the duplex dual of `httpAcceptorCarrier({ secure: false })`). A plain
2057
+ * carrier ignores the central crypto identity, so it needs no `storage` on `serveChannel`.
1453
2058
  */
1454
- securityLevel: ESecurityLevel | readonly ESecurityLevel[];
1455
- /** Trust decision for a client's verify key. Defaults to in-memory TOFU. */
1456
- verifyKeyResolver?: IClientVerifyKeyResolver;
2059
+ secure?: boolean;
2060
+ /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
2061
+ send: (connection: TConn, frame: TFrame$1) => void;
2062
+ /**
2063
+ * Perform the transport-specific upgrade for an inbound request, returning its raw response (e.g. a
2064
+ * Durable Object's `new WebSocketPair()` + `ctx.acceptWebSocket()` → a `101`). Omit for a carrier that
2065
+ * is fed connections out of band (the server then only routes frames via {@link receive}/{@link drop}).
2066
+ */
2067
+ upgrade?: (request: Request, url: URL) => Response | Promise<Response>;
2068
+ /**
2069
+ * Whether an inbound request is an upgrade for this carrier. Defaults to an `Upgrade: websocket` header.
2070
+ * Only consulted when {@link upgrade} is present.
2071
+ */
2072
+ isUpgrade?: (request: Request, url: URL) => boolean;
2073
+ /** Optional persistence + replay for connections that survive eviction (Durable Object hibernation). */
2074
+ hibernation?: IAcceptorHibernation<TConn>;
2075
+ /** Short carrier-kind label for the devtools chip (e.g. `"ws"`, `"webrtc"`). */
2076
+ readonly carrierLabel: string;
1457
2077
  }
1458
- declare function createServerHandshake(config: IServerHandshakeConfig): {
1459
- onHello(hello: THsHello): Promise<THsWelcome | THsReject>;
1460
- onProve(prove: THsProve): Promise<THsAccept | THsReject>; /** The completed handshake result once `onProve` has accepted, else `undefined`. */
1461
- getResult(): IHandshakeResult | undefined;
1462
- };
2078
+ /**
2079
+ * An exchange (request/reply) carrier on the acceptor side, over web-standard `Request`/`Response`. By
2080
+ * default it speaks the *secure* exchange protocol (handshake token session encrypted frames), whose
2081
+ * identity is supplied centrally by `serveChannel`. Set {@link secure} to `false` for a plain endpoint
2082
+ * that POSTs the raw action wire and returns the result inline — the request/reply dual of the connector's
2083
+ * `plainTransport({ carrier: httpCarrier(...) })`. So a server can pair a secure duplex (WebSocket) with a
2084
+ * plain HTTP fallback on the same runtime. Built by `httpAcceptorCarrier`.
2085
+ */
2086
+ interface IExchangeAcceptorCarrier {
2087
+ /** Discriminant so `serveChannel` can tell an exchange carrier from a duplex one. */
2088
+ readonly shape: ETransportShape.exchange;
2089
+ /**
2090
+ * Whether this endpoint runs the secure exchange protocol (default `true`). `false` makes it a plain
2091
+ * endpoint: the body is the raw action wire and the result is the response body — no handshake, token,
2092
+ * or encryption. A plain endpoint ignores the central crypto identity entirely.
2093
+ */
2094
+ secure?: boolean;
2095
+ /** Which requests carry an action exchange envelope on `POST`. Defaults to `serveChannel`'s path match. */
2096
+ isActionPath?: (url: URL) => boolean;
2097
+ /**
2098
+ * CORS headers merged onto every response (a preflight `OPTIONS` is answered `204`). Defaults to the
2099
+ * permissive `*` set; pass `false` to attach no CORS headers at all.
2100
+ */
2101
+ cors?: Record<string, string> | false;
2102
+ /** Plain mode only: use the error's HTTP status for failures (default `true`). Ignored when secure. */
2103
+ useErrorStatus?: boolean;
2104
+ /** Short carrier-kind label for the devtools chip (e.g. `"http"`). */
2105
+ readonly carrierLabel: string;
2106
+ }
2107
+ type TAcceptorCarrier<TConn = unknown> = IDuplexAcceptorCarrier<TConn> | IExchangeAcceptorCarrier;
2108
+ /**
2109
+ * Narrow an acceptor carrier to the exchange shape via its `shape` discriminant — the one branch
2110
+ * `serveChannel` uses to pick the duplex (push-capable) vs exchange (request/reply) wiring. A duplex
2111
+ * carrier carries `shape: ETransportShape.duplex`, so the `else` branch is the duplex one.
2112
+ */
2113
+ declare function isExchangeAcceptorCarrier<TConn>(carrier: TAcceptorCarrier<TConn>): carrier is IExchangeAcceptorCarrier;
1463
2114
  //#endregion
1464
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/TransportWebSocket.types.d.ts
2115
+ //#region src/ActionRuntime/Channel/serveChannel.d.ts
2116
+ interface IServeChannelOptions<TConn> {
2117
+ /**
2118
+ * Coordinate of the *connecting clients* (typically env-only, e.g. `RuntimeCoordinate.env("web_app")`),
2119
+ * used to score return-path dispatch back to the right connection.
2120
+ */
2121
+ clientEnv: RuntimeCoordinate;
2122
+ /**
2123
+ * One backing store for the server's crypto identity *and* its trust-on-first-use verify-key pins
2124
+ * (their keys don't collide). It is built once and shared across every carrier, so the WebSocket and the
2125
+ * secure-HTTP endpoint present the exact same verify/exchange keys and trust the same pinned clients.
2126
+ * Back it with persistent storage (e.g. a Durable Object's storage) so identity + pins survive eviction.
2127
+ *
2128
+ * Required only when at least one carrier is secure (the default). A fully-plain server (every carrier
2129
+ * `secure: false`) needs no storage and may omit it.
2130
+ */
2131
+ storage?: StorageAdapter;
2132
+ /**
2133
+ * The carriers this channel is served over — the accept-in dual of `connectChannel`'s `transports`.
2134
+ * Build them with `wsAcceptorCarrier` / `httpAcceptorCarrier`. Any number of duplex (push-capable)
2135
+ * carriers are supported (e.g. WebSocket + WebRTC), plus at most one exchange (request/reply) carrier;
2136
+ * all share one crypto identity and one runtime, and each result/push routes back over the carrier its
2137
+ * client connected on.
2138
+ */
2139
+ carriers: readonly TAcceptorCarrier<TConn>[];
2140
+ /** Your execution handlers (e.g. the local handler holding the action cases). Registered for you. */
2141
+ handlers?: TActionRuntimeHandler[];
2142
+ /**
2143
+ * The server's crypto identity. Defaults to a fresh {@link ClientCryptoKeyLink} over `storage`. Pass an
2144
+ * existing link only to share identity with acceptors built outside this call.
2145
+ */
2146
+ link?: ClientCryptoKeyLink;
2147
+ /** Accepted level(s) for every carrier; defaults to negotiating any of none/authenticated/encrypted. */
2148
+ securityLevel?: ESecurityLevel | readonly ESecurityLevel[];
2149
+ /** Trust decision for a client's verify key; defaults to storage-backed TOFU over `storage`. */
2150
+ verifyKeyResolver?: IClientVerifyKeyResolver;
2151
+ /** Timeout (ms) applied to server-initiated actions awaiting a client response. */
2152
+ defaultTimeout?: number;
2153
+ }
1465
2154
  /**
1466
- * Client-side secure-channel config for a WebSocket. When present (and `securityLevel !== none`), the
1467
- * connection runs the {@link createClientHandshake} handshake during initialization authenticating
1468
- * the identity and, for the `encrypted` level, deriving a shared key that encrypts every action frame.
2155
+ * One server serving a secure channel over several carriers the accept-in dual of `connectChannel`,
2156
+ * returned by {@link serveChannel}. Wire its surface straight to the host's request/socket events.
1469
2157
  */
1470
- interface IWsClientSecureChannel {
1471
- securityLevel: ESecurityLevel;
1472
- /** This client's crypto identity (verify + exchange key pairs, optionally persisted). */
2158
+ interface IChannelServer<TConn> {
2159
+ /**
2160
+ * The duplex acceptor handlers one per duplex carrier, in carrier order (empty if none). For pushing,
2161
+ * prefer {@link pushToClient} (it resolves the owning handler); reach for these for cross-carrier work
2162
+ * like a per-handler `broadcast`.
2163
+ */
2164
+ handlers: AcceptorHandler<TConn>[];
2165
+ /**
2166
+ * Unified request handler: answers the CORS preflight, performs the duplex upgrade for an upgrade
2167
+ * request, serves a secure-exchange action `POST`, else `404`. Forward the host's `fetch` straight to it.
2168
+ */
2169
+ fetch: (request: Request) => Promise<Response>;
2170
+ /**
2171
+ * Convenience lifecycle for the *sole* duplex carrier — `receive` inbound frames, `drop` on close. Set
2172
+ * only when exactly one duplex carrier was provided; with several, feed each carrier handle directly
2173
+ * (every duplex carrier is its own lifecycle handle). `undefined` when there are zero or multiple.
2174
+ */
2175
+ duplex?: IDuplexConnectionRouter<TConn>;
2176
+ /**
2177
+ * Push a server-initiated action to a connected client (the runtime is bound in, so unlike
2178
+ * {@link AcceptorHandler.pushToClient} you pass only the target + request). It routes through the duplex
2179
+ * carrier the target connected on. Throws if no duplex carrier currently holds the target.
2180
+ */
2181
+ pushToClient: <DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(target: TConn | RuntimeCoordinate, request: ActionPayload_Request<DOM, ID>, options?: {
2182
+ timeout?: number;
2183
+ }) => RunningAction<DOM, ID>;
2184
+ }
2185
+ /**
2186
+ * Serve a secure channel over one or more carriers from a single call — the accept-in dual of
2187
+ * `connectChannel`. It builds the crypto identity (a {@link ClientCryptoKeyLink} + a storage-backed TOFU
2188
+ * resolver) and the security block (coordinate, dictionary version, accepted levels) *once* from
2189
+ * `(runtime, channel)` and fans them across every carrier, so the WebSocket and the secure-HTTP endpoint
2190
+ * can never drift apart. It registers your handlers (plus the duplex acceptor it builds) on the runtime,
2191
+ * wires hibernation when the duplex carrier declares it, and returns a single {@link IChannelServer} whose
2192
+ * `fetch` / `duplex` / `pushToClient` you forward straight to the host:
2193
+ * ```ts
2194
+ * const server = serveChannel(runtime, channel, {
2195
+ * clientEnv, storage,
2196
+ * carriers: [wsAcceptorCarrier({ send, upgrade, hibernation }), httpAcceptorCarrier()],
2197
+ * handlers: [localHandler],
2198
+ * });
2199
+ * // fetch(req) => server.fetch(req)
2200
+ * // webSocketMessage(conn, m) => server.duplex?.receive(conn, m)
2201
+ * // webSocketClose/Error(conn) => server.duplex?.drop(conn)
2202
+ * ```
2203
+ *
2204
+ * `TConn` (the live-connection token a duplex carrier hands back through `send`/`receive`/`drop`) is
2205
+ * inferred from the carriers — `WebSocket` for `wsAcceptorCarrier`, the data-channel type for a WebRTC
2206
+ * carrier, and so on — so it stays carrier-agnostic.
2207
+ */
2208
+ declare function serveChannel<TO_ACCEPTOR extends readonly ActionDomain<any>[] = readonly ActionDomain<any>[], TO_CONNECTOR extends readonly ActionDomain<any>[] = readonly ActionDomain<any>[], TConn = unknown>(runtime: ActionRuntime, channel: ISecureChannel<TO_ACCEPTOR, TO_CONNECTOR>, options: IServeChannelOptions<TConn>): IChannelServer<TConn>;
2209
+ //#endregion
2210
+ //#region src/ActionRuntime/Transport/SecureSession/exchangeAcceptor.d.ts
2211
+ /** Acceptor secure config for the exchange (HTTP) endpoint — same identity an `AcceptorHandler` uses. */
2212
+ interface IExchangeAcceptorSecurity {
2213
+ /** This acceptor's crypto identity (verify + exchange key pairs, optionally persisted). */
1473
2214
  link: ClientCryptoKeyLink;
1474
- /** This client's runtime coordinate — its authenticated identity to the server. */
2215
+ /** This acceptor's coordinate — its identity to clients during the handshake. */
1475
2216
  localCoordinate: IRuntimeCoordinate;
1476
- /** Wire dictionary version; the server rejects the handshake on a mismatch. */
2217
+ /** Wire dictionary version; the handshake rejects a client on a mismatch. */
1477
2218
  dictionaryVersion: string;
2219
+ /** Accepted level(s) — a single level is strict, an array is a negotiable allowed set. */
2220
+ securityLevel: ESecurityLevel | readonly ESecurityLevel[];
2221
+ /** Trust decision for a client's verify key (defaults to in-memory TOFU inside the handshake). */
2222
+ verifyKeyResolver?: IClientVerifyKeyResolver;
1478
2223
  }
1479
- interface IActionTransportReadyData_Ws extends IActionTransportReadyData_Base {
1480
- formatMessage?: {
1481
- /**
1482
- * Pack an outgoing action payload. Return a `string` for text frames (JSON) or a binary
1483
- * `Uint8Array`/`ArrayBuffer` for optimized binary frames (e.g. msgpackr).
1484
- */
1485
- outgoing: (input: ITransportRouteActionParams) => string | Uint8Array | ArrayBuffer;
1486
- /**
1487
- * Unpack an incoming frame back into the wire JSON object the runtime hydrates + validates.
1488
- * Return `undefined` to defer to the connection's built-in JSON parser — this is how binary
1489
- * adapters stay backward compatible with plain-JSON clients on the same socket.
1490
- */
1491
- incoming?: (input: string | ArrayBuffer | Uint8Array | Blob) => TActionPayload_Any_JsonObject<any, any> | undefined;
1492
- };
1493
- ws: WebSocket;
1494
- /** Optional authenticated/encrypted channel; the connection runs the handshake during init. */
1495
- secureChannel?: IWsClientSecureChannel;
2224
+ interface IExchangeAcceptorConfig {
2225
+ security: IExchangeAcceptorSecurity;
2226
+ /** The runtime that executes an inbound action wire and produces its result. */
2227
+ runtime: ActionRuntime;
1496
2228
  }
1497
- interface IActionTransportInitialized_Ws extends IActionTransportInitialized<ITransportRouteActionParams, IActionTransportReadyData_Ws> {}
1498
- interface IActionTransportDef_Ws extends IActionTransportDef<ETransportType.ws, IActionTransportInitialized_Ws> {}
1499
- //#endregion
1500
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsAdapter.d.ts
1501
2229
  /**
1502
- * Builds a *stateless* `formatMessage` pipeline for {@link WebSocketTransport}, packing action
1503
- * payloads into a compact msgpackr binary frame instead of JSON. The `domain`/`id` route collapses to
1504
- * a single integer drawn from a shared dictionary; `form`/`type`, the recomputable
1505
- * `inputHash`/`outputHash`, and the per-frame `context.routing`/`context.timeCreated` are all dropped
1506
- * (see {@link ENVELOPE}).
1507
- *
1508
- * No validation runs here: `incoming` blindly reconstructs the wire JSON shape and hands it back to
1509
- * the connection, which flows into `ActionRuntime` → `domain.hydrateAnyAction()` where the Valibot
1510
- * schemas validate it exactly as they would for a JSON frame.
1511
- *
1512
- * Both ends of the socket MUST construct the adapter with the same domains in the same order — the
1513
- * integer dictionary is positional. Mismatched dictionaries will route to the wrong action.
2230
+ * Acceptor (accept-in) side of the secure exchange protocol the HTTP counterpart to
2231
+ * {@link AcceptorSecureSession}. Each POST body is one {@link decodeExchangeRequest} envelope; the
2232
+ * acceptor drives the server handshake over the two `hs` POSTs (correlated by `hsid`, since stateless
2233
+ * requests can't rely on channel ordering), mints a session **token** on accept, and on every later `act`
2234
+ * POST resolves the session by token, decrypts the body (at `encrypted`), routes it through the runtime,
2235
+ * and returns the (encrypted) result inline as the reply.
1514
2236
  *
1515
- * Because `incoming` returns `undefined` for text frames, a binary server can still serve plain-JSON
1516
- * clients on the same runtime (the connection falls back to its built-in JSON parser).
2237
+ * Sessions and in-flight handshakes are held in memory — fine for a single-instance server. (Surviving a
2238
+ * Durable-Object eviction would persist each token's `keyMaterial` and re-derive the key on a miss, the
2239
+ * same primitive `AcceptorSecureSession.rehydrate` uses; left as a follow-up.)
1517
2240
  */
1518
- declare function createBinaryWsAdapter(domains: ActionDomain<any>[]): NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
2241
+ declare class ExchangeAcceptor {
2242
+ private readonly _security;
2243
+ private readonly _runtime;
2244
+ private readonly _allowedLevels;
2245
+ private readonly _noneAllowed;
2246
+ private readonly _pendingHandshakes;
2247
+ private readonly _sessions;
2248
+ constructor(config: IExchangeAcceptorConfig);
2249
+ /** Process one POST body (an exchange envelope), returning the reply body to send back. */
2250
+ handlePost(body: string): Promise<string>;
2251
+ private _handleHandshake;
2252
+ private _handleAction;
2253
+ private _err;
2254
+ }
1519
2255
  //#endregion
1520
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/createBinaryWsSessionFactory.d.ts
1521
- type TFormatMessage = NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
1522
- interface IBinaryWsSessionOptions {
1523
- /** Override how long an unresolved correlation is retained before being swept (ms). */
1524
- correlationTtlMs?: number;
2256
+ //#region src/ActionRuntime/Handler/PeerLink/Acceptor/createActionFetchHandler.d.ts
2257
+ interface IActionFetchHandlerOptions {
2258
+ /**
2259
+ * CORS headers merged onto every response (a preflight `OPTIONS` is answered `204` with them).
2260
+ * Defaults to permissive `*`; pass `false` to attach no CORS headers at all.
2261
+ */
2262
+ cors?: Record<string, string> | false;
2263
+ /** Which requests carry an action wire on `POST`. Default: pathname ends with `/action`. */
2264
+ isActionPath?: (url: URL) => boolean;
2265
+ /** Which requests are WebSocket upgrades. Default: pathname ends with `/ws`. */
2266
+ isWebSocketPath?: (url: URL) => boolean;
2267
+ /**
2268
+ * Whether a request is a WebSocket upgrade for this endpoint, given the whole request (not just the
2269
+ * URL). When set it *replaces* the default gate (an `Upgrade: websocket` header on an
2270
+ * {@link isWebSocketPath} match) — use it when the discriminant needs a header or method, not only the
2271
+ * path. Only consulted when {@link onWebSocketUpgrade} is present.
2272
+ */
2273
+ isWebSocketUpgrade?: (request: Request, url: URL) => boolean;
2274
+ /**
2275
+ * Perform the transport-specific WebSocket upgrade (e.g. a Durable Object's
2276
+ * `new WebSocketPair()` + `ctx.acceptWebSocket()` returning a `101`). Omit for HTTP-only endpoints.
2277
+ * Its response is returned as-is — a `101` upgrade carries no CORS headers.
2278
+ */
2279
+ onWebSocketUpgrade?: (request: Request, url: URL) => Response | Promise<Response>;
2280
+ /** Forwarded to `ActionPayload_Result.toHttpResponse` — use the error's HTTP status (default true). */
2281
+ useErrorStatus?: boolean;
2282
+ /**
2283
+ * Enable the secure exchange protocol (handshake + token sessions + body encryption) on the `/action`
2284
+ * endpoint, mirroring an `AcceptorHandler`'s `security`. The matching connector is
2285
+ * `secureTransport({ carrier: httpCarrier(...), securityLevel })`. When omitted, the endpoint speaks
2286
+ * today's plain protocol (the raw action wire is POSTed and the result is the response body).
2287
+ */
2288
+ security?: IExchangeAcceptorSecurity;
1525
2289
  }
1526
2290
  /**
1527
- * Builds a factory of *stateful, per-connection* codecs for {@link WebSocketTransport} /
1528
- * `ActionServerHandler` the maximally compact binary wire. Call the returned factory once per live
1529
- * connection (each socket on the client, each accepted connection on the server) so every channel
1530
- * gets its own correlation + identity state.
1531
- *
1532
- * On top of everything {@link createBinaryWsAdapter} drops, a session also drops:
1533
- * - **`cuid`** — replaced by a per-connection integer correlation id. The initiator maps it to its
1534
- * real cuid; the responder echoes it; each side reconstructs the cuid from its own map. Correlation
1535
- * only needs to be unique per socket, so a counter suffices.
1536
- * - **`originClient` after the first request** — the first request each side sends carries its
1537
- * identity; the peer remembers it and injects it into later frames. Replies omit it entirely (a
1538
- * reply carries the initiator's own origin, which the initiator already knows).
1539
- *
1540
- * Both ends MUST build the factory from the same domains in the same order (positional dictionary).
1541
- * Text frames still return `undefined` from `incoming`, so JSON clients remain interoperable.
2291
+ * Build the `fetch` handler a server/Durable-Object exposes for action traffic, folding in the
2292
+ * boilerplate every endpoint repeats: CORS (incl. the `OPTIONS` preflight), routing the `/action`
2293
+ * `POST` body through the runtime (`handleActionPayloadWire` `waitForResultPayload`
2294
+ * `toHttpResponse`), an optional WebSocket-upgrade hook, and a `404` fallback.
1542
2295
  *
1543
- * Hibernation note: after a server connection is evicted its session resets, so a still-connected
1544
- * client (whose session persists) will keep omitting `originClient`. The server must therefore restore
1545
- * the connection→client binding from its own store (see `ActionServerHandler.rehydrateConnection`) and
1546
- * inject `originClient` from there — the session alone can't recover it.
2296
+ * It only touches web-standard `Request`/`Response`, so it stays transport-agnostic the one
2297
+ * environment-specific bit (the WS upgrade) is injected via {@link IActionFetchHandlerOptions.onWebSocketUpgrade}:
2298
+ * ```ts
2299
+ * this.fetchHandler = createActionFetchHandler(this.runtime, {
2300
+ * onWebSocketUpgrade: () => {
2301
+ * const pair = new WebSocketPair();
2302
+ * this.ctx.acceptWebSocket(pair[1]);
2303
+ * return new Response(null, { status: 101, webSocket: pair[0] });
2304
+ * },
2305
+ * });
2306
+ * // async fetch(request) { return this.fetchHandler(request); }
2307
+ * ```
1547
2308
  */
1548
- declare function createBinaryWsSessionFactory(domains: ActionDomain<any>[], options?: IBinaryWsSessionOptions): () => TFormatMessage;
2309
+ declare function createActionFetchHandler(runtime: ActionRuntime, options?: IActionFetchHandlerOptions): (request: Request) => Promise<Response>;
1549
2310
  //#endregion
1550
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketConnection.d.ts
1551
- declare class WebSocketConnection extends TransportConnection<ETransportType.ws, ITransportRouteActionParams, IActionTransportReadyData_Ws, IActionTransportInitialized_Ws, IActionTransportDef_Ws> {
1552
- private resolvers;
1553
- /** URL of the most recently resolved live socket — surfaced to devtools when the definition can't. */
1554
- private _liveSocketUrl?;
1555
- /** Sockets we closed on purpose (via `disconnect`), so their `close` event stays quiet. */
1556
- private _intentionalCloses;
1557
- constructor(def: Omit<IActionTransportDef_Ws, "type">, resolvers?: IActionTransportResolvers);
1558
- protected _getCacheKey(_input: ITransportRouteActionParams): string;
1559
- protected _processTransportStatus(input: ITransportRouteActionParams): TTransportStatusInfo<IActionTransportReadyData_Methods>;
1560
- getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo | undefined;
1561
- private _isSecure;
1562
- private _awaitOpen;
1563
- /** Non-secure connections finalize synchronously; secure ones run the handshake first. */
1564
- private _finalize;
1565
- _finalizeTransportMethods(wsData: IActionTransportReadyData_Ws): IActionTransportReadyData_Methods;
1566
- /**
1567
- * Secure path: a single message listener feeds the handshake until it completes, then routes action
1568
- * frames (decrypting for the `encrypted` level). Frames that arrive in the gap between accept and
1569
- * activation are buffered and flushed, so nothing is lost.
1570
- */
1571
- private _finalizeSecureMethods;
1572
- private _runClientHandshake;
1573
- private _buildSendMethods;
1574
- /** Decode (and, when encrypted, decrypt) one inbound action frame and hand it to the runtime. */
1575
- private _handleIncomingActionFrame;
1576
- /** Accept text + binary frames (ArrayBuffer / Uint8Array / Blob); Blobs are converted to a buffer. */
1577
- private _normalizeFrame;
1578
- private _captureSocketUrl;
1579
- private _attachLifecycle;
1580
- private _abortAll;
1581
- }
1582
- //#endregion
1583
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketTransport.d.ts
1584
- interface IWebSocketTransportSharedOptions {
1585
- /** Custom (de)serialization of action payloads on the wire (shared across all sockets). */
1586
- formatMessage?: IActionTransportReadyData_Ws["formatMessage"];
1587
- /**
1588
- * Per-socket codec factory — called once for each socket so stateful codecs (e.g. the
1589
- * `createBinaryWsSessionFactory` session, which holds per-connection correlation + identity state)
1590
- * get their own instance and reset cleanly on reconnect. Takes precedence over `formatMessage`.
1591
- */
1592
- createFormatMessage?: () => IActionTransportReadyData_Ws["formatMessage"];
1593
- updateRunConfig?: TUpdateActionRunConfig;
2311
+ //#region src/ActionRuntime/Handler/PeerLink/Acceptor/createSecureActionServer.d.ts
2312
+ interface ISecureAcceptorHandlerOptions<TConn> {
2313
+ /** The shared channel identity (codec + dictionary version) — same one the clients use. */
2314
+ channel: ISecureChannel;
1594
2315
  /**
1595
- * Keys that identify a reusable socket, so a single socket is shared across actions to the same
1596
- * endpoint instead of opening one per action.
2316
+ * Coordinate of the *connecting clients* (typically env-only, e.g. `RuntimeCoordinate.env("web_app")`),
2317
+ * used to route results/pushes back over this handler.
1597
2318
  */
1598
- getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
1599
- /** Override the devtools route info for a specific action. */
1600
- getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
2319
+ clientEnv: RuntimeCoordinate;
2320
+ /** This server's runtime — its coordinate is the server identity presented in the handshake. */
2321
+ runtime: ActionRuntime;
1601
2322
  /**
1602
- * Secure-channel config. When set (and `securityLevel !== none`), the connection runs the
1603
- * authenticated handshake during initialization and, at the `encrypted` level, encrypts every frame.
2323
+ * One backing store for the server's crypto identity *and* its trust-on-first-use verify-key pins.
2324
+ * Their keys don't collide, so a single adapter is enough; back it with persistent storage (e.g. a
2325
+ * Durable Object's storage) so identity and pins survive eviction.
1604
2326
  */
1605
- security?: IActionTransportReadyData_Ws["secureChannel"];
1606
- }
1607
- interface IWebSocketTransportSocketOptions extends IWebSocketTransportSharedOptions {
1608
- /** Open (or reuse) the WebSocket for an action — keep it simple or derive it per action. */
1609
- createWebSocket: (input: ITransportRouteActionParams) => WebSocket;
1610
- }
1611
- interface IWebSocketTransportAdvancedOptions extends IWebSocketTransportSharedOptions {
2327
+ storageAdapter: StorageAdapter;
2328
+ /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
2329
+ send: (connection: TConn, frame: string | Uint8Array | ArrayBuffer) => void;
1612
2330
  /**
1613
- * Full control over readiness/initialization. Use this for contextual support detection (return
1614
- * `{ status: ETransportStatus.unsupported }` when the socket shouldn't be used) or async url
1615
- * building (return `{ status: ETransportStatus.initializing, initializationPromise }` that resolves
1616
- * to a `ready` socket).
2331
+ * The server's crypto identity. Defaults to a fresh {@link ClientCryptoKeyLink} over `storageAdapter`.
2332
+ * Pass an existing link to share one identity across several acceptors on the same server (e.g. a
2333
+ * WebSocket acceptor and a secure-HTTP {@link createActionFetchHandler}), so they present the same
2334
+ * verify/exchange keys — avoiding a divergent-key race when two fresh links initialize concurrently.
1617
2335
  */
1618
- getTransport: IActionTransportInitialized_Ws["getTransport"];
1619
- }
1620
- type TWebSocketTransportOptions = (IWebSocketTransportSocketOptions & {
1621
- mode: "socket";
1622
- }) | (IWebSocketTransportAdvancedOptions & {
1623
- mode: "advanced";
1624
- });
1625
- /**
1626
- * Reusable WebSocket transport definition. Create one with `WebSocketTransport.create({ createWebSocket })`
1627
- * for the common case, or `WebSocketTransport.createAdvanced({ getTransport })` for full control over
1628
- * readiness. The underlying socket is cached (via `getTransportCacheKey`) and reused across actions.
1629
- */
1630
- declare class WebSocketTransport extends Transport<ETransportType.ws> {
1631
- private readonly options;
1632
- readonly type = ETransportType.ws;
1633
- constructor(options: TWebSocketTransportOptions);
1634
- static create(options: IWebSocketTransportSocketOptions): WebSocketTransport;
1635
- static createAdvanced(options: IWebSocketTransportAdvancedOptions): WebSocketTransport;
1636
- _createConnection(ctx: ITransportConnectionContext): WebSocketConnection;
1637
- getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
1638
- }
1639
- //#endregion
1640
- //#region src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/secureWsChannel.d.ts
1641
- /** The per-connection binary session codec — built once per socket from the channel's domains. */
1642
- type TChannelCodec = NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
1643
- /**
1644
- * The shared identity of a secure WebSocket channel: the wire dictionary version both ends check
1645
- * during the handshake, plus the per-connection codec factory both ends build from the *same* domain
1646
- * list. Define it once (typically in code shared by client and server) and hand it to
1647
- * {@link createSecureWebSocketTransport} on the client and `createSecureActionServerHandler` on the
1648
- * server, so the codec and version can never drift apart.
1649
- */
1650
- interface ISecureWsChannel {
1651
- /** Wire dictionary version — derived from the domains by default; the handshake rejects a mismatch. */
1652
- dictionaryVersion: string;
1653
- /** Per-connection session codec factory (call once per live connection). */
1654
- createCodec: () => TChannelCodec;
1655
- }
1656
- /**
1657
- * Bundle a secure channel's shared identity from its transported domains. Both ends MUST call this
1658
- * with the same domains in the same order (the binary wire dictionary is positional). The
1659
- * `dictionaryVersion` is derived from those domains unless you pin an explicit one.
1660
- */
1661
- declare function defineSecureWsChannel(options: {
1662
- /** Domains transported over this channel, in a stable order. Add new ones to the *end*. */domains: ActionDomain<any>[]; /** Pin a human-readable version instead of the derived hash (must match on both ends). */
1663
- dictionaryVersion?: string; /** Tuning for the per-connection binary session (e.g. correlation TTL). */
1664
- sessionOptions?: IBinaryWsSessionOptions;
1665
- }): ISecureWsChannel;
1666
- interface ISecureWebSocketTransportOptions {
1667
- /** The shared channel identity (codec + dictionary version). */
1668
- channel: ISecureWsChannel;
1669
- /** This client's runtime — its coordinate is the authenticated identity sent in the handshake. */
1670
- runtime: ActionRuntime;
1671
- /** Backing store for this client's crypto identity (a stable verify key across reloads). */
1672
- storageAdapter: StorageAdapter;
1673
- /** The level this client requests; the server must allow it. */
1674
- securityLevel: ESecurityLevel;
1675
- /** Endpoint URL — drives both the socket and the per-endpoint cache key. */
1676
- url: string;
1677
- /** Override socket creation (defaults to a `new WebSocket(url)` with `binaryType = "arraybuffer"`). */
1678
- createWebSocket?: (input: ITransportRouteActionParams) => WebSocket;
1679
- /** Override the reuse key (defaults to `[url]`, so one socket is shared per endpoint). */
1680
- getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
1681
- updateRunConfig?: TUpdateActionRunConfig;
1682
- getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
2336
+ link?: ClientCryptoKeyLink;
2337
+ /** Accepted level(s); defaults to negotiating any of none/authenticated/encrypted. */
2338
+ securityLevel?: ESecurityLevel | readonly ESecurityLevel[];
2339
+ /** Trust decision for a client's verify key; defaults to storage-backed TOFU over `storageAdapter`. */
2340
+ verifyKeyResolver?: IClientVerifyKeyResolver;
2341
+ /** Timeout (ms) applied to server-initiated actions awaiting a client response. */
2342
+ defaultTimeout?: number;
1683
2343
  }
1684
2344
  /**
1685
- * Build a {@link WebSocketTransport} for the secure binary channel with the boilerplate folded in: it
1686
- * creates the {@link ClientCryptoKeyLink} from `storageAdapter`, opens an `arraybuffer` socket to
1687
- * `url`, caches it per endpoint, installs the channel's per-connection codec, and assembles the
1688
- * `security` block from the runtime coordinate + channel version. Pass `createWebSocket` /
1689
- * `getTransportCacheKey` to take over those bits when you need to.
2345
+ * Build an {@link AcceptorHandler} for the secure binary channel with the boilerplate folded in:
2346
+ * it creates the {@link ClientCryptoKeyLink} and the storage-backed TOFU resolver from a single
2347
+ * `storageAdapter`, installs the channel's per-connection codec, and assembles the `security` block
2348
+ * from the runtime coordinate + channel version (accepting all three levels by default).
2349
+ *
2350
+ * For a hibernatable transport (e.g. a Durable Object), pair it with
2351
+ * {@link createHibernatableWsServerAdapter} to wire persistence + replay.
1690
2352
  */
1691
- declare function createSecureWebSocketTransport(options: ISecureWebSocketTransportOptions): WebSocketTransport;
2353
+ declare function createSecureAcceptorHandler<TConn = unknown>(options: ISecureAcceptorHandlerOptions<TConn>): AcceptorHandler<TConn>;
1692
2354
  //#endregion
1693
- //#region src/ActionRuntime/Handler/Server/WsConnectionStateStore.d.ts
2355
+ //#region src/ActionRuntime/Handler/PeerLink/Acceptor/Hibernation/ConnectionStateStore.d.ts
1694
2356
  /**
1695
2357
  * The composite value persisted to a connection's attachment: the consumer's own app state plus the
1696
- * {@link ActionServerHandler} routing binding. Co-storing them in one slot means a transport whose
2358
+ * {@link AcceptorHandler} routing binding. Co-storing them in one slot means a transport whose
1697
2359
  * sockets outlive process eviction (e.g. a Durable Object's hibernatable WebSocket) recovers both the
1698
2360
  * application identity *and* the action routing from a single attachment after a wake — no storage reads.
1699
2361
  */
1700
2362
  interface IConnectionAttachment<TApp> {
1701
2363
  app?: TApp;
1702
- binding?: IActionServerConnectionBinding;
2364
+ binding?: IAcceptorConnectionBinding;
1703
2365
  }
1704
- interface IWsConnectionStateStoreOptions<TConn, TApp> {
2366
+ interface IConnectionStateStoreOptions<TConn, TApp> {
1705
2367
  /** Read a connection's raw attachment (e.g. `(ws) => ws.deserializeAttachment()`). */
1706
2368
  read: (connection: TConn) => unknown;
1707
2369
  /** Persist a connection's attachment (e.g. `(ws, value) => ws.serializeAttachment(value)`). */
1708
2370
  write: (connection: TConn, value: IConnectionAttachment<TApp>) => void;
1709
2371
  /**
1710
2372
  * All currently-live connections (e.g. `() => ctx.getWebSockets()`). Used to replay routing bindings
1711
- * after a wake (via {@link ActionServerHandler.createConnectionState}) and to enumerate app state in
1712
- * {@link WsConnectionStateStore.entries}.
2373
+ * after a wake (via {@link createConnectionStateStore}) and to enumerate app state in
2374
+ * {@link ConnectionStateStore.entries}.
1713
2375
  */
1714
2376
  getConnections: () => TConn[];
1715
2377
  /**
@@ -1720,13 +2382,17 @@ interface IWsConnectionStateStoreOptions<TConn, TApp> {
1720
2382
  schema?: StandardSchemaV1<unknown, TApp>;
1721
2383
  }
1722
2384
  /**
1723
- * A typed per-connection state store that co-owns the app state and the server handler's routing
2385
+ * A typed per-connection state store that co-owns the app state and the acceptor handler's routing
1724
2386
  * binding in one attachment, so neither the consumer nor the handler has to hand-merge the two. Create
1725
- * it through {@link ActionServerHandler.createConnectionState} (which also wires binding persistence and
1726
- * replays surviving connections after a wake), then `get`/`set`/`clearApp` the app state directly.
2387
+ * it through {@link createConnectionStateStore} (which also wires binding persistence and replays
2388
+ * surviving connections after a wake), then `get`/`set`/`clearApp` the app state directly.
2389
+ *
2390
+ * The mechanism is carrier-neutral — it only needs read/write/enumerate callbacks for the connection's
2391
+ * attachment — but it pays off on transports whose connections outlive process eviction (e.g. a
2392
+ * Durable Object's hibernatable WebSockets), which is why it lives beside the hibernation adapter.
1727
2393
  *
1728
2394
  * ```ts
1729
- * const players = serverHandler.createConnectionState({
2395
+ * const players = createConnectionStateStore(serverHandler, {
1730
2396
  * schema: vs_player,
1731
2397
  * read: (ws) => ws.deserializeAttachment(),
1732
2398
  * write: (ws, v) => ws.serializeAttachment(v),
@@ -1736,9 +2402,9 @@ interface IWsConnectionStateStoreOptions<TConn, TApp> {
1736
2402
  * const player = players.get(ws);
1737
2403
  * ```
1738
2404
  */
1739
- declare class WsConnectionStateStore<TConn, TApp> {
2405
+ declare class ConnectionStateStore<TConn, TApp> {
1740
2406
  private readonly options;
1741
- constructor(options: IWsConnectionStateStoreOptions<TConn, TApp>);
2407
+ constructor(options: IConnectionStateStoreOptions<TConn, TApp>);
1742
2408
  /** The validated app state for a connection, or `null` if unset / invalid. */
1743
2409
  get(connection: TConn): TApp | null;
1744
2410
  /** Set the app state, preserving the runtime binding already pinned to the connection. */
@@ -1748,356 +2414,517 @@ declare class WsConnectionStateStore<TConn, TApp> {
1748
2414
  /** Every live connection paired with its (validated) app state — for rebuilding in-memory state after a wake. */
1749
2415
  entries(): [TConn, TApp | null][];
1750
2416
  /** @internal Persist a freshly-bound connection's binding, preserving any app state already stored. */
1751
- _persistBinding(connection: TConn, binding: IActionServerConnectionBinding): void;
2417
+ _persistBinding(connection: TConn, binding: IAcceptorConnectionBinding): void;
1752
2418
  /** @internal The persisted binding for a connection, if any (used to replay routing after a wake). */
1753
- _readBinding(connection: TConn): IActionServerConnectionBinding | undefined;
2419
+ _readBinding(connection: TConn): IAcceptorConnectionBinding | undefined;
1754
2420
  private _readAttachment;
1755
2421
  private _validateApp;
1756
2422
  }
2423
+ /**
2424
+ * Build a per-connection {@link ConnectionStateStore} bound to an {@link AcceptorHandler}: it registers
2425
+ * itself as the handler's connection-bound persistence callback (so bindings are written without
2426
+ * overwriting app state) and immediately replays every live connection's stored binding via
2427
+ * {@link AcceptorHandler.rehydrateConnection} — so on a transport that resumes after eviction (e.g. a
2428
+ * Durable Object waking from hibernation) both the app identity and the action routing come back from a
2429
+ * single attachment, with no storage reads and no hand-rolled merge.
2430
+ *
2431
+ * Lives outside the handler so the generic {@link AcceptorHandler} stays free of any attachment/
2432
+ * hibernation concern — it exposes only the neutral `setOnConnectionBound` + `rehydrateConnection`
2433
+ * hooks this builder drives.
2434
+ */
2435
+ declare function createConnectionStateStore<TConn, TApp>(handler: AcceptorHandler<TConn>, options: IConnectionStateStoreOptions<TConn, TApp>): ConnectionStateStore<TConn, TApp>;
1757
2436
  //#endregion
1758
- //#region src/ActionRuntime/Handler/Server/ActionServerHandler.d.ts
1759
- /** The codec shape `ActionServerHandler` uses to pack/unpack frames — same as the WS transport's. */
1760
- type TActionChannelFormatMessage = NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
1761
- /** How a connection encodes its frames, remembered so we answer each client in its own dialect. */
1762
- type TActionConnectionEncoding = "json" | "binary";
1763
- /** A connection's restorable identity — what to persist so a binding survives transport eviction. */
1764
- interface IActionServerConnectionBinding {
1765
- /** Full client coordinate, so `originClient` can be re-injected into frames that omit it. */
1766
- client: IRuntimeCoordinate;
1767
- encoding: TActionConnectionEncoding;
1768
- /**
1769
- * Secure-session state (set once a connection's handshake completes). Persist it alongside the
1770
- * binding so an authenticated/encrypted connection resumes after eviction without re-handshaking —
1771
- * the `keyMaterial` lets the server re-derive the shared key from its own persisted identity.
1772
- */
1773
- secure?: {
1774
- securityLevel: ESecurityLevel;
1775
- linkedClientId: TTypeAndId;
1776
- keyMaterial?: IHandshakeEncryptionKeyMaterial;
1777
- };
2437
+ //#region src/ActionRuntime/Handler/PeerLink/Connector/err_nice_external_client.d.ts
2438
+ declare const err_nice_external_client: import("@nice-code/error").NiceErrorDomain<{
2439
+ domain: string;
2440
+ allDomains: [string, string, "err_nice"];
2441
+ schema: {};
2442
+ }>;
2443
+ //#endregion
2444
+ //#region src/ActionRuntime/Transport/Carrier/duplex/inMemory/createInMemoryChannel.d.ts
2445
+ type TFrame = string | ArrayBuffer | Uint8Array;
2446
+ /** The peer (server) end of an in-memory pair — what you feed into an `AcceptorHandler`. */
2447
+ interface IInMemoryServerEndpoint {
2448
+ /** Write a frame back to the client end. */
2449
+ send(frame: TFrame): void;
2450
+ /** Register the inbound handler (frames the client sent). */
2451
+ onMessage(handler: (frame: TFrame) => void): void;
2452
+ /** Close the pair from this end. */
2453
+ close(): void;
2454
+ /** Notified when the pair closes (from either end). */
2455
+ onClose(handler: () => void): void;
2456
+ }
2457
+ interface IInMemoryChannelPair {
2458
+ /** The client end — pass as a {@link IDuplexCarrier} to a `LinkTransport`. */
2459
+ clientChannel: IDuplexCarrier;
2460
+ /** The server end — wire into an `AcceptorHandler` (`send` + `receive`). */
2461
+ serverEndpoint: IInMemoryServerEndpoint;
1778
2462
  }
1779
2463
  /**
1780
- * Server-side secure-channel config. When set, each connection negotiates a level from
1781
- * {@link securityLevel}: an `authenticated`/`encrypted` client must complete the handshake (and is then
1782
- * bound to its *authenticated* coordinate) before any action frame is accepted. A `none` client (only
1783
- * when `none` is in the allowed set) is accepted as-is with a self-asserted identity. For the
1784
- * `encrypted` level the codec source should be a session factory (`createFormatMessage`).
2464
+ * Two cross-wired in-process byte channels a loopback carrier with no socket. The client end is a
2465
+ * {@link IDuplexCarrier} you hand to a {@link LinkTransport}; the server end plugs into an
2466
+ * `AcceptorHandler` (`send: (_, f) => serverEndpoint.send(f)`, and `serverEndpoint.onMessage(f =>
2467
+ * handler.receive(conn, f))`). Frames are delivered on a microtask, so each side observes the other
2468
+ * asynchronously exactly like a real transport which makes this ideal for tests and for running
2469
+ * two runtimes in one process (or proving a non-WS carrier end to end).
1785
2470
  */
1786
- interface IActionServerSecurity {
1787
- /**
1788
- * Accepted level(s). A single level is strict; an array is a negotiable allowed set — the server
1789
- * adopts whichever level each client requests (e.g. `[none, authenticated, encrypted]` serves all
1790
- * three over one endpoint).
1791
- */
1792
- securityLevel: ESecurityLevel | readonly ESecurityLevel[];
1793
- /** This server's crypto identity (verify + exchange key pairs, optionally persisted). */
1794
- link: ClientCryptoKeyLink;
1795
- /** This server's coordinate — its identity to clients during the handshake. */
1796
- localCoordinate: IRuntimeCoordinate;
1797
- /** Wire dictionary version; the handshake rejects a client on a mismatch. */
1798
- dictionaryVersion: string;
1799
- /** Trust decision for a client's verify key (defaults to in-memory TOFU inside the handshake). */
1800
- verifyKeyResolver?: IClientVerifyKeyResolver;
1801
- }
1802
- interface IActionServerHandlerBaseOptions<TConn> {
1803
- /**
1804
- * Coordinate of the *connecting clients* (typically env-only, e.g. `RuntimeCoordinate.env("web_app")`).
1805
- * The runtime's return-path dispatch scores incoming actions' `originClient` against this to pick
1806
- * this handler for sending results/pushes back over the right channel.
1807
- */
1808
- clientEnv: RuntimeCoordinate;
1809
- /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
1810
- send: (connection: TConn, frame: string | Uint8Array | ArrayBuffer) => void;
1811
- /**
1812
- * The runtime this handler belongs to. When set, {@link ActionServerHandler.broadcast} can be called
1813
- * without threading a runtime through each call. Optional — `pushToClient` still takes one explicitly.
1814
- */
1815
- runtime?: ActionRuntime;
1816
- /** Timeout (ms) applied to server-initiated actions awaiting a client response. */
1817
- defaultTimeout?: number;
1818
- /**
1819
- * Called once when a connection is first bound to a client identity. Use it to persist the binding
1820
- * for transports that can resume after eviction — e.g. a Durable Object's hibernatable WebSocket:
1821
- * `(ws, binding) => ws.serializeAttachment(binding)` — then replay it via {@link ActionServerHandler.rehydrateConnection}
1822
- * when the channel comes back.
1823
- */
1824
- onConnectionBound?: (connection: TConn, binding: IActionServerConnectionBinding) => void;
1825
- /**
1826
- * Enable the authenticated (optionally encrypted) handshake. When omitted, connections are trusted
1827
- * as-is (identity self-asserted) — fine for dev / trusted networks.
1828
- */
1829
- security?: IActionServerSecurity;
2471
+ declare function createInMemoryChannelPair(): IInMemoryChannelPair;
2472
+ //#endregion
2473
+ //#region src/ActionRuntime/Transport/Carrier/duplex/inMemory/inMemoryCarrier.d.ts
2474
+ interface IInMemoryCarrier {
2475
+ /** The connector end — pass as the `carrier` to {@link secureTransport} / `LinkTransport`. */
2476
+ carrier: IDuplexCarrierSource;
2477
+ /** The acceptor end — wire into an `AcceptorHandler` (`send` + `receive`). */
2478
+ serverEndpoint: IInMemoryServerEndpoint;
1830
2479
  }
1831
2480
  /**
1832
- * Provide exactly one codec source:
1833
- * - `formatMessage` a single shared codec for every connection (stateless, e.g. `createBinaryWsAdapter`).
1834
- * - `createFormatMessage` a per-connection factory for stateful codecs (e.g.
1835
- * `createBinaryWsSessionFactory`, whose sessions hold correlation + identity state). Required for the
1836
- * leanest binary wire; the handler creates and caches one codec per connection.
2481
+ * A loopback duplex carrier with no socket — two cross-wired in-process ends. The connector end is an
2482
+ * {@link IDuplexCarrierSource} for {@link secureTransport}; the acceptor end plugs into an
2483
+ * `AcceptorHandler`. Ideal for tests and for running two runtimes in one process, or proving a
2484
+ * non-WS carrier end to end.
1837
2485
  */
1838
- type IActionServerHandlerOptions<TConn> = IActionServerHandlerBaseOptions<TConn> & ({
1839
- formatMessage: TActionChannelFormatMessage;
1840
- createFormatMessage?: never;
1841
- } | {
1842
- createFormatMessage: () => TActionChannelFormatMessage;
1843
- formatMessage?: never;
1844
- });
2486
+ declare function inMemoryCarrier(): IInMemoryCarrier;
2487
+ //#endregion
2488
+ //#region src/ActionRuntime/Transport/Carrier/duplex/rtc/rtcDataChannelByteChannel.d.ts
1845
2489
  /**
1846
- * A connection-aware execution case (see {@link ActionServerHandler.forConnectionDomainCases}). It
1847
- * receives the primed request plus the originating client's live connection (already resolved from the
1848
- * request's `originClient`, `undefined` if the socket is gone), and may return the action's raw output,
1849
- * a result payload, or nothing (auto-wrapped as an empty success) — exactly like a local handler case.
2490
+ * The slice of the `RTCDataChannel` surface this adapter uses — declared structurally so it accepts the
2491
+ * browser `RTCDataChannel`, React-Native (`react-native-webrtc`), or any node-webrtc shim without a hard
2492
+ * dependency on a specific DOM/RN type. A real `RTCDataChannel` satisfies it as-is.
1850
2493
  */
1851
- type TServerConnectionCaseFn<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string, TConn> = (action: TDistributeActionPayload_Request<DOM, ID>, connection: TConn | undefined) => ReturnType<THandleActionExecutionFn<DOM, ID>> | void;
2494
+ interface IRtcDataChannelLike {
2495
+ readyState: string;
2496
+ binaryType: string;
2497
+ label?: string;
2498
+ send(data: string | ArrayBuffer | ArrayBufferView): void;
2499
+ close(): void;
2500
+ addEventListener(type: string, listener: (event: any) => void, options?: unknown): void;
2501
+ }
1852
2502
  /**
1853
- * Server-side handler for backends that accept many client connections over a single open channel
1854
- * (WebSockets, Durable Objects, ). It is transport-agnostic: you feed it inbound frames with
1855
- * {@link receive} and tell it how to write outbound frames via the `send` option.
1856
- *
1857
- * Add it alongside your local execution handler:
1858
- * ```ts
1859
- * const serverHandler = createServerHandler({ clientEnv, formatMessage, send: (ws, f) => ws.send(f) });
1860
- * runtime.addHandlers([localHandler, serverHandler]);
1861
- * // per inbound message (e.g. a Durable Object's webSocketMessage):
1862
- * serverHandler.receive(ws, message);
1863
- * ```
1864
- *
1865
- * Inbound requests route to your local handler; the runtime's return dispatch then calls this
1866
- * handler back (it is an external handler keyed to `clientEnv`) to send the result to the originating
1867
- * connection. The handler keeps a per-connection identity registry so each result lands on the right
1868
- * socket, and remembers each connection's encoding so binary and JSON clients can share the channel.
2503
+ * Adapt a WebRTC `RTCDataChannel` to the carrier-agnostic {@link IDuplexCarrier}, so two browsers
2504
+ * (or two mobile apps) linked peer-to-peer no server in the middle — run the *same* secure session as
2505
+ * a WebSocket. Hand it to `createSecureLinkTransport({ openChannel: () => rtcDataChannelByteChannel(dc) })`.
1869
2506
  *
1870
- * It registers an empty action router, so it is never chosen to *execute* an inbound request — only
1871
- * to ferry results/pushes back out.
2507
+ * The data channel must already be created (its negotiation/signaling is the app's concern); this only
2508
+ * drives bytes over it. Binary frames are requested as `ArrayBuffer` so the binary session codec unpacks
2509
+ * them synchronously; a `Blob` (if the channel hands one back) is normalized to a buffer.
1872
2510
  */
1873
- declare class ActionServerHandler<TConn = unknown> extends ActionExternalClientHandler {
1874
- private readonly _formatMessage?;
1875
- private readonly _createFormatMessage?;
1876
- private readonly _send;
1877
- private readonly _runtime?;
1878
- private readonly _serverTimeout;
1879
- private _onConnectionBound?;
1880
- /** Incoming-data listeners installed by the runtime (`resolveIncomingActionPayload`). */
1881
- private readonly _incomingListeners;
1882
- private readonly _security?;
1883
- /** Normalized accepted levels; whether `none` (plain) is allowed; whether any level needs a handshake. */
1884
- private readonly _allowedLevels;
1885
- private readonly _noneAllowed;
1886
- private readonly _handshakeMode;
1887
- private readonly _connByClient;
1888
- private readonly _clientByConn;
1889
- private readonly _connEncoding;
1890
- private readonly _codecByConn;
1891
- private readonly _handshakeByConn;
1892
- private readonly _cryptoByConn;
1893
- private readonly _authedConns;
1894
- private readonly _plainConns;
1895
- private readonly _inboundChainByConn;
1896
- private readonly _outboundChainByConn;
1897
- constructor(options: IActionServerHandlerOptions<TConn>);
1898
- /**
1899
- * The codec for a connection: a per-connection session (cached) when a factory was provided, else
1900
- * the single shared `formatMessage`.
1901
- */
1902
- private _codecFor;
1903
- _setIncomingActionDataListener(listener: (json: TActionPayload_Any_JsonObject<any>) => void): void;
1904
- /**
1905
- * Register (or replace) the connection-bound persistence callback after construction. Used by
1906
- * lifecycle helpers like {@link createHibernatableWsServerAdapter} so persistence and replay are
1907
- * owned by one place instead of being split across the constructor options.
1908
- */
1909
- setOnConnectionBound(onConnectionBound: (connection: TConn, binding: IActionServerConnectionBinding) => void): void;
1910
- /**
1911
- * Create a typed per-connection state store that co-owns the consumer's app state and this handler's
1912
- * routing binding in one attachment. It registers itself as the connection-bound persistence callback
1913
- * (so bindings are written without overwriting app state) and immediately replays every live
1914
- * connection's stored binding via {@link rehydrateConnection} — so on a transport that resumes after
1915
- * eviction (e.g. a Durable Object waking from hibernation) both the app identity and the action
1916
- * routing come back from a single attachment, with no storage reads and no hand-rolled merge.
1917
- *
1918
- * This supersedes {@link createHibernatableWsServerAdapter} for app code that also pins its own state
1919
- * to the connection. Construct it once when the handler is built, then `get`/`set` app state directly.
1920
- */
1921
- createConnectionState<TApp>(options: IWsConnectionStateStoreOptions<TConn, TApp>): WsConnectionStateStore<TConn, TApp>;
1922
- /**
1923
- * Feed one inbound frame from a connection into the runtime. Decodes text or binary, binds the
1924
- * connection to the requesting client's identity, then routes it (requests execute locally;
1925
- * results/progress resolve pending server-initiated actions).
1926
- */
1927
- receive(connection: TConn, frame: string | ArrayBuffer | Uint8Array): void;
1928
- private _receivePlain;
1929
- private _receiveSecure;
1930
- private _completeServerHandshake;
1931
- /**
1932
- * Ensure an inbound request carries the client's identity and that this connection is bound to it,
1933
- * so its result can be routed back. A session codec omits `originClient` after the first request, so
1934
- * when it's missing we restore it from the (possibly rehydrated) binding instead. (Plain mode only;
1935
- * secure mode binds the authenticated coordinate at handshake time.)
1936
- */
1937
- private _resolveRequestIdentity;
1938
- /**
1939
- * Restore a connection→client binding without an inbound frame — for transports that resume after
1940
- * eviction. Pair it with the {@link IActionServerHandlerOptions.onConnectionBound} hook: persist
1941
- * the binding there, then replay each live connection here when the channel comes back (e.g. a
1942
- * Durable Object iterating `ctx.getWebSockets()` as it wakes from hibernation).
1943
- */
1944
- rehydrateConnection(connection: TConn, binding: IActionServerConnectionBinding): void;
1945
- toHandlerRouteItem(): IActionRouteItemHandler;
1946
- /** Forget a connection (call on socket close) so stale entries don't misroute later results. */
1947
- dropConnection(connection: TConn): void;
1948
- /** Live connection for a client coordinate, if currently registered. */
1949
- getConnectionForClient(client: RuntimeCoordinate): TConn | undefined;
1950
- /**
1951
- * Send (and optionally await) a server-initiated action to a specific connected client. Pass the
1952
- * connection token directly (e.g. the `ws`) or a client `RuntimeCoordinate` to look one up.
1953
- */
1954
- pushToClient<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(runtime: ActionRuntime, target: TConn | RuntimeCoordinate, request: ActionPayload_Request<DOM, ID>, options?: {
1955
- timeout?: number;
1956
- }): RunningAction<DOM, ID>;
2511
+ declare function rtcDataChannelByteChannel(dc: IRtcDataChannelLike): IDuplexCarrier;
2512
+ //#endregion
2513
+ //#region src/ActionRuntime/Transport/Carrier/duplex/rtc/rtcCarrier.d.ts
2514
+ interface IRtcCarrierOptions {
2515
+ getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
2516
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
2517
+ }
2518
+ /**
2519
+ * A WebRTC {@link IDuplexCarrierSource} over an already-negotiated `RTCDataChannel` (signaling is the
2520
+ * app's concern). Hand it to {@link secureTransport} so two browsers/apps linked peer-to-peer run the
2521
+ * identical secure session as a WebSocket.
2522
+ */
2523
+ declare function rtcCarrier(dataChannel: IRtcDataChannelLike, options?: IRtcCarrierOptions): IDuplexCarrierSource;
2524
+ //#endregion
2525
+ //#region src/ActionRuntime/Transport/Carrier/duplex/ws/err_nice_transport_ws.d.ts
2526
+ declare enum EErrId_NiceTransport_WebSocket {
2527
+ ws_disconnected = "ws_disconnected",
2528
+ ws_create_failed = "ws_create_failed",
2529
+ ws_error = "ws_error"
2530
+ }
2531
+ declare const err_nice_transport_ws: import("@nice-code/error").NiceErrorDomain<{
2532
+ domain: string;
2533
+ allDomains: [string, string, string, string, "err_nice"];
2534
+ schema: {
2535
+ ws_disconnected: import("@nice-code/error").INiceErrorIdMetadata<Record<string, never>, import("@nice-code/error").JSONSerializableValue>;
2536
+ ws_create_failed: import("@nice-code/error").INiceErrorIdMetadata<{
2537
+ originalError?: Error;
2538
+ }, import("@nice-code/error").JSONSerializableValue>;
2539
+ ws_error: import("@nice-code/error").INiceErrorIdMetadata<{
2540
+ originalError?: Error;
2541
+ }, import("@nice-code/error").JSONSerializableValue>;
2542
+ };
2543
+ }>;
2544
+ //#endregion
2545
+ //#region src/ActionRuntime/Transport/Carrier/duplex/ws/wsAcceptorCarrier.d.ts
2546
+ interface IWsAcceptorCarrierOptions<TConn> {
1957
2547
  /**
1958
- * Build a local handler whose cases are connection-aware: each case receives the primed request and
1959
- * the originating client's live connection (resolved from `originClient`), so handlers don't repeat
1960
- * the `getConnectionForClient(action.context.originClient)` lookup. Cases may return raw output or
1961
- * nothing, just like {@link ActionLocalHandler.forDomainActionCases}. Add the returned handler to the
1962
- * runtime alongside this server handler:
1963
- * ```ts
1964
- * runtime.addHandlers([serverHandler.forConnectionDomainCases(domain, { … }), serverHandler]);
1965
- * ```
2548
+ * Whether each socket runs the secure handshake (default `true`). Pass `false` for a plain WS endpoint
2549
+ * — connections speak the channel's wire codec with a self-asserted identity, no handshake/pins/encryption
2550
+ * (and `serveChannel` then needs no `storage` for this carrier).
1966
2551
  */
1967
- forConnectionDomainCases<FOR_DOM extends IActionDomain>(domain: ActionDomain<FOR_DOM>, cases: { [ID in keyof FOR_DOM["actionSchema"] & string]?: TServerConnectionCaseFn<FOR_DOM, ID, TConn> }): ActionLocalHandler;
2552
+ secure?: boolean;
2553
+ /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
2554
+ send: (connection: TConn, frame: TFrame$1) => void;
1968
2555
  /**
1969
- * Fan a server-initiated request out to every currently-bound connection. A fresh request is built
1970
- * per connection (each push mutates its own action context) and dispatched fire-and-forget. Pass
1971
- * `except` to skip the originating socket and `where` to filter by connection (e.g. read its
1972
- * attachment for a role). Iterating bound connections (rather than every accepted socket) skips
1973
- * sockets that are still mid-handshake and so can't yet receive a frame.
2556
+ * Perform the transport-specific WebSocket upgrade, returning its raw response (e.g. a Durable Object's
2557
+ * `new WebSocketPair()` + `ctx.acceptWebSocket()` a `101`). Omit if sockets are fed in out of band.
1974
2558
  */
1975
- broadcast<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(makeRequest: () => ActionPayload_Request<DOM, ID>, options?: {
1976
- runtime?: ActionRuntime;
1977
- except?: TConn | null;
1978
- where?: (connection: TConn) => boolean;
1979
- timeout?: number;
1980
- onError?: (error: unknown, connection: TConn) => void;
1981
- }): void;
1982
- sendReturnPayload(payload: TActionPayload_Any_Instance<any, any>, config: {
1983
- targetLocalRuntime: ActionRuntime;
1984
- }): Promise<boolean>;
1985
- handleActionRequest<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(action: ActionPayload_Request<DOM, ID>, config?: IHandleActionOptions): Promise<RunningAction<DOM, ID>>;
1986
- private _dispatch;
1987
- private _sendPayload;
1988
- private _bindConnection;
1989
- private _resolveConnection;
1990
- private _resolveSingleConnection;
2559
+ upgrade?: (request: Request, url: URL) => Response | Promise<Response>;
2560
+ /** Whether an inbound request is a WS upgrade. Defaults to an `Upgrade: websocket` header. */
2561
+ isUpgrade?: (request: Request, url: URL) => boolean;
2562
+ /** Persistence + replay hooks for hibernatable sockets (e.g. a Durable Object). */
2563
+ hibernation?: IAcceptorHibernation<TConn>;
2564
+ /** Override the devtools carrier-kind label (defaults to `"ws"`). */
2565
+ carrierLabel?: string;
1991
2566
  }
1992
- declare const createServerHandler: <TConn = unknown>(options: IActionServerHandlerOptions<TConn>) => ActionServerHandler<TConn>;
2567
+ /**
2568
+ * A WebSocket {@link IDuplexAcceptorCarrier}: the accept-in dual of {@link wsCarrier}. It describes how to
2569
+ * write frames back to a live socket, how to upgrade an inbound request into one, and (optionally) how to
2570
+ * persist bindings across hibernation. Hand it to `serveChannel`'s `carriers` list — the secure session,
2571
+ * codec, and crypto identity are supplied centrally there, so this only carries the WS-specific surface.
2572
+ */
2573
+ declare function wsAcceptorCarrier<TConn = WebSocket>(options: IWsAcceptorCarrierOptions<TConn>): IDuplexAcceptorCarrier<TConn>;
1993
2574
  //#endregion
1994
- //#region src/ActionRuntime/Handler/Server/createActionFetchHandler.d.ts
1995
- interface IActionFetchHandlerOptions {
2575
+ //#region src/ActionRuntime/Transport/Carrier/duplex/ws/wsCarrier.d.ts
2576
+ interface IWsCarrierOptions {
2577
+ /** Override socket creation (defaults to a `new WebSocket(url)` with `binaryType = "arraybuffer"`). */
2578
+ createWebSocket?: (input: ITransportRouteActionParams) => WebSocket;
2579
+ /** Override the reuse key (defaults to `[url]`, so one socket is shared per endpoint). */
2580
+ getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
2581
+ /** Override the devtools route info for a specific action. */
2582
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
2583
+ }
2584
+ /**
2585
+ * A WebSocket {@link IDuplexCarrierSource}: opens an `arraybuffer` socket to `url` (cached per endpoint)
2586
+ * and adapts it to a carrier. Hand it to {@link secureTransport} (or `LinkTransport`) — the WebSocket is
2587
+ * now "just another carrier" under the shared secure session, with no WS-specific transport class.
2588
+ */
2589
+ declare function wsCarrier(url: string, options?: IWsCarrierOptions): IDuplexCarrierSource;
2590
+ //#endregion
2591
+ //#region src/ActionRuntime/Transport/Carrier/exchange/http/httpAcceptorCarrier.d.ts
2592
+ interface IHttpAcceptorCarrierOptions {
1996
2593
  /**
1997
- * CORS headers merged onto every response (a preflight `OPTIONS` is answered `204` with them).
1998
- * Defaults to permissive `*`; pass `false` to attach no CORS headers at all.
2594
+ * Whether this endpoint runs the secure exchange protocol (default `true`). Pass `false` for a plain
2595
+ * endpoint the body is the raw action wire and the result is the response body, the request/reply dual
2596
+ * of `plainTransport({ carrier: httpCarrier(...) })`. A plain endpoint ignores the crypto identity, so it
2597
+ * can sit alongside a secure WebSocket on the same server (e.g. a secure WS preferred, plain HTTP fallback).
1999
2598
  */
2000
- cors?: Record<string, string> | false;
2001
- /** Which requests carry an action wire on `POST`. Default: pathname ends with `/action`. */
2599
+ secure?: boolean;
2600
+ /** Which requests carry an action exchange envelope on `POST`. Defaults to `serveChannel`'s path match. */
2002
2601
  isActionPath?: (url: URL) => boolean;
2003
- /** Which requests are WebSocket upgrades. Default: pathname ends with `/ws`. */
2004
- isWebSocketPath?: (url: URL) => boolean;
2005
2602
  /**
2006
- * Perform the transport-specific WebSocket upgrade (e.g. a Durable Object's
2007
- * `new WebSocketPair()` + `ctx.acceptWebSocket()` returning a `101`). Omit for HTTP-only endpoints.
2008
- * Its response is returned as-is — a `101` upgrade carries no CORS headers.
2603
+ * CORS headers merged onto every response (a preflight `OPTIONS` is answered `204`). Defaults to the
2604
+ * permissive `*` set; pass `false` to attach no CORS headers at all.
2009
2605
  */
2010
- onWebSocketUpgrade?: (request: Request, url: URL) => Response | Promise<Response>;
2011
- /** Forwarded to `ActionPayload_Result.toHttpResponse` use the error's HTTP status (default true). */
2606
+ cors?: Record<string, string> | false;
2607
+ /** Plain mode only: use the error's HTTP status for failures (default `true`). Ignored when secure. */
2012
2608
  useErrorStatus?: boolean;
2609
+ /** Override the devtools carrier-kind label (defaults to `"http"`). */
2610
+ carrierLabel?: string;
2013
2611
  }
2014
2612
  /**
2015
- * Build the `fetch` handler a server/Durable-Object exposes for action traffic, folding in the
2016
- * boilerplate every endpoint repeats: CORS (incl. the `OPTIONS` preflight), routing the `/action`
2017
- * `POST` body through the runtime (`handleActionPayloadWire` `waitForResultPayload`
2018
- * `toHttpResponse`), an optional WebSocket-upgrade hook, and a `404` fallback.
2613
+ * An HTTP {@link IExchangeAcceptorCarrier}: the accept-in dual of {@link httpCarrier}. It serves the
2614
+ * secure exchange protocol (handshake token session encrypted frames) over web-standard
2615
+ * `Request`/`Response`. The crypto identity, runtime coordinate, dictionary version, and accepted security
2616
+ * levels are all supplied centrally by `serveChannel`, so this only needs to say which requests carry an
2617
+ * action envelope and how to answer CORS.
2618
+ */
2619
+ declare function httpAcceptorCarrier(options?: IHttpAcceptorCarrierOptions): IExchangeAcceptorCarrier;
2620
+ //#endregion
2621
+ //#region src/ActionRuntime/Transport/Carrier/exchange/http/httpCarrier.d.ts
2622
+ /** The HTTP request an action is sent over (the body is supplied by the secure exchange session). */
2623
+ interface IHttpCarrierRequest {
2624
+ url: string;
2625
+ headers?: Record<string, string>;
2626
+ }
2627
+ /** The slice of `fetch` the carrier uses — a structural type so callers (and tests) needn't match the
2628
+ * full platform `fetch` (Bun's adds `preconnect`, etc.). The global `fetch` satisfies it. */
2629
+ type TCarrierFetch = (input: string, init?: RequestInit) => Promise<Response>;
2630
+ interface IHttpCarrierOptions {
2631
+ /** Override the reuse key (defaults to `[url]`, so one session is shared per endpoint). */
2632
+ getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
2633
+ /** Override the devtools route info for a specific action. */
2634
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
2635
+ /** Override `fetch` (e.g. to route to an in-memory handler in tests). Defaults to global `fetch`. */
2636
+ fetch?: TCarrierFetch;
2637
+ }
2638
+ /**
2639
+ * An HTTP {@link IExchangeCarrierSource}: each `exchange` POSTs one frame body to the action endpoint and
2640
+ * resolves with the response body as the single correlated reply. Hand it to {@link secureTransport} —
2641
+ * HTTP then runs the *same* secure session as a duplex carrier (handshake → token → encrypted frames),
2642
+ * the request/reply correlation provided for free by the HTTP transaction.
2019
2643
  *
2020
- * It only touches web-standard `Request`/`Response`, so it stays transport-agnostic the one
2021
- * environment-specific bit (the WS upgrade) is injected via {@link IActionFetchHandlerOptions.onWebSocketUpgrade}:
2022
- * ```ts
2023
- * this.fetchHandler = createActionFetchHandler(this.runtime, {
2024
- * onWebSocketUpgrade: () => {
2025
- * const pair = new WebSocketPair();
2026
- * this.ctx.acceptWebSocket(pair[1]);
2027
- * return new Response(null, { status: 101, webSocket: pair[0] });
2028
- * },
2029
- * });
2030
- * // async fetch(request) { return this.fetchHandler(request); }
2031
- * ```
2644
+ * `createRequest` derives the URL/headers per action (keep it simple with `() => ({ url })`). The body is
2645
+ * the session's responsibility, so it is never built here.
2032
2646
  */
2033
- declare function createActionFetchHandler(runtime: ActionRuntime, options?: IActionFetchHandlerOptions): (request: Request) => Promise<Response>;
2647
+ declare function httpCarrier(createRequest: (input: ITransportRouteActionParams) => IHttpCarrierRequest, options?: IHttpCarrierOptions): IExchangeCarrierSource;
2034
2648
  //#endregion
2035
- //#region src/ActionRuntime/Handler/Server/createSecureActionServer.d.ts
2036
- interface ISecureActionServerHandlerOptions<TConn> {
2037
- /** The shared channel identity (codec + dictionary version) same one the clients use. */
2038
- channel: ISecureWsChannel;
2649
+ //#region src/ActionRuntime/Transport/codec/createBinaryWireAdapter.d.ts
2650
+ /**
2651
+ * Builds a *stateless* `formatMessage` pipeline for {@link LinkTransport}, packing action
2652
+ * payloads into a compact msgpackr binary frame instead of JSON. The `domain`/`id` route collapses to
2653
+ * a single integer drawn from a shared dictionary; `form`/`type`, the recomputable
2654
+ * `inputHash`/`outputHash`, and the per-frame `context.routing`/`context.timeCreated` are all dropped
2655
+ * (see {@link ENVELOPE}).
2656
+ *
2657
+ * No validation runs here: `incoming` blindly reconstructs the wire JSON shape and hands it back to
2658
+ * the connection, which flows into `ActionRuntime` → `domain.hydrateAnyAction()` where the Valibot
2659
+ * schemas validate it exactly as they would for a JSON frame.
2660
+ *
2661
+ * Both ends of the socket MUST construct the adapter with the same domains in the same order — the
2662
+ * integer dictionary is positional. Mismatched dictionaries will route to the wrong action.
2663
+ *
2664
+ * Because `incoming` returns `undefined` for text frames, a binary server can still serve plain-JSON
2665
+ * clients on the same runtime (the connection falls back to its built-in JSON parser).
2666
+ */
2667
+ declare function createBinaryWireAdapter(domains: ActionDomain<any>[]): IActionWireFormat;
2668
+ //#endregion
2669
+ //#region src/ActionRuntime/Transport/crypto/actionFrameCrypto.d.ts
2670
+ /**
2671
+ * Async AES-GCM transform for the `encrypted` security level. It wraps the opaque binary frame a
2672
+ * session codec produces (it does NOT look inside it), encrypting on the way out and decrypting on the
2673
+ * way in with the shared key established by the handshake.
2674
+ *
2675
+ * It is deliberately separate from the (synchronous) session `formatMessage`: WebCrypto is always
2676
+ * Promise-based, so encryption has to happen at the transport's async I/O boundary — the connection
2677
+ * encrypts after `session.outgoing()` and decrypts before `session.incoming()`. The `authenticated`
2678
+ * and `none` levels use no crypto transform at all (frames go out as the session produced them).
2679
+ *
2680
+ * Wire shape of an encrypted frame: `pack([nonceBytes, ciphertextBytes])` — msgpack carries the two
2681
+ * binary fields with a couple of bytes of overhead, no base64 inflation.
2682
+ */
2683
+ interface IActionFrameCrypto {
2684
+ /** Encrypt one session frame for sending. */
2685
+ encryptFrame(frame: Uint8Array): Promise<Uint8Array>;
2686
+ /** Decrypt one received frame back to the session frame. Throws on a non-binary / malformed /
2687
+ * tampered frame — the caller (transport) decides how to react (drop / close). */
2688
+ decryptFrame(frame: string | ArrayBuffer | Uint8Array): Promise<Uint8Array>;
2689
+ }
2690
+ interface IActionFrameCryptoConfig {
2691
+ link: ClientCryptoKeyLink;
2692
+ /** The handshake-established link id for the remote (key + connection-registry id). */
2693
+ linkedClientId: TTypeAndId;
2694
+ }
2695
+ /**
2696
+ * Build the encrypt/decrypt transform for a connection whose handshake settled on the `encrypted`
2697
+ * level. Keyed by the link + `linkedClientId`, so it reuses the cached shared AES-GCM key.
2698
+ */
2699
+ declare function createActionFrameCrypto({
2700
+ link,
2701
+ linkedClientId
2702
+ }: IActionFrameCryptoConfig): IActionFrameCrypto;
2703
+ //#endregion
2704
+ //#region src/ActionRuntime/Transport/Exchange/TransportExchange.types.d.ts
2705
+ interface IActionTransportReadyData_Exchange extends IActionTransportReadyData_Base {
2706
+ /** The live request/reply carrier this connection drives its session over. */
2707
+ carrier: IExchangeCarrier;
2708
+ /** Optional authenticated/encrypted config; the handshake runs once at bring-up when set. */
2709
+ secureChannel?: ISecureClientConfig;
2710
+ }
2711
+ interface IActionTransportInitialized_Exchange extends IActionTransportInitialized<ITransportRouteActionParams, IActionTransportReadyData_Exchange> {}
2712
+ interface IActionTransportDef_Exchange extends IActionTransportDef<ETransportShape.exchange, IActionTransportInitialized_Exchange> {}
2713
+ //#endregion
2714
+ //#region src/ActionRuntime/Transport/Exchange/ExchangeConnection.d.ts
2715
+ /**
2716
+ * Carrier-agnostic live connection for the exchange (request → single reply) shape — the HTTP
2717
+ * counterpart to {@link LinkConnection}. It owns only the bring-up (run the secure handshake on first
2718
+ * use); the request/reply lifecycle + crypto live in the shared `establishExchangeSession`.
2719
+ */
2720
+ declare class ExchangeConnection extends TransportConnection<ETransportShape.exchange, ITransportRouteActionParams, IActionTransportReadyData_Exchange, IActionTransportInitialized_Exchange, IActionTransportDef_Exchange> {
2721
+ constructor(def: Omit<IActionTransportDef_Exchange, "type">);
2722
+ protected _getCacheKey(input: ITransportRouteActionParams): string;
2723
+ protected _needsAsyncBringUp(data: IActionTransportReadyData_Exchange): boolean;
2724
+ protected _finalizeReady(data: IActionTransportReadyData_Exchange): IActionTransportReadyData_Methods | Promise<IActionTransportReadyData_Methods>;
2725
+ _finalizeTransportMethods(data: IActionTransportReadyData_Exchange): IActionTransportReadyData_Methods;
2726
+ private _sessionContext;
2727
+ }
2728
+ //#endregion
2729
+ //#region src/ActionRuntime/Transport/Exchange/ExchangeTransport.d.ts
2730
+ interface IExchangeTransportOptions {
2731
+ /** Open (or reuse) the exchange carrier for an action — e.g. `httpCarrier(...).open`. */
2732
+ openCarrier: (input: ITransportRouteActionParams) => IExchangeCarrier;
2733
+ /** Secure config; when set (and `securityLevel !== none`) the handshake runs once at bring-up. */
2734
+ security?: ISecureClientConfig;
2735
+ updateRunConfig?: TUpdateActionRunConfig;
2736
+ /** Keys identifying a reusable session, so one carrier is shared across actions to the same peer. */
2737
+ getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
2738
+ /** Short label for the devtools chip (defaults to "exchange"). */
2739
+ label?: string;
2740
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
2741
+ }
2742
+ /**
2743
+ * A carrier-agnostic exchange (request → single reply) transport: it drives nice-action's secure session
2744
+ * over any {@link IExchangeCarrier} (HTTP being the one built-in). The duplex counterpart is
2745
+ * {@link LinkTransport}; this is the no-push half — its reply rides the response to its own request, so it
2746
+ * can't deliver an unsolicited frame (the runtime never picks it for the return path).
2747
+ */
2748
+ declare class ExchangeTransport extends Transport<ETransportShape.exchange> {
2749
+ private readonly options;
2750
+ readonly type = ETransportShape.exchange;
2751
+ constructor(options: IExchangeTransportOptions);
2752
+ static create(options: IExchangeTransportOptions): ExchangeTransport;
2753
+ _createConnection(_ctx: ITransportConnectionContext): ExchangeConnection;
2754
+ getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
2755
+ }
2756
+ //#endregion
2757
+ //#region src/ActionRuntime/Transport/err_nice_transport.d.ts
2758
+ declare enum EErrId_NiceTransport {
2759
+ timeout = "timeout",
2760
+ not_found = "not_found",
2761
+ unsupported = "unsupported",
2762
+ initialization_failed = "initialization_failed",
2763
+ send_failed = "send_failed",
2764
+ invalid_action_response = "invalid_action_response"
2765
+ }
2766
+ declare const err_nice_transport: import("@nice-code/error").NiceErrorDomain<{
2767
+ domain: string;
2768
+ allDomains: [string, string, string, "err_nice"];
2769
+ schema: {
2770
+ timeout: import("@nice-code/error").INiceErrorIdMetadata<{
2771
+ timeout: number;
2772
+ }, import("@nice-code/error").JSONSerializableValue>;
2773
+ not_found: import("@nice-code/error").INiceErrorIdMetadata<{
2774
+ actionId: string;
2775
+ }, import("@nice-code/error").JSONSerializableValue>;
2776
+ unsupported: import("@nice-code/error").INiceErrorIdMetadata<{
2777
+ transportShapes: ETransportShape[];
2778
+ }, import("@nice-code/error").JSONSerializableValue>;
2779
+ initialization_failed: import("@nice-code/error").INiceErrorIdMetadata<{
2780
+ actionId: string;
2781
+ }, import("@nice-code/error").JSONSerializableValue>;
2782
+ send_failed: import("@nice-code/error").INiceErrorIdMetadata<{
2783
+ actionState: string;
2784
+ actionId: string;
2785
+ httpStatusCode?: number;
2786
+ message?: string;
2787
+ }, import("@nice-code/error").JSONSerializableValue>;
2788
+ invalid_action_response: import("@nice-code/error").INiceErrorIdMetadata<{
2789
+ actionId: string;
2790
+ }, import("@nice-code/error").JSONSerializableValue>;
2791
+ };
2792
+ }>;
2793
+ //#endregion
2794
+ //#region src/ActionRuntime/Transport/Link/TransportLink.types.d.ts
2795
+ /** The per-connection codec (positional binary wire / JSON fallback) the carrier's session uses. */
2796
+ type TLinkFormatMessage = IActionWireFormat;
2797
+ interface IActionTransportReadyData_Link extends IActionTransportReadyData_Base {
2798
+ /** The live carrier this connection drives its session over. */
2799
+ channel: IDuplexCarrier;
2800
+ formatMessage?: TLinkFormatMessage;
2801
+ /** Optional authenticated/encrypted channel; the connection runs the handshake during init. */
2802
+ secureChannel?: ISecureClientConfig;
2803
+ }
2804
+ interface IActionTransportInitialized_Link extends IActionTransportInitialized<ITransportRouteActionParams, IActionTransportReadyData_Link> {}
2805
+ interface IActionTransportDef_Link extends IActionTransportDef<ETransportShape.duplex, IActionTransportInitialized_Link> {}
2806
+ //#endregion
2807
+ //#region src/ActionRuntime/Transport/Link/LinkConnection.d.ts
2808
+ /**
2809
+ * Carrier-agnostic live connection. It owns only the *bring-up* (open the carrier, then run the secure
2810
+ * session); the session itself — handshake, frame crypto, codec, send/receive — lives in the shared
2811
+ * {@link finalizeSecureLinkMethods}/{@link finalizePlainLinkMethods}, so a WebSocket, a WebRTC data
2812
+ * channel, a Bluetooth characteristic, and an in-memory pipe all run the identical secure layer.
2813
+ */
2814
+ declare class LinkConnection extends TransportConnection<ETransportShape.duplex, ITransportRouteActionParams, IActionTransportReadyData_Link, IActionTransportInitialized_Link, IActionTransportDef_Link> {
2815
+ private resolvers;
2816
+ constructor(def: Omit<IActionTransportDef_Link, "type">, resolvers?: IActionTransportResolvers);
2817
+ protected _getCacheKey(input: ITransportRouteActionParams): string;
2818
+ protected _needsAsyncBringUp(): boolean;
2819
+ protected _awaitCarrierReady(data: IActionTransportReadyData_Link): Promise<void>;
2820
+ protected _finalizeReady(data: IActionTransportReadyData_Link): IActionTransportReadyData_Methods | Promise<IActionTransportReadyData_Methods>;
2821
+ private _sessionContext;
2822
+ _finalizeTransportMethods(data: IActionTransportReadyData_Link): IActionTransportReadyData_Methods;
2823
+ }
2824
+ //#endregion
2825
+ //#region src/ActionRuntime/Transport/Link/LinkTransport.d.ts
2826
+ interface ILinkTransportOptions {
2039
2827
  /**
2040
- * Coordinate of the *connecting clients* (typically env-only, e.g. `RuntimeCoordinate.env("web_app")`),
2041
- * used to route results/pushes back over this handler.
2828
+ * Open (or reuse) the carrier for an action — a WebSocket adapter, a WebRTC data channel, a Bluetooth
2829
+ * characteristic, an in-memory pipe, anything that satisfies {@link IDuplexCarrier}.
2042
2830
  */
2043
- clientEnv: RuntimeCoordinate;
2044
- /** This server's runtime its coordinate is the server identity presented in the handshake. */
2045
- runtime: ActionRuntime;
2831
+ openChannel: (input: ITransportRouteActionParams) => IDuplexCarrier;
2832
+ /** Shared codec for every channel (stateless). */
2833
+ formatMessage?: TLinkFormatMessage;
2046
2834
  /**
2047
- * One backing store for the server's crypto identity *and* its trust-on-first-use verify-key pins.
2048
- * Their keys don't collide, so a single adapter is enough; back it with persistent storage (e.g. a
2049
- * Durable Object's storage) so identity and pins survive eviction.
2835
+ * Per-channel codec factory called once per opened channel so stateful codecs (e.g. the binary
2836
+ * session) get their own instance. Takes precedence over `formatMessage`.
2050
2837
  */
2051
- storageAdapter: StorageAdapter;
2052
- /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
2053
- send: (connection: TConn, frame: string | Uint8Array | ArrayBuffer) => void;
2054
- /** Accepted level(s); defaults to negotiating any of none/authenticated/encrypted. */
2055
- securityLevel?: ESecurityLevel | readonly ESecurityLevel[];
2056
- /** Trust decision for a client's verify key; defaults to storage-backed TOFU over `storageAdapter`. */
2057
- verifyKeyResolver?: IClientVerifyKeyResolver;
2058
- /** Timeout (ms) applied to server-initiated actions awaiting a client response. */
2059
- defaultTimeout?: number;
2838
+ createFormatMessage?: () => TLinkFormatMessage;
2839
+ /** Secure-channel config; when set (and `securityLevel !== none`) the handshake runs on init. */
2840
+ security?: ISecureClientConfig;
2841
+ updateRunConfig?: TUpdateActionRunConfig;
2842
+ /** Keys identifying a reusable channel, so one carrier is shared across actions to the same peer. */
2843
+ getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
2844
+ /** Short label for the devtools chip (defaults to "link"). */
2845
+ label?: string;
2846
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
2060
2847
  }
2061
2848
  /**
2062
- * Build an {@link ActionServerHandler} for the secure binary channel with the boilerplate folded in:
2063
- * it creates the {@link ClientCryptoKeyLink} and the storage-backed TOFU resolver from a single
2064
- * `storageAdapter`, installs the channel's per-connection codec, and assembles the `security` block
2065
- * from the runtime coordinate + channel version (accepting all three levels by default).
2066
- *
2067
- * For a hibernatable transport (e.g. a Durable Object), pair it with
2068
- * {@link createHibernatableWsServerAdapter} to wire persistence + replay.
2849
+ * A carrier-agnostic transport: it drives nice-action's secure session + action routing over any
2850
+ * {@link IDuplexCarrier}. The WebSocket transport is the special case that opens a `WebSocket`;
2851
+ * this opens whatever `openChannel` returns, so the identical secure layer works over WebRTC, Bluetooth,
2852
+ * or an in-memory pipe. Reported with an overridable carrier label in the devtools (defaults to "link").
2069
2853
  */
2070
- declare function createSecureActionServerHandler<TConn = unknown>(options: ISecureActionServerHandlerOptions<TConn>): ActionServerHandler<TConn>;
2071
- interface IHibernatableWsServerAdapterOptions<TConn> {
2072
- /** The handler to drive (from {@link createSecureActionServerHandler} or `createServerHandler`). */
2073
- handler: ActionServerHandler<TConn>;
2074
- /** All currently-live connections — replayed on construction to rebuild bindings after a wake. */
2075
- getWebSockets: () => TConn[];
2076
- /** Read a connection's persisted binding (e.g. `(ws) => ws.deserializeAttachment()`). */
2077
- getAttachment: (connection: TConn) => IActionServerConnectionBinding | undefined;
2078
- /** Persist a connection's binding when it is bound (e.g. `(ws, b) => ws.serializeAttachment(b)`). */
2079
- setAttachment: (connection: TConn, binding: IActionServerConnectionBinding) => void;
2854
+ declare class LinkTransport extends Transport<ETransportShape.duplex> {
2855
+ private readonly options;
2856
+ readonly type = ETransportShape.duplex;
2857
+ constructor(options: ILinkTransportOptions);
2858
+ static create(options: ILinkTransportOptions): LinkTransport;
2859
+ _createConnection(ctx: ITransportConnectionContext): LinkConnection;
2860
+ getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
2080
2861
  }
2081
- interface IHibernatableWsServerAdapter<TConn> {
2082
- /** Feed one inbound frame from a connection into the handler. */
2083
- receive: (connection: TConn, frame: string | ArrayBuffer | Uint8Array) => void;
2084
- /** Forget a connection (call on socket close/error). */
2085
- drop: (connection: TConn) => void;
2862
+ //#endregion
2863
+ //#region src/ActionRuntime/Transport/plainTransport.d.ts
2864
+ interface IPlainTransportOptions {
2865
+ /**
2866
+ * How to reach the peer with no security layer. A duplex carrier (`wsCarrier(url)`,
2867
+ * `rtcCarrier(dc)`, `inMemoryCarrier().carrier`) builds a push-capable {@link LinkTransport}; an
2868
+ * exchange carrier (`httpCarrier(...)`) builds a request/reply {@link ExchangeTransport}.
2869
+ */
2870
+ carrier: IDuplexCarrierSource | IExchangeCarrierSource;
2871
+ /**
2872
+ * Codec for a *duplex* carrier (a duplex link frames the action wire itself). Required for duplex;
2873
+ * ignored for an exchange carrier, which JSON-encodes the action wire in its envelope.
2874
+ */
2875
+ formatMessage?: TLinkFormatMessage;
2876
+ /** Per-channel codec factory for stateful duplex codecs (e.g. the binary session). */
2877
+ createFormatMessage?: () => TLinkFormatMessage;
2878
+ updateRunConfig?: TUpdateActionRunConfig;
2879
+ /** Override the devtools chip label (defaults to the carrier's `carrierLabel`). */
2880
+ label?: string;
2086
2881
  }
2087
2882
  /**
2088
- * Wire the hibernation lifecycle for a server handler on a transport whose sockets outlive process
2089
- * eviction (e.g. a Durable Object's hibernatable WebSockets). It owns persistence end to end:
2090
- * registers `setAttachment` as the handler's connection-bound callback and immediately replays every
2091
- * live connection's stored binding via `getAttachment`, so results/pushes still route after a wake.
2092
- *
2093
- * Construct it once when the handler is built, then forward socket events:
2094
- * ```ts
2095
- * const wsServer = createHibernatableWsServerAdapter({ handler, getWebSockets, getAttachment, setAttachment });
2096
- * // webSocketMessage(ws, msg) => wsServer.receive(ws, msg);
2097
- * // webSocketClose/Error(ws) => wsServer.drop(ws);
2098
- * ```
2883
+ * The plain (no-handshake, no-crypto) sibling of {@link secureTransport}: swap the carrier to change
2884
+ * protocol, with no security layer. Over an {@link IExchangeCarrierSource} it builds an
2885
+ * {@link ExchangeTransport} that POSTs the bare action wire and completes inline (HTTP is just
2886
+ * `carrier: httpCarrier(...)`); over an {@link IDuplexCarrierSource} it builds a push-capable
2887
+ * {@link LinkTransport} with the given codec. HTTP is therefore no longer a bespoke transport class —
2888
+ * it is "just another carrier", exactly like a WebSocket under {@link secureTransport}.
2889
+ */
2890
+ declare function plainTransport(options: IPlainTransportOptions & {
2891
+ carrier: IExchangeCarrierSource;
2892
+ }): ExchangeTransport;
2893
+ declare function plainTransport(options: IPlainTransportOptions & {
2894
+ carrier: IDuplexCarrierSource;
2895
+ }): LinkTransport;
2896
+ //#endregion
2897
+ //#region src/ActionRuntime/Transport/secureTransport.d.ts
2898
+ interface ISecureTransportOptions {
2899
+ /** The shared channel identity (per-connection codec + dictionary version) — same one both ends use. */
2900
+ channel: ISecureChannel;
2901
+ /** This client's runtime — its coordinate is the authenticated identity sent in the handshake. */
2902
+ runtime: ActionRuntime;
2903
+ /** Backing store for this client's crypto identity (a stable verify key across reloads). */
2904
+ storageAdapter: StorageAdapter;
2905
+ /** The level this client requests; the peer must allow it. */
2906
+ securityLevel: ESecurityLevel;
2907
+ /**
2908
+ * How to reach the peer. A duplex carrier (`wsCarrier(url)`, `rtcCarrier(dc)`,
2909
+ * `inMemoryCarrier().carrier`) runs the push-capable session; an exchange carrier (`httpCarrier(...)`)
2910
+ * runs the request/reply session over the same handshake + crypto.
2911
+ */
2912
+ carrier: IDuplexCarrierSource | IExchangeCarrierSource;
2913
+ }
2914
+ /**
2915
+ * The one secure-transport factory — swap the carrier to change protocol. Folds in the boilerplate (the
2916
+ * {@link ClientCryptoKeyLink} from `storageAdapter`, the `security` block from the runtime coordinate +
2917
+ * channel version) and drives it over whatever carrier you pass: a {@link IDuplexCarrierSource} builds a
2918
+ * push-capable {@link LinkTransport} (WS is just `carrier: wsCarrier(url)`), an
2919
+ * {@link IExchangeCarrierSource} builds a request/reply {@link ExchangeTransport} (HTTP, with the same
2920
+ * authentication/encryption). Replaces the old `createSecureWebSocketTransport` / `createSecureLinkTransport`.
2099
2921
  */
2100
- declare function createHibernatableWsServerAdapter<TConn>(options: IHibernatableWsServerAdapterOptions<TConn>): IHibernatableWsServerAdapter<TConn>;
2922
+ declare function secureTransport(options: ISecureTransportOptions & {
2923
+ carrier: IDuplexCarrierSource;
2924
+ }): LinkTransport;
2925
+ declare function secureTransport(options: ISecureTransportOptions & {
2926
+ carrier: IExchangeCarrierSource;
2927
+ }): ExchangeTransport;
2101
2928
  //#endregion
2102
2929
  //#region src/errors/err_nice_action.d.ts
2103
2930
  declare enum EErrId_NiceAction {
@@ -2198,7 +3025,7 @@ declare const err_nice_action: import("@nice-code/error").NiceErrorDomain<{
2198
3025
  //#region src/utils/decodeActionFrame.d.ts
2199
3026
  /**
2200
3027
  * Minimal codec shape needed to turn an incoming channel frame back into action wire JSON. Matches
2201
- * the `formatMessage` object the WebSocket transport (and `createBinaryWsAdapter`) provide.
3028
+ * the `formatMessage` object the WebSocket transport (and `createBinaryWireAdapter`) provide.
2202
3029
  */
2203
3030
  interface IActionFrameDecoder {
2204
3031
  incoming?: (frame: string | ArrayBuffer | Uint8Array | Blob) => TActionPayload_Any_JsonObject<any, any> | undefined;
@@ -2207,7 +3034,7 @@ interface IActionFrameDecoder {
2207
3034
  * Decode a single inbound channel frame (text or binary) into validated action wire JSON, or
2208
3035
  * `undefined` if it isn't a recognisable action payload.
2209
3036
  *
2210
- * Shared by the WebSocket transport's message listener and the server-side `ActionServerHandler` so
3037
+ * Shared by the WebSocket transport's message listener and the server-side `AcceptorHandler` so
2211
3038
  * both decode identically: a binary `decoder.incoming` (e.g. msgpackr) takes precedence, and plain
2212
3039
  * text frames fall back to JSON — keeping binary and JSON clients interoperable on one channel.
2213
3040
  */
@@ -2237,8 +3064,8 @@ interface IActionPayload_Base<DT extends EActionPayloadType, DOM extends IAction
2237
3064
  readonly type: DT;
2238
3065
  readonly context: ActionContext<DOM, ID>;
2239
3066
  }
2240
- type IActionRouteItemHandler = IActionHandler_Local_Json | (IActionHandler_ExternalClient_Json & {
2241
- transType: ETransportType;
3067
+ type IActionRouteItemHandler = IActionHandler_Local_Json | (IActionHandler_Peer_Json & {
3068
+ transShape: ETransportShape;
2242
3069
  transOrd: number;
2243
3070
  transInfo?: ITransportRouteInfo;
2244
3071
  });
@@ -2324,5 +3151,5 @@ interface IActionPayload_Result_JsonObject<DOM extends IActionDomain = IActionDo
2324
3151
  type TActionPayload_Any_Instance<DOM extends IActionDomain = IActionDomain, ID extends keyof DOM["actionSchema"] & string = keyof DOM["actionSchema"] & string> = ActionPayload_Request<DOM, ID> | ActionPayload_Result<DOM, ID> | ActionPayload_Progress<DOM, ID>;
2325
3152
  type TActionPayload_Any_JsonObject<DOM extends IActionDomain = IActionDomain, ID extends keyof DOM["actionSchema"] & string = keyof DOM["actionSchema"] & string> = IActionPayload_Request_JsonObject<DOM, ID> | IActionPayload_Progress_JsonObject<DOM, ID> | IActionPayload_Result_JsonObject<DOM, ID>;
2326
3153
  //#endregion
2327
- export { createBinaryWsAdapter as $, TInferOutputFromSchema as $n, ITransportDispatchAction as $t, createSecureActionServerHandler as A, ERunningActionState as An, ICustomTransportSendOptions as At, IConnectionAttachment as B, IRuntimeCoordinate as Bn, MaybePromise as Bt, decodeActionFrame as C, TUpdateActionRunConfig as Cn, IHttpTransportOptions as Ct, IHibernatableWsServerAdapterOptions as D, IActionCore as Dn, err_nice_transport as Dt, IHibernatableWsServerAdapter as E, ActionPayload_Request as En, EErrId_NiceTransport as Et, IActionServerHandlerOptions as F, IRunningActionUpdate_Success as Fn, ActionDomain as Ft, createSecureWebSocketTransport as G, TRuntimeCoordinateStringId as Gn, ETransportStatus as Gt, WsConnectionStateStore as H, IRuntimeFullCoordinates as Hn, createExternalClientHandler as Ht, TActionChannelFormatMessage as I, TRunningActionUpdate as In, ActionRootDomain as It, IWebSocketTransportSocketOptions as J, IActionRootDomain as Jn, IActionTransportInitialized as Jt, defineSecureWsChannel as K, IActionDomain as Kn, ETransportType as Kt, TActionConnectionEncoding as L, TRunningActionUpdateFinished as Ln, ActionRuntime as Lt, createActionFetchHandler as M, IRunningActionUpdate_Abort as Mn, err_nice_external_client as Mt, ActionServerHandler as N, IRunningActionUpdate_Progress as Nn, createActionRootDomain as Nt, ISecureActionServerHandlerOptions as O, IActionCore_JsonObject as On, CustomTransport as Ot, IActionServerConnectionBinding as P, IRunningActionUpdate_Started as Pn, ActionCore as Pt, createBinaryWsSessionFactory as Q, TInferInputFromSchema as Qn, IActionTransportResolvers as Qt, TServerConnectionCaseFn as R, TRunningActionUpdateListener as Rn, ActionLocalHandler as Rt, IActionFrameDecoder as S, TTransportStatusInfo_GetTransport_Output as Sn, HttpTransport as St, err_nice_action as T, ActionPayload_Progress as Tn, err_nice_transport_ws as Tt, ISecureWebSocketTransportOptions as U, RuntimeCoordinate as Un, ITransportConnectionContext as Ut, IWsConnectionStateStoreOptions as V, IRuntimeCoordinateSpecifics as Vn, ActionExternalClientHandler as Vt, ISecureWsChannel as W, TRuntimeCoordinateEnvId as Wn, Transport as Wt, WebSocketTransport as X, TActionDomainSchema as Xn, IActionTransportReadyData_Base as Xt, TWebSocketTransportOptions as Y, TActionDomainChildDef as Yn, IActionTransportReady as Yt, IBinaryWsSessionOptions as Z, TDomainActionId as Zn, IActionTransportReadyData_Methods as Zt, TActionProgress as _, TSendActionDataMethod as _n, encodeHandshakeMessage as _t, IActionPayload_Data_Base as a, ITransportStatusInfo_Failed as an, TActionSchemaOptions as ar, IClientHandshakeConfig as at, isActionPayload_Request_JsonObject as b, TTransportInitializationFinishedInfo as bn, IActionFrameCryptoConfig as bt, IActionPayload_Request_JsonObject as c, ITransportStatusInfo_Unsupported as cn, IHandshakeEncryptionKeyMaterial as ct, IActionProgress_Custom as d, TOnResolveAnyIncomingActionData as dn, THandshakeMessage as dt, ITransportMethod_SendActionData_Input as en, TPossibleDomainId as er, IActionTransportDef_Ws as et, IActionProgress_None as f, TOnResolveAnyIncomingActionData_Json as fn, createClientHandshake as ft, TActionPayload_Any_JsonObject as g, TOnResolveIncomingResponseJson as gn, decodeHandshakeMessage as gt, TActionPayload_Any_Instance as h, TOnResolveIncomingResponse as hn, createStorageTofuVerifyKeyResolver as ht, IActionPayload_Base_JsonObject as i, ITransportStatusInfo_Base as in, actionSchema as ir, ESecurityLevel as it, IActionFetchHandlerOptions as j, ERunningActionUpdateType as jn, TCustomTransportOptions as jt, createHibernatableWsServerAdapter as k, ERunningActionFinishedType as kn, ICustomTransportAdvancedOptions as kt, IActionPayload_Result as l, IUpdateActionRunConfig_Output as ln, IHandshakeResult as lt, IActionRouteItemHandler as m, TOnResolveIncomingRequestJson as mn, createServerHandshake as mt, EActionProgressType as n, ITransportRouteClientParams as nn, ActionSchema as nr, IActionTransportReadyData_Ws as nt, IActionPayload_Progress as o, ITransportStatusInfo_Initializing as on, TActionSerializationDefinition as or, IClientVerifyKeyResolveInput as ot, IActionProgress_Percentage as p, TOnResolveIncomingRequest as pn, createInMemoryTofuVerifyKeyResolver as pt, IWebSocketTransportAdvancedOptions as q, IActionDomainChildOptions as qn, IActionTransportDef as qt, IActionPayload_Base as r, ITransportRouteInfo as rn, TInferActionError as rr, EHandshakeMessageType as rt, IActionPayload_Progress_JsonObject as s, ITransportStatusInfo_Ready as sn, TTransportedValue as sr, IClientVerifyKeyResolver as st, EActionPayloadType as t, ITransportRouteActionParams as tn, TPossibleDomainIdList as tr, IActionTransportInitialized_Ws as tt, IActionPayload_Result_JsonObject as u, TGetTransportFn as un, IServerHandshakeConfig as ut, TActionResultOutcome as v, TSendReturnDataMethod as vn, runtimeLinkId as vt, EErrId_NiceAction as w, RunningAction as wn, EErrId_NiceTransport_WebSocket as wt, isActionPayload_Any_JsonObject as x, TTransportStatusInfo as xn, createActionFrameCrypto as xt, isActionPayload_Result_JsonObject as y, TTransportCache as yn, IActionFrameCrypto as yt, createServerHandler as z, ActionPayload_Result as zn, createLocalHandler as zt };
2328
- //# sourceMappingURL=ActionPayload.types-BN-rXFBK.d.cts.map
3154
+ export { EErrId_NiceTransport_WebSocket as $, IClientVerifyKeyResolver as $n, TTransportedValue as $r, IAcceptorConnectionBinding as $t, ILinkTransportOptions as A, ITransportStatusInfo_Ready as An, ActionPayload_Result as Ar, IDuplexCarrier as At, IActionFrameCryptoConfig as B, TSendActionDataMethod as Bn, TActionDomainChildDef as Br, IActionChannel as Bt, decodeActionFrame as C, ITransportMethod_SendActionData_Input as Cn, IRunningActionUpdate_Abort as Cr, IServeChannelOptions as Ct, secureTransport as D, ITransportStatusInfo_Base as Dn, TRunningActionUpdate as Dr, IExchangeAcceptorCarrier as Dt, ISecureTransportOptions as E, ITransportRouteInfo as En, IRunningActionUpdate_Success as Er, IDuplexAcceptorCarrier as Et, err_nice_transport as F, TOnResolveAnyIncomingActionData_Json as Fn, TRuntimeCoordinateEnvId as Fr, TFrame$1 as Ft, TCarrierFetch as G, TTransportStatusInfo_GetTransport_Output as Gn, TPossibleDomainId as Gr, acceptChannelConnections as Gt, createBinaryWireAdapter as H, TTransportCache as Hn, TDomainActionId as Hr, TChannelAcceptorCases as Ht, ExchangeTransport as I, TOnResolveIncomingRequest as In, TRuntimeCoordinateStringId as Ir, IDuplexConnectionRouter as It, httpAcceptorCarrier as J, Transport as Jn, EActionResponseMode as Jr, ISecureChannel as Jt, httpCarrier as K, TUpdateActionRunConfig as Kn, TPossibleDomainIdList as Kr, connectChannel as Kt, IExchangeTransportOptions as L, TOnResolveIncomingRequestJson as Ln, IActionDomain as Lr, IHibernatableWsServerAdapterOptions as Lt, IActionTransportReadyData_Link as M, IUpdateActionRunConfig_Output as Mn, IRuntimeCoordinateSpecifics as Mr, IExchangeCarrier as Mt, TLinkFormatMessage as N, TGetTransportFn as Nn, IRuntimeFullCoordinates as Nr, IExchangeCarrierSource as Nt, IPlainTransportOptions as O, ITransportStatusInfo_Failed as On, TRunningActionUpdateFinished as Or, TAcceptorCarrier as Ot, EErrId_NiceTransport as P, TOnResolveAnyIncomingActionData as Pn, RuntimeCoordinate as Pr, TCarrier as Pt, wsAcceptorCarrier as Q, IClientVerifyKeyResolveInput as Qn, TActionSerializationDefinition as Qr, AcceptorHandler as Qt, IActionTransportReadyData_Exchange as R, TOnResolveIncomingResponse as Rn, IActionDomainChildOptions as Rr, createHibernatableWsServerAdapter as Rt, IActionFrameDecoder as S, ITransportDispatchAction as Sn, ERunningActionUpdateType as Sr, IChannelServer as St, err_nice_action as T, ITransportRouteClientParams as Tn, IRunningActionUpdate_Started as Tr, IAcceptorHibernation as Tt, IHttpCarrierOptions as U, TTransportInitializationFinishedInfo as Un, TInferInputFromSchema as Ur, TChannelPushHandlers as Ut, createActionFrameCrypto as V, TSendReturnDataMethod as Vn, TActionDomainSchema as Vr, IConnectChannelOptions as Vt, IHttpCarrierRequest as W, TTransportStatusInfo as Wn, TInferOutputFromSchema as Wr, acceptChannel as Wt, wsCarrier as X, ESecurityLevel as Xn, actionSchema as Xr, IBinaryWireSessionOptions as Xt, IWsCarrierOptions as Y, EHandshakeMessageType as Yn, TInferActionError as Yr, defineSecureChannel as Yt, IWsAcceptorCarrierOptions as Z, IClientHandshakeConfig as Zn, TActionSchemaOptions as Zr, createBinaryWireSessionFactory as Zt, TActionProgress as _, IActionTransportReady as _n, ActionPayload_Request as _r, IActionFetchHandlerOptions as _t, IActionPayload_Data_Base as a, IActionWireFormat as an, createInMemoryTofuVerifyKeyResolver as ar, IInMemoryCarrier as at, isActionPayload_Request_JsonObject as b, IActionTransportResolvers as bn, ERunningActionFinishedType as br, IExchangeAcceptorConfig as bt, IActionPayload_Request_JsonObject as c, ActionDomain as cn, decodeHandshakeMessage as cr, IInMemoryServerEndpoint as ct, IActionProgress_Custom as d, ConnectorHandler as dn, PeerLinkHandler as dr, ConnectionStateStore as dt, IAcceptorHandlerOptions as en, IHandshakeEncryptionKeyMaterial as er, err_nice_transport_ws as et, IActionProgress_None as f, createConnectorHandler as fn, ActionLocalHandler as fr, IConnectionAttachment as ft, TActionPayload_Any_JsonObject as g, IActionTransportInitialized as gn, ActionPayload_Progress as gr, createSecureAcceptorHandler as gt, TActionPayload_Any_Instance as h, IActionTransportDef as hn, RunningAction as hr, ISecureAcceptorHandlerOptions as ht, IActionPayload_Base_JsonObject as i, createAcceptorHandler as in, createClientHandshake as ir, rtcDataChannelByteChannel as it, LinkTransport as j, ITransportStatusInfo_Unsupported as jn, IRuntimeCoordinate as jr, IDuplexCarrierSource as jt, plainTransport as k, ITransportStatusInfo_Initializing as kn, TRunningActionUpdateListener as kr, isExchangeAcceptorCarrier as kt, IActionPayload_Result as l, ActionRootDomain as ln, encodeHandshakeMessage as lr, createInMemoryChannelPair as lt, IActionRouteItemHandler as m, ETransportStatus as mn, MaybePromise as mr, createConnectionStateStore as mt, EActionProgressType as n, TActionChannelFormatMessage as nn, IServerHandshakeConfig as nr, rtcCarrier as nt, IActionPayload_Progress as o, createActionRootDomain as on, createServerHandshake as or, inMemoryCarrier as ot, IActionProgress_Percentage as p, ETransportShape as pn, createLocalHandler as pr, IConnectionStateStoreOptions as pt, IHttpAcceptorCarrierOptions as q, ITransportConnectionContext as qn, ActionSchema as qr, defineChannel as qt, IActionPayload_Base as r, TActionConnectionEncoding as rn, THandshakeMessage as rr, IRtcDataChannelLike as rt, IActionPayload_Progress_JsonObject as s, ActionCore as sn, createStorageTofuVerifyKeyResolver as sr, IInMemoryChannelPair as st, EActionPayloadType as t, TAcceptorConnectionCaseFn as tn, IHandshakeResult as tr, IRtcCarrierOptions as tt, IActionPayload_Result_JsonObject as u, ActionRuntime as un, runtimeLinkId as ur, err_nice_external_client as ut, TActionResultOutcome as v, IActionTransportReadyData_Base as vn, IActionCore as vr, createActionFetchHandler as vt, EErrId_NiceAction as w, ITransportRouteActionParams as wn, IRunningActionUpdate_Progress as wr, serveChannel as wt, isActionPayload_Any_JsonObject as x, ISecureClientConfig as xn, ERunningActionState as xr, IExchangeAcceptorSecurity as xt, isActionPayload_Result_JsonObject as y, IActionTransportReadyData_Methods as yn, IActionCore_JsonObject as yr, ExchangeAcceptor as yt, IActionFrameCrypto as z, TOnResolveIncomingResponseJson as zn, IActionRootDomain as zr, IAcceptChannelOptions as zt };
3155
+ //# sourceMappingURL=ActionPayload.types-BchJrBIX.d.mts.map