@nice-code/action 0.6.3 → 0.8.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 (41) hide show
  1. package/build/devtools/browser/index.js +590 -1246
  2. package/build/devtools/server/index.js +7 -1
  3. package/build/index.js +1257 -972
  4. package/build/types/ActionRuntime/ActionRuntime.d.ts +23 -1
  5. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Transport.types.d.ts +6 -0
  6. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketConnection.d.ts +2 -1
  7. package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/secureWsChannel.d.ts +63 -0
  8. package/build/types/ActionRuntime/Handler/Server/ActionServerHandler.d.ts +64 -1
  9. package/build/types/ActionRuntime/Handler/Server/WsConnectionStateStore.d.ts +61 -0
  10. package/build/types/ActionRuntime/Handler/Server/createActionFetchHandler.d.ts +40 -0
  11. package/build/types/ActionRuntime/Handler/Server/createSecureActionServer.d.ts +71 -0
  12. package/build/types/devtools/browser/NiceActionDevtools.d.ts +1 -1
  13. package/build/types/devtools/browser/components/ActionErrorDisplay.d.ts +1 -1
  14. package/build/types/devtools/browser/components/CallStackSection.d.ts +1 -1
  15. package/build/types/devtools/browser/components/ChildDispatchChips.d.ts +1 -1
  16. package/build/types/devtools/browser/components/Chip.d.ts +1 -1
  17. package/build/types/devtools/browser/components/DetailSection.d.ts +1 -1
  18. package/build/types/devtools/browser/components/DomainChip.d.ts +1 -1
  19. package/build/types/devtools/browser/components/HandlerChips.d.ts +1 -1
  20. package/build/types/devtools/browser/components/Icon.d.ts +1 -1
  21. package/build/types/devtools/browser/components/MetaSection.d.ts +1 -1
  22. package/build/types/devtools/browser/components/NiceErrorDisplay.d.ts +2 -2
  23. package/build/types/devtools/browser/components/OriginChip.d.ts +1 -1
  24. package/build/types/devtools/browser/components/RoutingSection.d.ts +1 -1
  25. package/build/types/devtools/browser/components/RunningTimer.d.ts +2 -2
  26. package/build/types/devtools/browser/components/SectionLabel.d.ts +1 -4
  27. package/build/types/devtools/browser/components/StackTraceSection.d.ts +1 -1
  28. package/build/types/devtools/browser/components/Tooltip.d.ts +1 -24
  29. package/build/types/devtools/browser/components/action_detail/ActionDetailPanel.d.ts +1 -1
  30. package/build/types/devtools/browser/components/action_list/ActionEntryRow.d.ts +1 -1
  31. package/build/types/devtools/browser/components/action_list/ActionInputAndOutputChip.d.ts +1 -1
  32. package/build/types/devtools/browser/components/action_list/ActionList.d.ts +1 -1
  33. package/build/types/devtools/browser/components/action_list/IoTooltipContent.d.ts +1 -4
  34. package/build/types/devtools/browser/components/utils.d.ts +1 -3
  35. package/build/types/devtools/core/ActionDevtools.types.d.ts +1 -1
  36. package/build/types/devtools/core/ActionDevtoolsCore.d.ts +4 -1
  37. package/build/types/devtools/core/devtools_colors.d.ts +1 -26
  38. package/build/types/index.d.ts +5 -1
  39. package/package.json +33 -29
  40. package/build/types/devtools/browser/components/PanelChrome.d.ts +0 -41
  41. package/build/types/devtools/browser/devtools_dock.d.ts +0 -54
@@ -1,10 +1,13 @@
1
+ import type { ActionCore } from "../ActionDefinition/Action/Core/ActionCore";
1
2
  import type { TActionPayload_Any_Instance } from "../ActionDefinition/Action/Payload/ActionPayload.types";
2
3
  import { type TActionPayload_Any_JsonObject } from "../ActionDefinition/Action/Payload/ActionPayload.types";
3
4
  import { RunningAction } from "../ActionDefinition/Action/RunningAction";
5
+ import type { ActionDomain } from "../ActionDefinition/Domain/ActionDomain";
4
6
  import type { IActionDomain } from "../ActionDefinition/Domain/ActionDomain.types";
5
7
  import type { IRuntimeMeta, TActionRuntimeHandler } from "./ActionRuntime.types";
6
8
  import { type IHandleActionOptions, type TActionHandler } from "./Handler/ActionHandler.types";
7
- import type { ActionExternalClientHandler } from "./Handler/ExternalClient/ActionExternalClientHandler";
9
+ import { ActionExternalClientHandler } from "./Handler/ExternalClient/ActionExternalClientHandler";
10
+ import type { Transport } from "./Handler/ExternalClient/Transport/Transport";
8
11
  import { type IRuntimeCoordinateSpecifics, RuntimeCoordinate } from "./RuntimeCoordinate";
