@nice-code/action 0.18.0 → 0.20.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 (30) hide show
  1. package/README.md +699 -637
  2. package/build/{ActionDevtoolsCore-C5XrQI1K.d.mts → ActionDevtoolsCore-D_JvgPmz.d.mts} +2 -2
  3. package/build/{ActionDevtoolsCore-Dd1qJAwK.d.cts → ActionDevtoolsCore-dV-IVPcP.d.cts} +2 -2
  4. package/build/{ActionPayload.types-BchJrBIX.d.mts → ActionPayload.types-CnfWlkA1.d.cts} +252 -106
  5. package/build/{ActionPayload.types-snDlSIF-.d.cts → ActionPayload.types-D0DM-g65.d.mts} +252 -106
  6. package/build/devtools/browser/index.d.cts +1 -1
  7. package/build/devtools/browser/index.d.mts +1 -1
  8. package/build/devtools/server/index.d.cts +1 -1
  9. package/build/devtools/server/index.d.mts +1 -1
  10. package/build/index.cjs +169 -138
  11. package/build/index.cjs.map +1 -1
  12. package/build/index.d.cts +2 -2
  13. package/build/index.d.mts +2 -2
  14. package/build/index.mjs +167 -139
  15. package/build/index.mjs.map +1 -1
  16. package/build/platform/cloudflare/index.cjs +8 -4
  17. package/build/platform/cloudflare/index.cjs.map +1 -1
  18. package/build/platform/cloudflare/index.d.cts +8 -3
  19. package/build/platform/cloudflare/index.d.mts +8 -3
  20. package/build/platform/cloudflare/index.mjs +8 -4
  21. package/build/platform/cloudflare/index.mjs.map +1 -1
  22. package/build/react-query/index.d.cts +1 -1
  23. package/build/react-query/index.d.mts +1 -1
  24. package/build/{wsAcceptorCarrier-DHRbsY1X.cjs → wsAcceptorCarrier-BDJRIPfu.cjs} +2 -2
  25. package/build/wsAcceptorCarrier-BDJRIPfu.cjs.map +1 -0
  26. package/build/{wsAcceptorCarrier-CXGlQU_f.mjs → wsAcceptorCarrier-CW2qX25W.mjs} +2 -2
  27. package/build/wsAcceptorCarrier-CW2qX25W.mjs.map +1 -0
  28. package/package.json +4 -4
  29. package/build/wsAcceptorCarrier-CXGlQU_f.mjs.map +0 -1
  30. package/build/wsAcceptorCarrier-DHRbsY1X.cjs.map +0 -1
package/build/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
3
3
  const require_RunningAction_types = require("./RunningAction.types-DjCX1xp5.cjs");
4
- const require_wsAcceptorCarrier = require("./wsAcceptorCarrier-DHRbsY1X.cjs");
4
+ const require_wsAcceptorCarrier = require("./wsAcceptorCarrier-BDJRIPfu.cjs");
5
5
  let nanoid = require("nanoid");
6
6
  let _nice_code_error = require("@nice-code/error");
7
7
  let _nice_code_common_errors = require("@nice-code/common-errors");
