@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.
- package/build/devtools/browser/index.js +590 -1246
- package/build/devtools/server/index.js +7 -1
- package/build/index.js +1257 -972
- package/build/types/ActionRuntime/ActionRuntime.d.ts +23 -1
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/Transport.types.d.ts +6 -0
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketConnection.d.ts +2 -1
- package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/secureWsChannel.d.ts +63 -0
- package/build/types/ActionRuntime/Handler/Server/ActionServerHandler.d.ts +64 -1
- package/build/types/ActionRuntime/Handler/Server/WsConnectionStateStore.d.ts +61 -0
- package/build/types/ActionRuntime/Handler/Server/createActionFetchHandler.d.ts +40 -0
- package/build/types/ActionRuntime/Handler/Server/createSecureActionServer.d.ts +71 -0
- package/build/types/devtools/browser/NiceActionDevtools.d.ts +1 -1
- package/build/types/devtools/browser/components/ActionErrorDisplay.d.ts +1 -1
- package/build/types/devtools/browser/components/CallStackSection.d.ts +1 -1
- package/build/types/devtools/browser/components/ChildDispatchChips.d.ts +1 -1
- package/build/types/devtools/browser/components/Chip.d.ts +1 -1
- package/build/types/devtools/browser/components/DetailSection.d.ts +1 -1
- package/build/types/devtools/browser/components/DomainChip.d.ts +1 -1
- package/build/types/devtools/browser/components/HandlerChips.d.ts +1 -1
- package/build/types/devtools/browser/components/Icon.d.ts +1 -1
- package/build/types/devtools/browser/components/MetaSection.d.ts +1 -1
- package/build/types/devtools/browser/components/NiceErrorDisplay.d.ts +2 -2
- package/build/types/devtools/browser/components/OriginChip.d.ts +1 -1
- package/build/types/devtools/browser/components/RoutingSection.d.ts +1 -1
- package/build/types/devtools/browser/components/RunningTimer.d.ts +2 -2
- package/build/types/devtools/browser/components/SectionLabel.d.ts +1 -4
- package/build/types/devtools/browser/components/StackTraceSection.d.ts +1 -1
- package/build/types/devtools/browser/components/Tooltip.d.ts +1 -24
- package/build/types/devtools/browser/components/action_detail/ActionDetailPanel.d.ts +1 -1
- package/build/types/devtools/browser/components/action_list/ActionEntryRow.d.ts +1 -1
- package/build/types/devtools/browser/components/action_list/ActionInputAndOutputChip.d.ts +1 -1
- package/build/types/devtools/browser/components/action_list/ActionList.d.ts +1 -1
- package/build/types/devtools/browser/components/action_list/IoTooltipContent.d.ts +1 -4
- package/build/types/devtools/browser/components/utils.d.ts +1 -3
- package/build/types/devtools/core/ActionDevtools.types.d.ts +1 -1
- package/build/types/devtools/core/ActionDevtoolsCore.d.ts +4 -1
- package/build/types/devtools/core/devtools_colors.d.ts +1 -26
- package/build/types/index.d.ts +5 -1
- package/package.json +33 -29
- package/build/types/devtools/browser/components/PanelChrome.d.ts +0 -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
|
|
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>;
|
package/build/types/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/secureWsChannel.d.ts
ADDED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
8
|
+
}): 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
|
|
21
|
+
export declare function Chip({ color, subtle, size, rounded, tooltip, children, }: IChipProps): import("react").JSX.Element;
|
|
22
22
|
export {};
|
|
@@ -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
|
|
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
|
|
15
|
+
export declare function Icon({ icon: IconComponent, color, size, subtle, tooltip, style, noBackground, }: IIconProps): import("react").JSX.Element;
|
|
16
16
|
export {};
|
|
@@ -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
|
|
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
|
|
19
|
+
}): 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
|
|
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
|
|
4
|
+
}): import("react").JSX.Element;
|
|
5
5
|
export declare function DurationDisplay({ entry }: {
|
|
6
6
|
entry: IDevtoolsActionEntry;
|
|
7
|
-
}): import("react
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -1,24 +1 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3
|
+
}): import("react").JSX.Element;
|