9
12
  export declare class ActionRuntime {
10
13
  private _coordinate;
@@ -38,6 +41,25 @@ export declare class ActionRuntime {
38
41
  * Duplicate registrations (same handler cuid for the same key) are skipped.
39
42
  */
40
43
  addHandlers(handlers: TActionRuntimeHandler[]): this;
44
+ /**
45
+ * Declare an external "backend client" in one call: build an
46
+ * {@link ActionExternalClientHandler} for `externalCoordinate` carrying the given
47
+ * `transports`, route the listed `domains`/`actions` to it, register it (plus any
48
+ * `localHandlers` — e.g. server→client push handlers that share the same channel)
49
+ * on this runtime, and `apply()`. Returns the external handler so the caller can
50
+ * later `clearTransportCache()` it.
51
+ *
52
+ * Sugar over `new ActionExternalClientHandler(...).forDomain(...)` + `addHandlers([...])`,
53
+ * so a single runtime can host one handler per backend target with its transports
54
+ * declared once and reused across every action routed to that backend.
55
+ */
56
+ connectTo(externalCoordinate: RuntimeCoordinate, options: {
57
+ transports: Transport[];
58
+ domains?: ActionDomain<any>[];
59
+ actions?: ActionCore<any, any>[];
60
+ localHandlers?: TActionRuntimeHandler[];
61
+ defaultTimeout?: number;
62
+ }): ActionExternalClientHandler;
41
63
  private applyRuntimeForDomain;
42
64
  /**
43
65
  * Register this runtime with all root domains covered by its currently-added handlers,
@@ -99,6 +99,12 @@ export interface IActionTransportReadyData_Methods extends IActionTransportReady
99
99
  */
100
100
  sendReturnData?: TSendReturnDataMethod;
101
101
  addOnDisconnectListener?: (callback: () => void) => void;
102
+ /**
103
+ * Optional — implement on transports holding a long-lived connection (WebSocket, Custom) to close it
104
+ * deliberately. Called by `ActionExternalClientHandler.clearTransportCache()` so a teardown actually
105
+ * releases the underlying socket instead of leaving it open until GC.
106
+ */
107
+ disconnect?: () => void;
102
108
  }
103
109
  export interface IActionTransportReady {
104
110
  methods: IActionTransportReadyData_Methods;
@@ -3,9 +3,10 @@ import { TransportConnection } from "../TransportConnection";
3
3
  import type { IActionTransportDef_Ws, IActionTransportInitialized_Ws, IActionTransportReadyData_Ws } from "./TransportWebSocket.types";
4
4
  export declare class WebSocketConnection extends TransportConnection<ETransportType.ws, ITransportRouteActionParams, IActionTransportReadyData_Ws, IActionTransportInitialized_Ws, IActionTransportDef_Ws> {
5
5
  private resolvers;
6
- private _abortSet;
7
6
  /** URL of the most recently resolved live socket — surfaced to devtools when the definition can't. */
8
7
  private _liveSocketUrl?;
8
+ /** Sockets we closed on purpose (via `disconnect`), so their `close` event stays quiet. */
9
+ private _intentionalCloses;
9
10
  constructor(def: Omit<IActionTransportDef_Ws, "type">, resolvers?: IActionTransportResolvers);
10
11
  protected _getCacheKey(_input: ITransportRouteActionParams): string;
11
12
  protected _processTransportStatus(input: ITransportRouteActionParams): TTransportStatusInfo<IActionTransportReadyData_Methods>;
@@ -0,0 +1,63 @@
1
+ import { type StorageAdapter } from "@nice-code/util";
2
+ import type { ActionDomain } from "../../../../../ActionDefinition/Domain/ActionDomain";
3
+ import type { ActionRuntime } from "../../../../ActionRuntime";
4
+ import type { ITransportRouteActionParams, ITransportRouteInfo, TUpdateActionRunConfig } from "../Transport.types";
5
+ import { ESecurityLevel } from "./actionWsHandshake";
6
+ import { type IBinaryWsSessionOptions } from "./createBinaryWsSessionFactory";
7
+ import type { IActionTransportReadyData_Ws } from "./TransportWebSocket.types";
8
+ import { WebSocketTransport } from "./WebSocketTransport";
9
+ /** The per-connection binary session codec — built once per socket from the channel's domains. */
10
+ type TChannelCodec = NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
11
+ /**
12
+ * The shared identity of a secure WebSocket channel: the wire dictionary version both ends check
13
+ * during the handshake, plus the per-connection codec factory both ends build from the *same* domain
14
+ * list. Define it once (typically in code shared by client and server) and hand it to
15
+ * {@link createSecureWebSocketTransport} on the client and `createSecureActionServerHandler` on the
16
+ * server, so the codec and version can never drift apart.
17
+ */
18
+ export interface ISecureWsChannel {
19
+ /** Wire dictionary version — derived from the domains by default; the handshake rejects a mismatch. */
20
+ dictionaryVersion: string;
21
+ /** Per-connection session codec factory (call once per live connection). */
22
+ createCodec: () => TChannelCodec;
23
+ }
24
+ /**
25
+ * Bundle a secure channel's shared identity from its transported domains. Both ends MUST call this
26
+ * with the same domains in the same order (the binary wire dictionary is positional). The
27
+ * `dictionaryVersion` is derived from those domains unless you pin an explicit one.
28
+ */
29
+ export declare function defineSecureWsChannel(options: {
30
+ /** Domains transported over this channel, in a stable order. Add new ones to the *end*. */
31
+ domains: ActionDomain<any>[];
32
+ /** Pin a human-readable version instead of the derived hash (must match on both ends). */
33
+ dictionaryVersion?: string;
34
+ /** Tuning for the per-connection binary session (e.g. correlation TTL). */
35
+ sessionOptions?: IBinaryWsSessionOptions;
36
+ }): ISecureWsChannel;
37
+ export interface ISecureWebSocketTransportOptions {
38
+ /** The shared channel identity (codec + dictionary version). */
39
+ channel: ISecureWsChannel;
40
+ /** This client's runtime — its coordinate is the authenticated identity sent in the handshake. */
41
+ runtime: ActionRuntime;
42
+ /** Backing store for this client's crypto identity (a stable verify key across reloads). */
43
+ storageAdapter: StorageAdapter;
44
+ /** The level this client requests; the server must allow it. */
45
+ securityLevel: ESecurityLevel;
46
+ /** Endpoint URL — drives both the socket and the per-endpoint cache key. */
47
+ url: string;
48
+ /** Override socket creation (defaults to a `new WebSocket(url)` with `binaryType = "arraybuffer"`). */
49
+ createWebSocket?: (input: ITransportRouteActionParams) => WebSocket;
50
+ /** Override the reuse key (defaults to `[url]`, so one socket is shared per endpoint). */
51
+ getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
52
+ updateRunConfig?: TUpdateActionRunConfig;
53
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
54
+ }
55
+ /**
56
+ * Build a {@link WebSocketTransport} for the secure binary channel with the boilerplate folded in: it
57
+ * creates the {@link ClientCryptoKeyLink} from `storageAdapter`, opens an `arraybuffer` socket to
58
+ * `url`, caches it per endpoint, installs the channel's per-connection codec, and assembles the
59
+ * `security` block from the runtime coordinate + channel version. Pass `createWebSocket` /
60
+ * `getTransportCacheKey` to take over those bits when you need to.
61
+ */
62
+ export declare function createSecureWebSocketTransport(options: ISecureWebSocketTransportOptions): WebSocketTransport;
63
+ export {};
@@ -1,8 +1,10 @@
1
1
  import type { ClientCryptoKeyLink, TTypeAndId } from "@nice-code/util";
2
+ import type { TDistributeActionPayload_Request } from "../../../ActionDefinition/Action/Action.combined.types";
2
3
  import type { IActionRouteItemHandler } from "../../../ActionDefinition/Action/Payload/ActionPayload.types";
3
4
  import { type TActionPayload_Any_Instance, type TActionPayload_Any_JsonObject } from "../../../ActionDefinition/Action/Payload/ActionPayload.types";
4
5
  import type { ActionPayload_Request } from "../../../ActionDefinition/Action/Payload/ActionPayload_Request";
5
6
  import { RunningAction } from "../../../ActionDefinition/Action/RunningAction";
7
+ import type { ActionDomain } from "../../../ActionDefinition/Domain/ActionDomain";
6
8
  import type { IActionDomain } from "../../../ActionDefinition/Domain/ActionDomain.types";
7
9
  import { ActionRuntime } from "../../ActionRuntime";
8
10
  import { type IRuntimeCoordinate, RuntimeCoordinate } from "../../RuntimeCoordinate";
@@ -10,6 +12,9 @@ import type { IHandleActionOptions } from "../ActionHandler.types";
10
12
  import { ActionExternalClientHandler } from "../ExternalClient/ActionExternalClientHandler";
11
13
  import { ESecurityLevel, type IClientVerifyKeyResolver, type IHandshakeEncryptionKeyMaterial } from "../ExternalClient/Transport/WebSocket/actionWsHandshake";
12
14
  import type { IActionTransportReadyData_Ws } from "../ExternalClient/Transport/WebSocket/TransportWebSocket.types";
15
+ import { ActionLocalHandler } from "../Local/ActionLocalHandler";
16
+ import type { THandleActionExecutionFn } from "../Local/ActionLocalHandler.types";
17
+ import { type IWsConnectionStateStoreOptions, WsConnectionStateStore } from "./WsConnectionStateStore";
13
18
  /** The codec shape `ActionServerHandler` uses to pack/unpack frames — same as the WS transport's. */
14
19
  export type TActionChannelFormatMessage = NonNullable<IActionTransportReadyData_Ws["formatMessage"]>;
15
20
  /** How a connection encodes its frames, remembered so we answer each client in its own dialect. */
@@ -62,6 +67,11 @@ interface IActionServerHandlerBaseOptions<TConn> {
62
67
  clientEnv: RuntimeCoordinate;
63
68
  /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
64
69
  send: (connection: TConn, frame: string | Uint8Array | ArrayBuffer) => void;
70
+ /**
71
+ * The runtime this handler belongs to. When set, {@link ActionServerHandler.broadcast} can be called
72
+ * without threading a runtime through each call. Optional — `pushToClient` still takes one explicitly.
73
+ */
74
+ runtime?: ActionRuntime;
65
75
  /** Timeout (ms) applied to server-initiated actions awaiting a client response. */
66
76
  defaultTimeout?: number;
67
77
  /**
@@ -91,6 +101,13 @@ export type IActionServerHandlerOptions<TConn> = IActionServerHandlerBaseOptions
91
101
  createFormatMessage: () => TActionChannelFormatMessage;
92
102
  formatMessage?: never;
93
103
  });
104
+ /**
105
+ * A connection-aware execution case (see {@link ActionServerHandler.forConnectionDomainCases}). It
106
+ * receives the primed request plus the originating client's live connection (already resolved from the
107
+ * request's `originClient`, `undefined` if the socket is gone), and may return the action's raw output,
108
+ * a result payload, or nothing (auto-wrapped as an empty success) — exactly like a local handler case.
109
+ */
110
+ export 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;
94
111
  /**
95
112
  * Server-side handler for backends that accept many client connections over a single open channel
96
113
  * (WebSockets, Durable Objects, …). It is transport-agnostic: you feed it inbound frames with
@@ -116,8 +133,9 @@ export declare class ActionServerHandler<TConn = unknown> extends ActionExternal
116
133
  private readonly _formatMessage?;
117
134
  private readonly _createFormatMessage?;
118
135
  private readonly _send;
136
+ private readonly _runtime?;
119
137
  private readonly _serverTimeout;
120
- private readonly _onConnectionBound?;
138
+ private _onConnectionBound?;
121
139
  /** Incoming-data listeners installed by the runtime (`resolveIncomingActionPayload`). */
122
140
  private readonly _incomingListeners;
123
141
  private readonly _security?;
@@ -142,6 +160,24 @@ export declare class ActionServerHandler<TConn = unknown> extends ActionExternal
142
160
  */
143
161
  private _codecFor;
144
162
  _setIncomingActionDataListener(listener: (json: TActionPayload_Any_JsonObject<any>) => void): void;
163
+ /**
164
+ * Register (or replace) the connection-bound persistence callback after construction. Used by
165
+ * lifecycle helpers like {@link createHibernatableWsServerAdapter} so persistence and replay are
166
+ * owned by one place instead of being split across the constructor options.
167
+ */
168
+ setOnConnectionBound(onConnectionBound: (connection: TConn, binding: IActionServerConnectionBinding) => void): void;
169
+ /**
170
+ * Create a typed per-connection state store that co-owns the consumer's app state and this handler's
171
+ * routing binding in one attachment. It registers itself as the connection-bound persistence callback
172
+ * (so bindings are written without overwriting app state) and immediately replays every live
173
+ * connection's stored binding via {@link rehydrateConnection} — so on a transport that resumes after
174
+ * eviction (e.g. a Durable Object waking from hibernation) both the app identity and the action
175
+ * routing come back from a single attachment, with no storage reads and no hand-rolled merge.
176
+ *
177
+ * This supersedes {@link createHibernatableWsServerAdapter} for app code that also pins its own state
178
+ * to the connection. Construct it once when the handler is built, then `get`/`set` app state directly.
179
+ */
180
+ createConnectionState<TApp>(options: IWsConnectionStateStoreOptions<TConn, TApp>): WsConnectionStateStore<TConn, TApp>;
145
181
  /**
146
182
  * Feed one inbound frame from a connection into the runtime. Decodes text or binary, binds the
147
183
  * connection to the requesting client's identity, then routes it (requests execute locally;
@@ -177,6 +213,33 @@ export declare class ActionServerHandler<TConn = unknown> extends ActionExternal
177
213
  pushToClient<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(runtime: ActionRuntime, target: TConn | RuntimeCoordinate, request: ActionPayload_Request<DOM, ID>, options?: {
178
214
  timeout?: number;
179
215
  }): RunningAction<DOM, ID>;
216
+ /**
217
+ * Build a local handler whose cases are connection-aware: each case receives the primed request and
218
+ * the originating client's live connection (resolved from `originClient`), so handlers don't repeat
219
+ * the `getConnectionForClient(action.context.originClient)` lookup. Cases may return raw output or
220
+ * nothing, just like {@link ActionLocalHandler.forDomainActionCases}. Add the returned handler to the
221
+ * runtime alongside this server handler:
222
+ * ```ts
223
+ * runtime.addHandlers([serverHandler.forConnectionDomainCases(domain, { … }), serverHandler]);
224
+ * ```
225
+ */
226
+ forConnectionDomainCases<FOR_DOM extends IActionDomain>(domain: ActionDomain<FOR_DOM>, cases: {
227
+ [ID in keyof FOR_DOM["actionSchema"] & string]?: TServerConnectionCaseFn<FOR_DOM, ID, TConn>;
228
+ }): ActionLocalHandler;
229
+ /**
230
+ * Fan a server-initiated request out to every currently-bound connection. A fresh request is built
231
+ * per connection (each push mutates its own action context) and dispatched fire-and-forget. Pass
232
+ * `except` to skip the originating socket and `where` to filter by connection (e.g. read its
233
+ * attachment for a role). Iterating bound connections (rather than every accepted socket) skips
234
+ * sockets that are still mid-handshake and so can't yet receive a frame.
235
+ */
236
+ broadcast<DOM extends IActionDomain, ID extends keyof DOM["actionSchema"] & string>(makeRequest: () => ActionPayload_Request<DOM, ID>, options?: {
237
+ runtime?: ActionRuntime;
238
+ except?: TConn | null;
239
+ where?: (connection: TConn) => boolean;
240
+ timeout?: number;
241
+ onError?: (error: unknown, connection: TConn) => void;
242
+ }): void;
180
243
  sendReturnPayload(payload: TActionPayload_Any_Instance<any, any>, config: {
181
244
  targetLocalRuntime: ActionRuntime;
182
245
  }): Promise<boolean>;
@@ -0,0 +1,61 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import type { IActionServerConnectionBinding } from "./ActionServerHandler";
3
+ /**
4
+ * The composite value persisted to a connection's attachment: the consumer's own app state plus the
5
+ * {@link ActionServerHandler} routing binding. Co-storing them in one slot means a transport whose
6
+ * sockets outlive process eviction (e.g. a Durable Object's hibernatable WebSocket) recovers both the
7
+ * application identity *and* the action routing from a single attachment after a wake — no storage reads.
8
+ */
9
+ export interface IConnectionAttachment<TApp> {
10
+ app?: TApp;
11
+ binding?: IActionServerConnectionBinding;
12
+ }
13
+ export interface IWsConnectionStateStoreOptions<TConn, TApp> {
14
+ /** Read a connection's raw attachment (e.g. `(ws) => ws.deserializeAttachment()`). */
15
+ read: (connection: TConn) => unknown;
16
+ /** Persist a connection's attachment (e.g. `(ws, value) => ws.serializeAttachment(value)`). */
17
+ write: (connection: TConn, value: IConnectionAttachment<TApp>) => void;
18
+ /**
19
+ * All currently-live connections (e.g. `() => ctx.getWebSockets()`). Used to replay routing bindings
20
+ * after a wake (via {@link ActionServerHandler.createConnectionState}) and to enumerate app state in
21
+ * {@link WsConnectionStateStore.entries}.
22
+ */
23
+ getConnections: () => TConn[];
24
+ /**
25
+ * Optional Standard Schema (valibot, zod, …) validating the *app* portion on read. A value that
26
+ * fails validation reads back as `null` — the same lenient behavior as a hand-written safeParse
27
+ * helper. The binding is the library's own shape and is never validated.
28
+ */
29
+ schema?: StandardSchemaV1<unknown, TApp>;
30
+ }
31
+ /**
32
+ * A typed per-connection state store that co-owns the app state and the server handler's routing
33
+ * binding in one attachment, so neither the consumer nor the handler has to hand-merge the two. Create
34
+ * it through {@link ActionServerHandler.createConnectionState} (which also wires binding persistence and
35
+ * replays surviving connections after a wake), then `get`/`set`/`clearApp` the app state directly.
36
+ *
37
+ * ```ts
38
+ * const players = serverHandler.createConnectionState({
39
+ * schema: vs_player,
40
+ * read: (ws) => ws.deserializeAttachment(),
41
+ * write: (ws, v) => ws.serializeAttachment(v),
42
+ * getConnections: () => ctx.getWebSockets(),
43
+ * });
44
+ * players.set(ws, player); // binding is preserved automatically
45
+ * const player = players.get(ws);
46
+ * ```
47
+ */
48
+ export declare class WsConnectionStateStore<TConn, TApp> {
49
+ private readonly options;
50
+ constructor(options: IWsConnectionStateStoreOptions<TConn, TApp>);
51
+ /** The validated app state for a connection, or `null` if unset / invalid. */
52
+ get(connection: TConn): TApp | null;
53
+ /** Set the app state, preserving the runtime binding already pinned to the connection. */
54
+ set(connection: TConn, app: TApp): void;
55
+ /** Clear the app state but keep the binding (e.g. a spectator that stopped watching). */
56
+ clearApp(connection: TConn): void;
57
+ /** Every live connection paired with its (validated) app state — for rebuilding in-memory state after a wake. */
58
+ entries(): [TConn, TApp | null][];
59
+ private _readAttachment;
60
+ private _validateApp;
61
+ }
@@ -0,0 +1,40 @@
1
+ import type { ActionRuntime } from "../../ActionRuntime";
2
+ export interface IActionFetchHandlerOptions {
3
+ /**
4
+ * CORS headers merged onto every response (a preflight `OPTIONS` is answered `204` with them).
5
+ * Defaults to permissive `*`; pass `false` to attach no CORS headers at all.
6
+ */
7
+ cors?: Record<string, string> | false;
8
+ /** Which requests carry an action wire on `POST`. Default: pathname ends with `/action`. */
9
+ isActionPath?: (url: URL) => boolean;
10
+ /** Which requests are WebSocket upgrades. Default: pathname ends with `/ws`. */
11
+ isWebSocketPath?: (url: URL) => boolean;
12
+ /**
13
+ * Perform the transport-specific WebSocket upgrade (e.g. a Durable Object's
14
+ * `new WebSocketPair()` + `ctx.acceptWebSocket()` returning a `101`). Omit for HTTP-only endpoints.
15
+ * Its response is returned as-is — a `101` upgrade carries no CORS headers.
16
+ */
17
+ onWebSocketUpgrade?: (request: Request, url: URL) => Response | Promise<Response>;
18
+ /** Forwarded to `ActionPayload_Result.toHttpResponse` — use the error's HTTP status (default true). */
19
+ useErrorStatus?: boolean;
20
+ }
21
+ /**
22
+ * Build the `fetch` handler a server/Durable-Object exposes for action traffic, folding in the
23
+ * boilerplate every endpoint repeats: CORS (incl. the `OPTIONS` preflight), routing the `/action`
24
+ * `POST` body through the runtime (`handleActionPayloadWire` → `waitForResultPayload` →
25
+ * `toHttpResponse`), an optional WebSocket-upgrade hook, and a `404` fallback.
26
+ *
27
+ * It only touches web-standard `Request`/`Response`, so it stays transport-agnostic — the one
28
+ * environment-specific bit (the WS upgrade) is injected via {@link IActionFetchHandlerOptions.onWebSocketUpgrade}:
29
+ * ```ts
30
+ * this.fetchHandler = createActionFetchHandler(this.runtime, {
31
+ * onWebSocketUpgrade: () => {
32
+ * const pair = new WebSocketPair();
33
+ * this.ctx.acceptWebSocket(pair[1]);
34
+ * return new Response(null, { status: 101, webSocket: pair[0] });
35
+ * },
36
+ * });
37
+ * // async fetch(request) { return this.fetchHandler(request); }
38
+ * ```
39
+ */
40
+ export declare function createActionFetchHandler(runtime: ActionRuntime, options?: IActionFetchHandlerOptions): (request: Request) => Promise<Response>;
@@ -0,0 +1,71 @@
1
+ import { type StorageAdapter } from "@nice-code/util";
2
+ import type { ActionRuntime } from "../../ActionRuntime";
3
+ import type { RuntimeCoordinate } from "../../RuntimeCoordinate";
4
+ import { ESecurityLevel, type IClientVerifyKeyResolver } from "../ExternalClient/Transport/WebSocket/actionWsHandshake";
5
+ import type { ISecureWsChannel } from "../ExternalClient/Transport/WebSocket/secureWsChannel";
6
+ import { ActionServerHandler, type IActionServerConnectionBinding } from "./ActionServerHandler";
7
+ export interface ISecureActionServerHandlerOptions<TConn> {
8
+ /** The shared channel identity (codec + dictionary version) — same one the clients use. */
9
+ channel: ISecureWsChannel;
10
+ /**
11
+ * Coordinate of the *connecting clients* (typically env-only, e.g. `RuntimeCoordinate.env("web_app")`),
12
+ * used to route results/pushes back over this handler.
13
+ */
14
+ clientEnv: RuntimeCoordinate;
15
+ /** This server's runtime — its coordinate is the server identity presented in the handshake. */
16
+ runtime: ActionRuntime;
17
+ /**
18
+ * One backing store for the server's crypto identity *and* its trust-on-first-use verify-key pins.
19
+ * Their keys don't collide, so a single adapter is enough; back it with persistent storage (e.g. a
20
+ * Durable Object's storage) so identity and pins survive eviction.
21
+ */
22
+ storageAdapter: StorageAdapter;
23
+ /** Write an encoded frame to a specific live connection (e.g. `(ws, frame) => ws.send(frame)`). */
24
+ send: (connection: TConn, frame: string | Uint8Array | ArrayBuffer) => void;
25
+ /** Accepted level(s); defaults to negotiating any of none/authenticated/encrypted. */
26
+ securityLevel?: ESecurityLevel | readonly ESecurityLevel[];
27
+ /** Trust decision for a client's verify key; defaults to storage-backed TOFU over `storageAdapter`. */
28
+ verifyKeyResolver?: IClientVerifyKeyResolver;
29
+ /** Timeout (ms) applied to server-initiated actions awaiting a client response. */
30
+ defaultTimeout?: number;
31
+ }
32
+ /**
33
+ * Build an {@link ActionServerHandler} for the secure binary channel with the boilerplate folded in:
34
+ * it creates the {@link ClientCryptoKeyLink} and the storage-backed TOFU resolver from a single
35
+ * `storageAdapter`, installs the channel's per-connection codec, and assembles the `security` block
36
+ * from the runtime coordinate + channel version (accepting all three levels by default).
37
+ *
38
+ * For a hibernatable transport (e.g. a Durable Object), pair it with
39
+ * {@link createHibernatableWsServerAdapter} to wire persistence + replay.
40
+ */
41
+ export declare function createSecureActionServerHandler<TConn = unknown>(options: ISecureActionServerHandlerOptions<TConn>): ActionServerHandler<TConn>;
42
+ export interface IHibernatableWsServerAdapterOptions<TConn> {
43
+ /** The handler to drive (from {@link createSecureActionServerHandler} or `createServerHandler`). */
44
+ handler: ActionServerHandler<TConn>;
45
+ /** All currently-live connections — replayed on construction to rebuild bindings after a wake. */
46
+ getWebSockets: () => TConn[];
47
+ /** Read a connection's persisted binding (e.g. `(ws) => ws.deserializeAttachment()`). */
48
+ getAttachment: (connection: TConn) => IActionServerConnectionBinding | undefined;
49
+ /** Persist a connection's binding when it is bound (e.g. `(ws, b) => ws.serializeAttachment(b)`). */
50
+ setAttachment: (connection: TConn, binding: IActionServerConnectionBinding) => void;
51
+ }
52
+ export interface IHibernatableWsServerAdapter<TConn> {
53
+ /** Feed one inbound frame from a connection into the handler. */
54
+ receive: (connection: TConn, frame: string | ArrayBuffer | Uint8Array) => void;
55
+ /** Forget a connection (call on socket close/error). */
56
+ drop: (connection: TConn) => void;
57
+ }
58
+ /**
59
+ * Wire the hibernation lifecycle for a server handler on a transport whose sockets outlive process
60
+ * eviction (e.g. a Durable Object's hibernatable WebSockets). It owns persistence end to end:
61
+ * registers `setAttachment` as the handler's connection-bound callback and immediately replays every
62
+ * live connection's stored binding via `getAttachment`, so results/pushes still route after a wake.
63
+ *
64
+ * Construct it once when the handler is built, then forward socket events:
65
+ * ```ts
66
+ * const wsServer = createHibernatableWsServerAdapter({ handler, getWebSockets, getAttachment, setAttachment });
67
+ * // webSocketMessage(ws, msg) => wsServer.receive(ws, msg);
68
+ * // webSocketClose/Error(ws) => wsServer.drop(ws);
69
+ * ```
70
+ */
71
+ export declare function createHibernatableWsServerAdapter<TConn>(options: IHibernatableWsServerAdapterOptions<TConn>): IHibernatableWsServerAdapter<TConn>;
@@ -11,4 +11,4 @@ export interface IEntryGroup {
11
11
  representative: IDevtoolsActionEntry;
12
12
  rest: IDevtoolsActionEntry[];
13
13
  }
14
- export declare function NiceActionDevtools({ forceEnable, ...props }: INiceActionDevtoolsProps): import("react/jsx-runtime").JSX.Element | null;
14
+ export declare function NiceActionDevtools({ forceEnable, ...props }: INiceActionDevtoolsProps): import("react").JSX.Element | null;
@@ -2,4 +2,4 @@ import type { IDevtoolsActionEntry } from "../../core/ActionDevtools.types";
2
2
  export declare function ActionErrorDisplay({ entry, compact, }: {
3
3
  entry: IDevtoolsActionEntry;
4
4
  compact?: boolean;
5
- }): import("react/jsx-runtime").JSX.Element | null;
5
+ }): import("react").JSX.Element | null;
@@ -5,4 +5,4 @@ export declare function CallStackSection({ parent, childEntries, focusedChildCui
5
5
  focusedChildCuid: string | null;
6
6
  onFocusChild: (cuid: string) => void;
7
7
  onSelectParent: (cuid: string) => void;
8
- }): import("react/jsx-runtime").JSX.Element | null;
8
+ }): import("react").JSX.Element | null;
@@ -3,4 +3,4 @@ import { ESize } from "../ui_util/size";
3
3
  export declare function ChildDispatchChips({ childRouteItems, size, }: {
4
4
  childRouteItems?: IDevtoolsRouteItem[];
5
5
  size?: ESize;
6
- }): import("react/jsx-runtime").JSX.Element | null;
6
+ }): import("react").JSX.Element | null;
@@ -18,5 +18,5 @@ interface IChipProps {
18
18
  tooltip?: ITooltipConfig;
19
19
  children: ReactNode;
20
20
  }
