@nice-code/action 0.4.6 → 0.4.8

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/README.md CHANGED
@@ -154,7 +154,9 @@ export const clientCoord = RuntimeCoordinate.env("frontend");
154
154
  export const serverCoord = RuntimeCoordinate.env("backend");
155
155
 
156
156
  // Transport definitions are plain, reusable objects you construct once.
157
- const serverHttp = new HttpTransport({ url: "https://api.example.com/resolve_action" });
157
+ const serverHttp = HttpTransport.create({
158
+ createRequest: () => ({ url: "https://api.example.com/resolve_action" }),
159
+ });
158
160
 
159
161
  const serverHandler = new ActionExternalClientHandler({
160
162
  runtimeCoordinate: serverCoord,
@@ -218,7 +220,10 @@ function RenameUser() {
218
220
  ```ts
219
221
  import { WebSocketTransport } from "@nice-code/action";
220
222
 
221
- const serverWs = new WebSocketTransport({ url: "wss://api.example.com/resolve_action/ws" });
223
+ const serverWs = WebSocketTransport.create({
224
+ createWebSocket: () => new WebSocket("wss://api.example.com/resolve_action/ws"),
225
+ getTransportCacheKey: () => ["wss://api.example.com/resolve_action/ws"],
226
+ });
222
227
 
223
228
  const serverHandler = new ActionExternalClientHandler({
224
229
  runtimeCoordinate: serverCoord,
@@ -226,12 +231,15 @@ const serverHandler = new ActionExternalClientHandler({
226
231
  }).forDomain(userDomain);
227
232
  ```
228
233
 
229
- The socket is opened lazily and reused across actions to the same endpoint. Multiple transports can
230
- be registered; the runtime picks the best available one (WebSocket preferred for lower latency, HTTP
231
- as fallback). For channels nice-action doesn't model natively, use `CustomTransport`.
234
+ The socket is opened lazily and reused across actions sharing a `getTransportCacheKey`. Multiple
235
+ transports can be registered; the runtime picks the best available one (WebSocket preferred for lower
236
+ latency, HTTP as fallback). For channels nice-action doesn't model natively, use `CustomTransport`.
232
237
 
233
- For advanced cases, `HttpTransport` / `WebSocketTransport` accept functions to derive the URL or
234
- headers per action (e.g. `new HttpTransport({ url: (input) => \`/api/${input.action.id}\` })`).
238
+ Each transport takes a single creation function so you decide how simple or complex it should be —
239
+ derive the request per action straight from the params (e.g.
240
+ `HttpTransport.create({ createRequest: (input) => ({ url: \`/api/${input.action.id}\` }) })`). For full
241
+ control over readiness (support detection, async init), `WebSocketTransport` / `CustomTransport` also
242
+ offer `.createAdvanced({ getTransport })`.
235
243
 
236
244
  ---
237
245
 
package/build/index.js CHANGED
@@ -2124,20 +2124,32 @@ class CustomTransport extends Transport {
2124
2124
  super();
2125
2125
  this.options = options;
2126
2126
  }
2127
+ static create(options) {
2128
+ return new CustomTransport({ ...options, mode: "send" });
2129
+ }
2130
+ static createAdvanced(options) {
2131
+ return new CustomTransport({ ...options, mode: "advanced" });
2132
+ }
2127
2133
  _createConnection(_ctx) {
2128
2134
  const options = this.options;
2135
+ let getTransport;
2136
+ if (options.mode === "advanced") {
2137
+ getTransport = options.getTransport;
2138
+ } else {
2139
+ getTransport = () => ({
2140
+ status: "ready" /* ready */,
2141
+ readyData: {
2142
+ sendActionData: options.sendActionData,
2143
+ sendReturnData: options.sendReturnData,
2144
+ updateRunConfig: options.updateRunConfig,
2145
+ closeTransport: options.closeTransport ?? (() => {})
2146
+ }
2147
+ });
2148
+ }
2129
2149
  return new CustomConnection({
2130
2150
  initialize: () => ({
2131
2151
  getTransportCacheKey: options.getTransportCacheKey,
2132
- getTransport: options.getTransport ?? (() => ({
2133
- status: "ready" /* ready */,
2134
- readyData: {
2135
- sendActionData: options.sendActionData,
2136
- sendReturnData: options.sendReturnData,
2137
- updateRunConfig: options.updateRunConfig,
2138
- closeTransport: options.closeTransport ?? (() => {})
2139
- }
2140
- }))
2152
+ getTransport
2141
2153
  })
2142
2154
  });
2143
2155
  }
@@ -2285,9 +2297,6 @@ class HttpConnection extends TransportConnection {
2285
2297
  }
2286
2298
 
2287
2299
  // src/ActionRuntime/Handler/ExternalClient/Transport/Http/HttpTransport.ts
2288
- function resolveMaybe(value, input) {
2289
- return typeof value === "function" ? value(input) : value;
2290
- }
2291
2300
  function shortPath(url) {
2292
2301
  try {
2293
2302
  return new URL(url).pathname || url;
@@ -2303,13 +2312,8 @@ class HttpTransport extends Transport {
2303
2312
  super();
2304
2313
  this.options = options;
2305
2314
  }
2306
- _resolveRequest(input) {
2307
- if (this.options.createRequest != null)
2308
- return this.options.createRequest(input);
2309
- return {
2310
- url: resolveMaybe(this.options.url, input),
2311
- headers: this.options.headers != null ? resolveMaybe(this.options.headers, input) : undefined
2312
- };
2315
+ static create(options) {
2316
+ return new HttpTransport(options);
2313
2317
  }
2314
2318
  _createConnection(_ctx) {
2315
2319
  return new HttpConnection({
@@ -2318,7 +2322,7 @@ class HttpTransport extends Transport {
2318
2322
  getTransport: () => ({
2319
2323
  status: "ready" /* ready */,
2320
2324
  readyData: {
2321
- createRequest: (input) => this._resolveRequest(input),
2325
+ createRequest: this.options.createRequest,
2322
2326
  updateRunConfig: this.options.updateRunConfig
2323
2327
  }
2324
2328
  })
@@ -2326,7 +2330,7 @@ class HttpTransport extends Transport {
2326
2330
  });
2327
2331
  }
2328
2332
  getRouteInfo(input) {
2329
- const { url } = this._resolveRequest(input);
2333
+ const { url } = this.options.createRequest(input);
2330
2334
  return {
2331
2335
  type: "http" /* http */,
2332
2336
  method: "POST",
@@ -2502,10 +2506,6 @@ class WebSocketConnection extends TransportConnection {
2502
2506
  }
2503
2507
 
2504
2508
  // src/ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketTransport.ts
2505
- function resolveMaybe2(value, input) {
2506
- return typeof value === "function" ? value(input) : value;
2507
- }
2508
-
2509
2509
  class WebSocketTransport extends Transport {
2510
2510
  options;
2511
2511
  type = "ws" /* ws */;
@@ -2513,38 +2513,40 @@ class WebSocketTransport extends Transport {
2513
2513
  super();
2514
2514
  this.options = options;
2515
2515
  }
2516
- _createSocket(input) {
2517
- if (this.options.createWebSocket != null)
2518
- return this.options.createWebSocket(input);
2519
- if (this.options.url == null) {
2520
- throw new Error("WebSocketTransport requires `url` or `createWebSocket` when not using `getTransport`.");
2521
- }
2522
- return new WebSocket(resolveMaybe2(this.options.url, input));
2516
+ static create(options) {
2517
+ return new WebSocketTransport({ ...options, mode: "socket" });
2518
+ }
2519
+ static createAdvanced(options) {
2520
+ return new WebSocketTransport({ ...options, mode: "advanced" });
2523
2521
  }
2524
2522
  _createConnection(ctx) {
2525
- const { url, getTransportCacheKey, getTransport } = this.options;
2523
+ const options = this.options;
2524
+ let getTransport;
2525
+ if (options.mode === "advanced") {
2526
+ getTransport = options.getTransport;
2527
+ } else {
2528
+ getTransport = (input) => ({
2529
+ status: "ready" /* ready */,
2530
+ readyData: {
2531
+ ws: options.createWebSocket(input),
2532
+ formatMessage: options.formatMessage,
2533
+ updateRunConfig: options.updateRunConfig
2534
+ }
2535
+ });
2536
+ }
2526
2537
  return new WebSocketConnection({
2527
2538
  initialize: () => ({
2528
- getTransportCacheKey: getTransportCacheKey ?? (url != null ? (input) => [resolveMaybe2(url, input)] : undefined),
2529
- getTransport: getTransport ?? ((input) => ({
2530
- status: "ready" /* ready */,
2531
- readyData: {
2532
- ws: this._createSocket(input),
2533
- formatMessage: this.options.formatMessage,
2534
- updateRunConfig: this.options.updateRunConfig
2535
- }
2536
- }))
2539
+ getTransportCacheKey: options.getTransportCacheKey,
2540
+ getTransport
2537
2541
  })
2538
2542
  }, ctx.resolvers);
2539
2543
  }
2540
2544
  getRouteInfo(input) {
2541
2545
  if (this.options.getRouteInfo != null)
2542
2546
  return this.options.getRouteInfo(input);
2543
- const url = this.options.url != null ? resolveMaybe2(this.options.url, input) : undefined;
2544
2547
  return {
2545
2548
  type: "ws" /* ws */,
2546
- url,
2547
- summary: url != null ? `ws ${shortWs(url)}` : "ws"
2549
+ summary: "ws"
2548
2550
  };
2549
2551
  }
2550
2552
  }
@@ -2,32 +2,42 @@ import { type ITransportConnectionContext, Transport } from "../Transport";
2
2
  import { ETransportType, type ITransportMethod_SendActionData_Input, type ITransportRouteActionParams, type ITransportRouteInfo, type TSendReturnDataMethod, type TUpdateActionRunConfig } from "../Transport.types";
3
3
  import { CustomConnection } from "./CustomConnection";
4
4
  import type { IActionTransportInitialized_Custom } from "./TransportCustom.types";
5
- export interface ICustomTransportOptions {
6
- /** Send a request/progress payload to the external client. */
7
- sendActionData: (input: ITransportMethod_SendActionData_Input) => void;
8
- /** Optional return-path dispatch for bidirectional channels. */
9
- sendReturnData?: TSendReturnDataMethod;
10
- closeTransport?: (input?: ITransportRouteActionParams) => void;
5
+ interface ICustomTransportSharedOptions {
11
6
  updateRunConfig?: TUpdateActionRunConfig;
12
7
  getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
13
- /**
14
- * Advanced escape hatch — full control over readiness/initialization. When provided, the simple
15
- * `sendActionData` / `sendReturnData` / `closeTransport` fields are ignored.
16
- */
17
- getTransport?: IActionTransportInitialized_Custom["getTransport"];
18
8
  /** Short label shown in the devtools chip (defaults to "custom"). */
19
9
  label?: string;
20
10
  /** Override the devtools route info for a specific action. */
21
11
  getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
22
12
  }
13
+ export interface ICustomTransportSendOptions extends ICustomTransportSharedOptions {
14
+ /** Send a request/progress payload to the external client. */
15
+ sendActionData: (input: ITransportMethod_SendActionData_Input) => void;
16
+ /** Optional return-path dispatch for bidirectional channels. */
17
+ sendReturnData?: TSendReturnDataMethod;
18
+ closeTransport?: (input?: ITransportRouteActionParams) => void;
19
+ }
20
+ export interface ICustomTransportAdvancedOptions extends ICustomTransportSharedOptions {
21
+ /** Full control over readiness/initialization for the custom channel. */
22
+ getTransport: IActionTransportInitialized_Custom["getTransport"];
23
+ }
24
+ export type TCustomTransportOptions = (ICustomTransportSendOptions & {
25
+ mode: "send";
26
+ }) | (ICustomTransportAdvancedOptions & {
27
+ mode: "advanced";
28
+ });
23
29
  /**
24
- * Reusable custom transport definition for channels nice-action doesn't model natively. Provide
25
- * `sendActionData` for the simple case, or `getTransport` for full control over the lifecycle.
30
+ * Reusable custom transport definition for channels nice-action doesn't model natively. Create one
31
+ * with `CustomTransport.create({ sendActionData })` for the simple case, or
32
+ * `CustomTransport.createAdvanced({ getTransport })` for full control over the lifecycle.
26
33
  */
27
34
  export declare class CustomTransport extends Transport<ETransportType.custom> {
28
35
  private readonly options;
29
36
  readonly type = ETransportType.custom;
30
- constructor(options: ICustomTransportOptions);
37
+ constructor(options: TCustomTransportOptions);
38
+ static create(options: ICustomTransportSendOptions): CustomTransport;
39
+ static createAdvanced(options: ICustomTransportAdvancedOptions): CustomTransport;
31
40
  _createConnection(_ctx: ITransportConnectionContext): CustomConnection;
32
41
  getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
33
42
  }
43
+ export {};
@@ -2,27 +2,25 @@ import { type ITransportConnectionContext, Transport } from "../Transport";
2
2
  import { ETransportType, type ITransportRouteActionParams, type ITransportRouteInfo, type TUpdateActionRunConfig } from "../Transport.types";
3
3
  import { HttpConnection } from "./HttpConnection";
4
4
  import type { IHttpRequestParams } from "./TransportHttp.types";
5
- type TMaybeResolved<T> = T | ((input: ITransportRouteActionParams) => T);
6
5
  export interface IHttpTransportOptions {
7
- /** Endpoint URL, either static or derived per action. */
8
- url: TMaybeResolved<string>;
9
- /** Optional extra headers, either static or derived per action. */
10
- headers?: TMaybeResolved<Record<string, string>>;
11
- /** Advanced escape hatch — full control over the request. Overrides `url` / `headers`. */
12
- createRequest?: (input: ITransportRouteActionParams) => IHttpRequestParams;
6
+ /**
7
+ * Build the HTTP request for an action. Return a static request for the simple case, or derive the
8
+ * url / headers / body from the action params for full control.
9
+ */
10
+ createRequest: (input: ITransportRouteActionParams) => IHttpRequestParams;
13
11
  updateRunConfig?: TUpdateActionRunConfig;
14
12
  getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
15
13
  }
16
14
  /**
17
- * Reusable HTTP transport definition. Common case is just `new HttpTransport({ url })`; pass
18
- * functions for `url` / `headers` to derive them per action, or `createRequest` for full control.
15
+ * Reusable HTTP transport definition. Create one with `HttpTransport.create({ createRequest })` — the
16
+ * single `createRequest` function lets you keep it simple (`() => ({ url })`) or derive the request
17
+ * per action.
19
18
  */
20
19
  export declare class HttpTransport extends Transport<ETransportType.http> {
21
20
  private readonly options;
22
21
  readonly type = ETransportType.http;
23
22
  constructor(options: IHttpTransportOptions);
24
- private _resolveRequest;
23
+ static create(options: IHttpTransportOptions): HttpTransport;
25
24
  _createConnection(_ctx: ITransportConnectionContext): HttpConnection;
26
25
  getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
27
26
  }
28
- export {};
@@ -9,8 +9,9 @@ export interface ITransportConnectionContext {
9
9
  resolvers?: IActionTransportResolvers;
10
10
  }
11
11
  /**
12
- * Reusable transport definition. Devs construct these (`new HttpTransport({ url })`,
13
- * `new WebSocketTransport({ url })`, …) and pass them to an `ActionExternalClientHandler`. A single
12
+ * Reusable transport definition. Devs construct these (`HttpTransport.create({ createRequest })`,
13
+ * `WebSocketTransport.create({ createWebSocket })`, …) and pass them to an
14
+ * `ActionExternalClientHandler`. A single
14
15
  * definition can be shared across multiple handlers — each handler builds its own live
15
16
  * {@link TransportConnection} via {@link TransportConnection._createConnection}.
16
17
  */
@@ -2,39 +2,47 @@ import { type ITransportConnectionContext, Transport } from "../Transport";
2
2
  import { ETransportType, type ITransportRouteActionParams, type ITransportRouteInfo, type TUpdateActionRunConfig } from "../Transport.types";
3
3
  import type { IActionTransportInitialized_Ws, IActionTransportReadyData_Ws } from "./TransportWebSocket.types";
4
4
  import { WebSocketConnection } from "./WebSocketConnection";
5
- type TMaybeResolved<T> = T | ((input: ITransportRouteActionParams) => T);
6
- export interface IWebSocketTransportOptions {
7
- /** WebSocket endpoint URL, either static or derived per action. Optional when `getTransport` is used. */
8
- url?: TMaybeResolved<string>;
9
- /** Advanced escape hatch — provide your own socket (e.g. with sub-protocols). Overrides `url`. */
10
- createWebSocket?: (input: ITransportRouteActionParams) => WebSocket;
5
+ interface IWebSocketTransportSharedOptions {
11
6
  /** Custom (de)serialization of action payloads on the wire. */
12
7
  formatMessage?: IActionTransportReadyData_Ws["formatMessage"];
13
8
  updateRunConfig?: TUpdateActionRunConfig;
14
9
  /**
15
- * Keys that identify a reusable socket. Defaults to the resolved url, so a single socket is shared
16
- * across actions to the same endpoint instead of opening one per action.
10
+ * Keys that identify a reusable socket, so a single socket is shared across actions to the same
11
+ * endpoint instead of opening one per action.
17
12
  */
18
13
  getTransportCacheKey?: (input: ITransportRouteActionParams) => string[];
14
+ /** Override the devtools route info for a specific action. */
15
+ getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
16
+ }
17
+ export interface IWebSocketTransportSocketOptions extends IWebSocketTransportSharedOptions {
18
+ /** Open (or reuse) the WebSocket for an action — keep it simple or derive it per action. */
19
+ createWebSocket: (input: ITransportRouteActionParams) => WebSocket;
20
+ }
21
+ export interface IWebSocketTransportAdvancedOptions extends IWebSocketTransportSharedOptions {
19
22
  /**
20
- * Advanced escape hatch — full control over readiness/initialization. Use this for contextual
21
- * support detection (return `{ status: ETransportStatus.unsupported }` when the socket shouldn't be
22
- * used) or async URL building (return `{ status: ETransportStatus.initializing, initializationPromise }`
23
- * that resolves to a `ready` socket). When provided, `url` / `createWebSocket` are ignored.
23
+ * Full control over readiness/initialization. Use this for contextual support detection (return
24
+ * `{ status: ETransportStatus.unsupported }` when the socket shouldn't be used) or async url
25
+ * building (return `{ status: ETransportStatus.initializing, initializationPromise }` that resolves
26
+ * to a `ready` socket).
24
27
  */
25
- getTransport?: IActionTransportInitialized_Ws["getTransport"];
26
- /** Override the devtools route info for a specific action (useful alongside `getTransport`). */
27
- getRouteInfo?: (input: ITransportRouteActionParams) => ITransportRouteInfo;
28
+ getTransport: IActionTransportInitialized_Ws["getTransport"];
28
29
  }
30
+ export type TWebSocketTransportOptions = (IWebSocketTransportSocketOptions & {
31
+ mode: "socket";
32
+ }) | (IWebSocketTransportAdvancedOptions & {
33
+ mode: "advanced";
34
+ });
29
35
  /**
30
- * Reusable WebSocket transport definition. Common case is just `new WebSocketTransport({ url })`. The
31
- * underlying socket is cached (by url, by default) and reused across actions to the same endpoint.
36
+ * Reusable WebSocket transport definition. Create one with `WebSocketTransport.create({ createWebSocket })`
37
+ * for the common case, or `WebSocketTransport.createAdvanced({ getTransport })` for full control over
38
+ * readiness. The underlying socket is cached (via `getTransportCacheKey`) and reused across actions.
32
39
  */
33
40
  export declare class WebSocketTransport extends Transport<ETransportType.ws> {
34
41
  private readonly options;
35
42
  readonly type = ETransportType.ws;
36
- constructor(options: IWebSocketTransportOptions);
37
- private _createSocket;
43
+ constructor(options: TWebSocketTransportOptions);
44
+ static create(options: IWebSocketTransportSocketOptions): WebSocketTransport;
45
+ static createAdvanced(options: IWebSocketTransportAdvancedOptions): WebSocketTransport;
38
46
  _createConnection(ctx: ITransportConnectionContext): WebSocketConnection;
39
47
  getRouteInfo(input: ITransportRouteActionParams): ITransportRouteInfo;
40
48
  }
@@ -22,7 +22,7 @@ export * from "./ActionRuntime/Handler/ExternalClient/Transport/err_nice_transpo
22
22
  export { HttpTransport, type IHttpTransportOptions, } from "./ActionRuntime/Handler/ExternalClient/Transport/Http/HttpTransport";
23
23
  export { type ITransportConnectionContext, Transport, } from "./ActionRuntime/Handler/ExternalClient/Transport/Transport";
24
24
  export * from "./ActionRuntime/Handler/ExternalClient/Transport/Transport.types";
25
- export { type IWebSocketTransportOptions, WebSocketTransport, } from "./ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketTransport";
25
+ export { type IWebSocketTransportAdvancedOptions, type IWebSocketTransportSocketOptions, type TWebSocketTransportOptions, WebSocketTransport, } from "./ActionRuntime/Handler/ExternalClient/Transport/WebSocket/WebSocketTransport";
26
26
  export { ActionLocalHandler, createLocalHandler, } from "./ActionRuntime/Handler/Local/ActionLocalHandler";
27
27
  export * from "./ActionRuntime/RuntimeCoordinate";
28
28
  export { EErrId_NiceAction, err_nice_action } from "./errors/err_nice_action";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nice-code/action",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -44,9 +44,9 @@
44
44
  "build-types": "tsc --project tsconfig.build.json"
45
45
  },
46
46
  "dependencies": {
47
- "@nice-code/common-errors": "0.4.6",
48
- "@nice-code/error": "0.4.6",
49
- "@nice-code/util": "0.4.6",
47
+ "@nice-code/common-errors": "0.4.8",
48
+ "@nice-code/error": "0.4.8",
49
+ "@nice-code/util": "0.4.8",
50
50
  "@standard-schema/spec": "^1.1.0",
51
51
  "@tanstack/react-virtual": "^3.13.26",
52
52
  "http-status-codes": "^2.3.0",