@@ -879,6 +879,10 @@ var ConnectionTransportManager = class {
879
879
  const candidates = [];
880
880
  const unavailableTransports = [];
881
881
  for (const transport of this._transports) {
882
+ if (!transport.isAvailable(routeActionParams)) {
883
+ unavailableTransports.push(transport);
884
+ continue;
885
+ }
882
886
  const cacheKey = transport.getCacheKey(routeActionParams);
883
887
  if (cacheKey != null) {
884
888
  const cached = this._cache.get(cacheKey);
@@ -3534,6 +3538,113 @@ function createActionFetchHandler(runtime, options = {}) {
3534
3538
  };
3535
3539
  }
3536
3540
  //#endregion
3541
+ //#region src/ActionRuntime/Handler/PeerLink/Acceptor/Hibernation/ConnectionStateStore.ts
3542
+ /**
3543
+ * A typed per-connection state store that co-owns the app state and the acceptor handler's routing
3544
+ * binding in one attachment, so neither the consumer nor the handler has to hand-merge the two. Create
3545
+ * it through {@link createConnectionStateStore} (which also wires binding persistence and replays
3546
+ * surviving connections after a wake), then `get`/`set`/`clearApp` the app state directly.
3547
+ *
3548
+ * The mechanism is carrier-neutral — it only needs read/write/enumerate callbacks for the connection's
3549
+ * attachment — but it pays off on transports whose connections outlive process eviction (e.g. a
3550
+ * Durable Object's hibernatable WebSockets), which is why it lives beside the hibernation adapter.
3551
+ *
3552
+ * ```ts
3553
+ * const players = createConnectionStateStore(serverHandler, {
3554
+ * schema: vs_player,
3555
+ * read: (ws) => ws.deserializeAttachment(),
3556
+ * write: (ws, v) => ws.serializeAttachment(v),
3557
+ * getConnections: () => ctx.getWebSockets(),
3558
+ * });
3559
+ * players.set(ws, player); // binding is preserved automatically
3560
+ * const player = players.get(ws);
3561
+ * ```
3562
+ */
3563
+ var ConnectionStateStore = class {
3564
+ options;
3565
+ constructor(options) {
3566
+ this.options = options;
3567
+ }
3568
+ /** The validated app state for a connection, or `null` if unset / invalid. */
3569
+ get(connection) {
3570
+ return this._readAttachment(connection).app ?? null;
3571
+ }
3572
+ /** Set the app state, preserving the runtime binding already pinned to the connection. */
3573
+ set(connection, app) {
3574
+ const existing = this._readAttachment(connection);
3575
+ this.options.write(connection, {
3576
+ app,
3577
+ binding: existing.binding
3578
+ });
3579
+ }
3580
+ /** Clear the app state but keep the binding (e.g. a spectator that stopped watching). */
3581
+ clearApp(connection) {
3582
+ const existing = this._readAttachment(connection);
3583
+ this.options.write(connection, { binding: existing.binding });
3584
+ }
3585
+ /** Every live connection paired with its (validated) app state — for rebuilding in-memory state after a wake. */
3586
+ entries() {
3587
+ return this.options.getConnections().map((connection) => [connection, this._readAttachment(connection).app ?? null]);
3588
+ }
3589
+ /** @internal Persist a freshly-bound connection's binding, preserving any app state already stored. */
3590
+ _persistBinding(connection, binding) {
3591
+ const existing = this._readAttachment(connection);
3592
+ this.options.write(connection, {
3593
+ app: existing.app,
3594
+ binding
3595
+ });
3596
+ }
3597
+ /** @internal The persisted binding for a connection, if any (used to replay routing after a wake). */
3598
+ _readBinding(connection) {
3599
+ return this._readAttachment(connection).binding;
3600
+ }
3601
+ _readAttachment(connection) {
3602
+ try {
3603
+ const raw = this.options.read(connection);
3604
+ if (typeof raw !== "object" || raw === null) return {};
3605
+ const attachment = raw;
3606
+ const result = {};
3607
+ if (attachment.binding != null) result.binding = attachment.binding;
3608
+ if (attachment.app !== void 0) {
3609
+ const app = this._validateApp(attachment.app);
3610
+ if (app !== void 0) result.app = app;
3611
+ }
3612
+ return result;
3613
+ } catch {
3614
+ return {};
3615
+ }
3616
+ }
3617
+ _validateApp(value) {
3618
+ const schema = this.options.schema;
3619
+ if (schema == null) return value;
3620
+ const result = schema["~standard"].validate(value);
3621
+ if (result instanceof Promise) return void 0;
3622
+ if (result.issues != null) return void 0;
3623
+ return result.value;
3624
+ }
3625
+ };
3626
+ /**
3627
+ * Build a per-connection {@link ConnectionStateStore} bound to an {@link AcceptorHandler}: it registers
3628
+ * itself as the handler's connection-bound persistence callback (so bindings are written without
3629
+ * overwriting app state) and immediately replays every live connection's stored binding via
3630
+ * {@link AcceptorHandler.rehydrateConnection} — so on a transport that resumes after eviction (e.g. a
3631
+ * Durable Object waking from hibernation) both the app identity and the action routing come back from a
3632
+ * single attachment, with no storage reads and no hand-rolled merge.
3633
+ *
3634
+ * Lives outside the handler so the generic {@link AcceptorHandler} stays free of any attachment/
3635
+ * hibernation concern — it exposes only the neutral `setOnConnectionBound` + `rehydrateConnection`
3636
+ * hooks this builder drives.
3637
+ */
3638
+ function createConnectionStateStore(handler, options) {
3639
+ const store = new ConnectionStateStore(options);
3640
+ handler.setOnConnectionBound((connection, binding) => store._persistBinding(connection, binding));
3641
+ for (const connection of options.getConnections()) {
3642
+ const binding = store._readBinding(connection);
3643
+ if (binding != null) handler.rehydrateConnection(connection, binding);
3644
+ }
3645
+ return store;
3646
+ }
3647
+ //#endregion
3537
3648
  //#region src/ActionRuntime/Handler/PeerLink/Acceptor/Hibernation/createHibernatableWsServerAdapter.ts
3538
3649
  /**
3539
3650
  * Wire the hibernation lifecycle for an acceptor handler on a transport whose connections outlive process
@@ -3572,34 +3683,14 @@ const DEFAULT_SERVER_SECURITY_LEVELS = [
3572
3683
  "authenticated",
3573
3684
  "encrypted"
3574
3685
  ];
3575
- /**
3576
- * Serve a secure channel over one or more carriers from a single call — the accept-in dual of
3577
- * `connectChannel`. It builds the crypto identity (a {@link ClientCryptoKeyLink} + a storage-backed TOFU
3578
- * resolver) and the security block (coordinate, dictionary version, accepted levels) *once* from
3579
- * `(runtime, channel)` and fans them across every carrier, so the WebSocket and the secure-HTTP endpoint
3580
- * can never drift apart. It registers your handlers (plus the duplex acceptor it builds) on the runtime,
3581
- * wires hibernation when the duplex carrier declares it, and returns a single {@link IChannelServer} whose
3582
- * `fetch` / `duplex` / `pushToClient` you forward straight to the host:
3583
- * ```ts
3584
- * const server = serveChannel(runtime, channel, {
3585
- * clientEnv, storage,
3586
- * carriers: [wsAcceptorCarrier({ send, upgrade, hibernation }), httpAcceptorCarrier()],
3587
- * handlers: [localHandler],
3588
- * });
3589
- * // fetch(req) => server.fetch(req)
3590
- * // webSocketMessage(conn, m) => server.duplex?.receive(conn, m)
3591
- * // webSocketClose/Error(conn) => server.duplex?.drop(conn)
3592
- * ```
3593
- *
3594
- * `TConn` (the live-connection token a duplex carrier hands back through `send`/`receive`/`drop`) is
3595
- * inferred from the carriers — `WebSocket` for `wsAcceptorCarrier`, the data-channel type for a WebRTC
3596
- * carrier, and so on — so it stays carrier-agnostic.
3597
- */
3598
3686
  function serveChannel(runtime, channel, options) {
3599
3687
  const duplexCarriers = options.carriers.filter((carrier) => !require_wsAcceptorCarrier.isExchangeAcceptorCarrier(carrier));
3600
3688
  const exchangeCarriers = options.carriers.filter(require_wsAcceptorCarrier.isExchangeAcceptorCarrier);
3601
3689
  if (exchangeCarriers.length > 1) throw new Error("serveChannel: at most one exchange carrier is supported");
3602
3690
  const exchangeCarrier = exchangeCarriers[0];
3691
+ const singleDuplex = duplexCarriers.length === 1;
3692
+ if (options.connectionState != null && !singleDuplex) throw new Error("serveChannel: `connectionState` requires exactly one duplex carrier");
3693
+ if (options.channelCases != null && !singleDuplex) throw new Error("serveChannel: `channelCases` requires exactly one duplex carrier");
3603
3694
  const exchangeSecure = exchangeCarrier != null && (exchangeCarrier.secure ?? true);
3604
3695
  const anyDuplexSecure = duplexCarriers.some((carrier) => carrier.secure ?? true);
3605
3696
  const securityLevel = options.securityLevel ?? DEFAULT_SERVER_SECURITY_LEVELS;
@@ -3613,7 +3704,13 @@ function serveChannel(runtime, channel, options) {
3613
3704
  verifyKeyResolver: options.verifyKeyResolver ?? createStorageTofuVerifyKeyResolver(storage)
3614
3705
  };
3615
3706
  }
3707
+ const plainRouter = (handler) => ({
3708
+ receive: (connection, frame) => handler.receive(connection, frame),
3709
+ drop: (connection) => handler.dropConnection(connection)
3710
+ });
3711
+ const asObject = (value) => typeof value === "object" && value != null ? value : {};
3616
3712
  const handlers = [];
3713
+ let connections;
3617
3714
  for (const carrier of duplexCarriers) {
3618
3715
  const handler = (carrier.secure ?? true) && secure != null ? acceptChannel(runtime, channel, {
3619
3716
  clientEnv: options.clientEnv,
@@ -3630,17 +3727,31 @@ function serveChannel(runtime, channel, options) {
3630
3727
  runtime,
3631
3728
  defaultTimeout: options.defaultTimeout
3632
3729
  });
3633
- const router = carrier.hibernation != null ? createHibernatableWsServerAdapter({
3730
+ const attach = carrier.attachmentStore;
3731
+ let router;
3732
+ if (attach == null) router = plainRouter(handler);
3733
+ else if (options.connectionState != null) {
3734
+ connections = createConnectionStateStore(handler, {
3735
+ schema: options.connectionState.schema,
3736
+ getConnections: attach.getConnections,
3737
+ read: attach.read,
3738
+ write: attach.write
3739
+ });
3740
+ router = plainRouter(handler);
3741
+ } else router = createHibernatableWsServerAdapter({
3634
3742
  handler,
3635
- ...carrier.hibernation
3636
- }) : {
3637
- receive: (connection, frame) => handler.receive(connection, frame),
3638
- drop: (connection) => handler.dropConnection(connection)
3639
- };
3743
+ getConnections: attach.getConnections,
3744
+ getAttachment: (connection) => attach.read(connection)?.binding,
3745
+ setAttachment: (connection, binding) => attach.write(connection, {
3746
+ ...asObject(attach.read(connection)),
3747
+ binding
3748
+ })
3749
+ });
3640
3750
  carrier._activate(router);
3641
3751
  handlers.push(handler);
3642
3752
  }
3643
3753
  runtime.addHandlers([...options.handlers ?? [], ...handlers]);
3754
+ if (options.channelCases != null) runtime.addHandlers([acceptChannelConnections(handlers[0], channel, options.channelCases)]);
3644
3755
  const exchangeSecurity = exchangeSecure && secure != null ? {
3645
3756
  link: secure.link,
3646
3757
  verifyKeyResolver: secure.verifyKeyResolver,
@@ -3671,121 +3782,23 @@ function serveChannel(runtime, channel, options) {
3671
3782
  if (owner == null) throw new Error("serveChannel: no duplex carrier holds a connection for the push target");
3672
3783
  return owner.pushToClient(runtime, target, request, pushOptions);
3673
3784
  };
3785
+ const broadcast = (makeRequest, broadcastOptions) => {
3786
+ if (!singleDuplex) throw new Error("serveChannel: broadcast requires exactly one duplex carrier — broadcast over a specific handlers[i] instead");
3787
+ handlers[0].broadcast(makeRequest, {
3788
+ runtime,
3789
+ ...broadcastOptions
3790
+ });
3791
+ };
3674
3792
  return {
3675
3793
  handlers,
3676
3794
  fetch,
3677
3795
  duplex,
3678
- pushToClient
3796
+ pushToClient,
3797
+ broadcast,
3798
+ connections
3679
3799
  };
3680
3800
  }
3681
3801
  //#endregion
3682
- //#region src/ActionRuntime/Handler/PeerLink/Acceptor/Hibernation/ConnectionStateStore.ts
3683
- /**
3684
- * A typed per-connection state store that co-owns the app state and the acceptor handler's routing
3685
- * binding in one attachment, so neither the consumer nor the handler has to hand-merge the two. Create
3686
- * it through {@link createConnectionStateStore} (which also wires binding persistence and replays
3687
- * surviving connections after a wake), then `get`/`set`/`clearApp` the app state directly.
3688
- *
3689
- * The mechanism is carrier-neutral — it only needs read/write/enumerate callbacks for the connection's
3690
- * attachment — but it pays off on transports whose connections outlive process eviction (e.g. a
3691
- * Durable Object's hibernatable WebSockets), which is why it lives beside the hibernation adapter.
3692
- *
3693
- * ```ts
3694
- * const players = createConnectionStateStore(serverHandler, {
3695
- * schema: vs_player,
3696
- * read: (ws) => ws.deserializeAttachment(),
3697
- * write: (ws, v) => ws.serializeAttachment(v),
3698
- * getConnections: () => ctx.getWebSockets(),
3699
- * });
3700
- * players.set(ws, player); // binding is preserved automatically
3701
- * const player = players.get(ws);
3702
- * ```
3703
- */
3704
- var ConnectionStateStore = class {
3705
- options;
3706
- constructor(options) {
3707
- this.options = options;
3708
- }
3709
- /** The validated app state for a connection, or `null` if unset / invalid. */
3710
- get(connection) {
3711
- return this._readAttachment(connection).app ?? null;
3712
- }
3713
- /** Set the app state, preserving the runtime binding already pinned to the connection. */
3714
- set(connection, app) {
3715
- const existing = this._readAttachment(connection);
3716
- this.options.write(connection, {
3717
- app,
3718
- binding: existing.binding
3719
- });
3720
- }
3721
- /** Clear the app state but keep the binding (e.g. a spectator that stopped watching). */
3722
- clearApp(connection) {
3723
- const existing = this._readAttachment(connection);
3724
- this.options.write(connection, { binding: existing.binding });
3725
- }
3726
- /** Every live connection paired with its (validated) app state — for rebuilding in-memory state after a wake. */
3727
- entries() {
3728
- return this.options.getConnections().map((connection) => [connection, this._readAttachment(connection).app ?? null]);
3729
- }
3730
- /** @internal Persist a freshly-bound connection's binding, preserving any app state already stored. */
3731
- _persistBinding(connection, binding) {
3732
- const existing = this._readAttachment(connection);
3733
- this.options.write(connection, {
3734
- app: existing.app,
3735
- binding
3736
- });
3737
- }
3738
- /** @internal The persisted binding for a connection, if any (used to replay routing after a wake). */
3739
- _readBinding(connection) {
3740
- return this._readAttachment(connection).binding;
3741
- }
3742
- _readAttachment(connection) {
3743
- try {
3744
- const raw = this.options.read(connection);
3745
- if (typeof raw !== "object" || raw === null) return {};
3746
- const attachment = raw;
3747
- const result = {};
3748
- if (attachment.binding != null) result.binding = attachment.binding;
3749
- if (attachment.app !== void 0) {
3750
- const app = this._validateApp(attachment.app);
3751
- if (app !== void 0) result.app = app;
3752
- }
3753
- return result;
3754
- } catch {
3755
- return {};
3756
- }
3757
- }
3758
- _validateApp(value) {
3759
- const schema = this.options.schema;
3760
- if (schema == null) return value;
3761
- const result = schema["~standard"].validate(value);
3762
- if (result instanceof Promise) return void 0;
3763
- if (result.issues != null) return void 0;
3764
- return result.value;
3765
- }
3766
- };
3767
- /**
3768
- * Build a per-connection {@link ConnectionStateStore} bound to an {@link AcceptorHandler}: it registers
3769
- * itself as the handler's connection-bound persistence callback (so bindings are written without
3770
- * overwriting app state) and immediately replays every live connection's stored binding via
3771
- * {@link AcceptorHandler.rehydrateConnection} — so on a transport that resumes after eviction (e.g. a
3772
- * Durable Object waking from hibernation) both the app identity and the action routing come back from a
3773
- * single attachment, with no storage reads and no hand-rolled merge.
3774
- *
3775
- * Lives outside the handler so the generic {@link AcceptorHandler} stays free of any attachment/
3776
- * hibernation concern — it exposes only the neutral `setOnConnectionBound` + `rehydrateConnection`
3777
- * hooks this builder drives.
3778
- */
3779
- function createConnectionStateStore(handler, options) {
3780
- const store = new ConnectionStateStore(options);
3781
- handler.setOnConnectionBound((connection, binding) => store._persistBinding(connection, binding));
3782
- for (const connection of options.getConnections()) {
3783
- const binding = store._readBinding(connection);
3784
- if (binding != null) handler.rehydrateConnection(connection, binding);
3785
- }
3786
- return store;
3787
- }
3788
- //#endregion
3789
3802
  //#region src/ActionRuntime/Transport/Carrier/duplex/inMemory/createInMemoryChannel.ts
3790
3803
  /**
3791
3804
  * Two cross-wired in-process byte channels — a loopback carrier with no socket. The client end is a
@@ -4440,6 +4453,15 @@ var TransportConnection = class {
4440
4453
  if (inner == null) return null;
4441
4454
  return `${this.transOrd}:${inner}`;
4442
4455
  }
4456
+ /**
4457
+ * Whether this transport can serve the given action right now. Consulted by the manager before
4458
+ * cache-key resolution and `getTransport`; a `false` result skips this transport (treated as
4459
+ * `unsupported`) and the manager falls through to the next in preference order. Defaults to `true`
4460
+ * when the transport declares no gate.
4461
+ */
4462
+ isAvailable(input) {
4463
+ return this.initialized.isAvailable?.(input) ?? true;
4464
+ }
4443
4465
  getTransport(input) {
4444
4466
  return this._processTransportStatus(input);
4445
4467
  }
@@ -4536,6 +4558,7 @@ var ExchangeTransport = class ExchangeTransport extends Transport {
4536
4558
  const options = this.options;
4537
4559
  return new ExchangeConnection({ initialize: () => ({
4538
4560
  getTransportCacheKey: options.getTransportCacheKey,
4561
+ isAvailable: options.available,
4539
4562
  getTransport: (input) => ({
4540
4563
  status: "ready",
4541
4564
  readyData: {
@@ -4784,6 +4807,7 @@ var LinkTransport = class LinkTransport extends Transport {
4784
4807
  const options = this.options;
4785
4808
  return new LinkConnection({ initialize: () => ({
4786
4809
  getTransportCacheKey: options.getTransportCacheKey,
4810
+ isAvailable: options.available,
4787
4811
  getTransport: (input) => ({
4788
4812
  status: "ready",
4789
4813
  readyData: {
@@ -4820,6 +4844,7 @@ function plainTransport(options) {
4820
4844
  if (isExchangeCarrierSource(carrier)) return ExchangeTransport.create({
4821
4845
  openCarrier: carrier.open,
4822
4846
  getTransportCacheKey: carrier.getCacheKey,
4847
+ available: options.available,
4823
4848
  getRouteInfo: carrier.getRouteInfo,
4824
4849
  label: options.label ?? carrier.carrierLabel,
4825
4850
  updateRunConfig: options.updateRunConfig
@@ -4829,6 +4854,7 @@ function plainTransport(options) {
4829
4854
  formatMessage: options.formatMessage,
4830
4855
  createFormatMessage: options.createFormatMessage,
4831
4856
  getTransportCacheKey: carrier.getCacheKey,
4857
+ available: options.available,
4832
4858
  getRouteInfo: carrier.getRouteInfo,
4833
4859
  label: options.label ?? carrier.carrierLabel,
4834
4860
  updateRunConfig: options.updateRunConfig
@@ -4848,6 +4874,7 @@ function secureTransport(options) {
4848
4874
  if (isExchangeCarrierSource(carrier)) return ExchangeTransport.create({
4849
4875
  openCarrier: carrier.open,
4850
4876
  getTransportCacheKey: carrier.getCacheKey,
4877
+ available: options.available,
4851
4878
  getRouteInfo: carrier.getRouteInfo,
4852
4879
  label: carrier.carrierLabel,
4853
4880
  security
@@ -4856,6 +4883,7 @@ function secureTransport(options) {
4856
4883
  openChannel: carrier.open,
4857
4884
  createFormatMessage: options.channel.createCodec,
4858
4885
  getTransportCacheKey: carrier.getCacheKey,
4886
+ available: options.available,
4859
4887
  getRouteInfo: carrier.getRouteInfo,
4860
4888
  label: carrier.carrierLabel,
4861
4889
  security
@@ -4912,9 +4940,12 @@ exports.createSecureAcceptorHandler = createSecureAcceptorHandler;
4912
4940
  exports.createServerHandshake = createServerHandshake;
4913
4941
  exports.createStorageTofuVerifyKeyResolver = createStorageTofuVerifyKeyResolver;
4914
4942
  exports.decodeActionFrame = decodeActionFrame;
4943
+ exports.decodeExchangeReply = decodeExchangeReply;
4944
+ exports.decodeExchangeRequest = decodeExchangeRequest;
4915
4945
  exports.decodeHandshakeMessage = decodeHandshakeMessage;
4916
4946
  exports.defineChannel = defineChannel;
4917
4947
  exports.defineSecureChannel = defineSecureChannel;
4948
+ exports.encodeExchange = encodeExchange;
4918
4949
  exports.encodeHandshakeMessage = encodeHandshakeMessage;
4919
4950
  exports.err_nice_action = err_nice_action;
4920
4951
  exports.err_nice_external_client = err_nice_external_client;