21
- export declare function Chip({ color, subtle, size, rounded, tooltip, children, }: IChipProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function Chip({ color, subtle, size, rounded, tooltip, children, }: IChipProps): import("react").JSX.Element;
22
22
  export {};
@@ -2,4 +2,4 @@ export declare function DetailSection({ label, value, color, }: {
2
2
  label: string;
3
3
  value: unknown;
4
4
  color?: string;
5
- }): import("react/jsx-runtime").JSX.Element;
5
+ }): import("react").JSX.Element;
@@ -5,4 +5,4 @@ export declare function DomainChip({ compact, subtle, domain, allDomains, size,
5
5
  domain: string;
6
6
  allDomains: string[];
7
7
  size?: ESize;
8
- }): import("react/jsx-runtime").JSX.Element;
8
+ }): import("react").JSX.Element;
@@ -17,5 +17,5 @@ interface IHandlerChipsProps {
17
17
  size: ESize;
18
18
  subtle?: boolean;
19
19
  }
20
- export declare function HandlerChips({ entry, size, subtle }: IHandlerChipsProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function HandlerChips({ entry, size, subtle }: IHandlerChipsProps): import("react").JSX.Element;
21
21
  export {};
@@ -12,5 +12,5 @@ interface IIconProps {
12
12
  style?: CSSProperties;
13
13
  noBackground?: boolean;
14
14
  }
15
- export declare function Icon({ icon: IconComponent, color, size, subtle, tooltip, style, noBackground, }: IIconProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function Icon({ icon: IconComponent, color, size, subtle, tooltip, style, noBackground, }: IIconProps): import("react").JSX.Element;
16
16
  export {};
@@ -1,4 +1,4 @@
1
1
  import type { IDevtoolsActionEntry } from "../../core/ActionDevtools.types";
2
2
  export declare function MetaSection({ entry }: {
3
3
  entry: IDevtoolsActionEntry;
4
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): import("react").JSX.Element;
@@ -11,9 +11,9 @@ export type TNiceErrorJson = {
11
11
  export declare function isNiceErrorJson(value: unknown): value is TNiceErrorJson;
12
12
  export declare function NiceErrorBody({ error }: {
13
13
  error: TNiceErrorJson;
14
- }): import("react/jsx-runtime").JSX.Element;
14
+ }): import("react").JSX.Element;
15
15
  export declare function NiceErrorDisplay({ error, label, color, }: {
16
16
  error: TNiceErrorJson;
17
17
  label: string;
18
18
  color: string;
19
- }): import("react/jsx-runtime").JSX.Element;
19
+ }): import("react").JSX.Element;
@@ -12,4 +12,4 @@ export declare function OriginChip({ origin, size, subtle, }: {
12
12
  };
13
13
  size?: ESize;
14
14
  subtle?: boolean;
15
- }): import("react/jsx-runtime").JSX.Element;
15
+ }): import("react").JSX.Element;
@@ -2,4 +2,4 @@ import type { IDevtoolsActionEntry } from "../../core/ActionDevtools.types";
2
2
  export declare function RoutingSection({ entry, minHopCount, }: {
3
3
  entry: IDevtoolsActionEntry;
4
4
  minHopCount?: number;
5
- }): import("react/jsx-runtime").JSX.Element | null;
5
+ }): import("react").JSX.Element | null;
@@ -1,7 +1,7 @@
1
1
  import type { IDevtoolsActionEntry } from "../../core/ActionDevtools.types";
2
2
  export declare function RunningTimer({ startTime }: {
3
3
  startTime: number;
4
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): import("react").JSX.Element;
5
5
  export declare function DurationDisplay({ entry }: {
6
6
  entry: IDevtoolsActionEntry;
7
- }): import("react/jsx-runtime").JSX.Element;
7
+ }): import("react").JSX.Element;
@@ -1,4 +1 @@
1
- export declare function SectionLabel({ label, color, }: {
2
- label: string;
3
- color?: string;
4
- }): import("react/jsx-runtime").JSX.Element;
1
+ export { SectionLabel } from "nice-devtools-shared";
@@ -6,4 +6,4 @@ export declare function StackTraceSection({ label, stack, color, runtime, minFra
6
6
  runtime: IRuntimeCoordinate;
7
7
  color?: string;
8
8
  minFrameCount?: number;
9
- }): import("react/jsx-runtime").JSX.Element | null;
9
+ }): import("react").JSX.Element | null;
@@ -1,24 +1 @@
1
- import { type CSSProperties, type ReactNode } from "react";
2
- export interface ITooltipConfig {
3
- content: ReactNode;
4
- title?: ReactNode;
5
- /** "center" centers the tooltip above/below the anchor (default).
6
- * "edge" aligns the nearer horizontal edge with the anchor edge. */
7
- align?: "center" | "edge";
8
- maxWidth?: number;
9
- }
10
- export declare function Tooltip({ anchor, config, children, }: {
11
- anchor: DOMRect;
12
- config: ITooltipConfig;
13
- children?: ReactNode;
14
- }): import("react/jsx-runtime").JSX.Element;
15
- /**
16
- * Wraps inline content so it shows `config` as a hover tooltip. When `config` is null the children
17
- * render untouched (no hover handlers, no tooltip). Useful for non-Chip text labels that still need
18
- * the same tooltip treatment as Chips.
19
- */
20
- export declare function HoverTooltip({ config, children, style, }: {
21
- config?: ITooltipConfig;
22
- children: ReactNode;
23
- style?: CSSProperties;
24
- }): import("react/jsx-runtime").JSX.Element;
1
+ export { HoverTooltip, type ITooltipConfig, Tooltip } from "nice-devtools-shared";
@@ -4,4 +4,4 @@ export declare function ActionDetailPanel({ entry, parent, childEntries, onSelec
4
4
  parent: IDevtoolsActionEntry | null;
5
5
  childEntries: IDevtoolsActionEntry[];
6
6
  onSelectEntry: (cuid: string) => void;
7
- }): import("react/jsx-runtime").JSX.Element;
7
+ }): import("react").JSX.Element;
@@ -11,4 +11,4 @@ export declare function ActionEntryRow({ entry, isSelected, onClick, isLatest, l
11
11
  groupEntries?: IDevtoolsActionEntry[];
12
12
  selectedGroupCuid?: string | null;
13
13
  onSelectGroupEntry?: (cuid: string) => void;
14
- }): import("react/jsx-runtime").JSX.Element;
14
+ }): import("react").JSX.Element;
@@ -7,5 +7,5 @@ interface IActionInputAndOutputChipProps {
7
7
  subtle?: boolean;
8
8
  size?: ESize;
9
9
  }
10
- export declare const ActionInputAndOutputChip: ({ entry, breakReasons, subtle, size, }: IActionInputAndOutputChipProps) => import("react/jsx-runtime").JSX.Element;
10
+ export declare const ActionInputAndOutputChip: ({ entry, breakReasons, subtle, size, }: IActionInputAndOutputChipProps) => import("react").JSX.Element;
11
11
  export {};
@@ -7,4 +7,4 @@ export declare function ActionList({ groups, selectedCuid, onGroupClick, onSubCl
7
7
  onSubClick: (cuid: string, isSelected: boolean) => void;
8
8
  childEntriesMap: Map<string, IDevtoolsActionEntry[]>;
9
9
  style?: React.CSSProperties;
10
- }): import("react/jsx-runtime").JSX.Element;
10
+ }): import("react").JSX.Element;
@@ -1,6 +1,3 @@
1
- export declare const _IoTooltipContent: ({ value }: {
2
- value: unknown;
3
- }) => import("react/jsx-runtime").JSX.Element;
4
1
  export declare function IoTooltipContent({ value }: {
5
2
  value: unknown;
6
- }): import("react/jsx-runtime").JSX.Element;
3
+ }): import("react").JSX.Element;