@fuzdev/fuz_app 0.10.1 → 0.12.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/dist/actions/action_bridge.d.ts +8 -8
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +5 -5
- package/dist/actions/action_codegen.d.ts +18 -1
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +49 -6
- package/dist/actions/action_event.d.ts +60 -0
- package/dist/actions/action_event.d.ts.map +1 -0
- package/dist/actions/action_event.js +367 -0
- package/dist/actions/action_event_data.d.ts +639 -0
- package/dist/actions/action_event_data.d.ts.map +1 -0
- package/dist/actions/action_event_data.js +29 -0
- package/dist/actions/action_event_helpers.d.ts +73 -0
- package/dist/actions/action_event_helpers.d.ts.map +1 -0
- package/dist/actions/action_event_helpers.js +96 -0
- package/dist/actions/action_event_types.d.ts +31 -0
- package/dist/actions/action_event_types.d.ts.map +1 -0
- package/dist/actions/action_event_types.js +38 -0
- package/dist/actions/action_peer.d.ts +30 -0
- package/dist/actions/action_peer.d.ts.map +1 -0
- package/dist/actions/action_peer.js +146 -0
- package/dist/actions/action_spec.d.ts +1 -1
- package/dist/actions/action_spec.js +1 -1
- package/dist/actions/request_tracker.svelte.d.ts +69 -0
- package/dist/actions/request_tracker.svelte.d.ts.map +1 -0
- package/dist/actions/request_tracker.svelte.js +161 -0
- package/dist/actions/rpc_client.d.ts +43 -0
- package/dist/actions/rpc_client.d.ts.map +1 -0
- package/dist/actions/rpc_client.js +151 -0
- package/dist/actions/transports.d.ts +47 -0
- package/dist/actions/transports.d.ts.map +1 -0
- package/dist/actions/transports.js +108 -0
- package/dist/actions/transports_http.d.ts +16 -0
- package/dist/actions/transports_http.d.ts.map +1 -0
- package/dist/actions/transports_http.js +81 -0
- package/dist/actions/transports_ws.d.ts +26 -0
- package/dist/actions/transports_ws.d.ts.map +1 -0
- package/dist/actions/transports_ws.js +94 -0
- package/dist/actions/transports_ws_backend.d.ts +42 -0
- package/dist/actions/transports_ws_backend.d.ts.map +1 -0
- package/dist/actions/transports_ws_backend.js +133 -0
- package/dist/http/jsonrpc_errors.d.ts +2 -0
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +2 -0
- package/dist/http/surface.d.ts +3 -3
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/realtime/sse.d.ts +3 -3
- package/dist/realtime/sse.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.d.ts +2 -2
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/server/app_server.d.ts +2 -2
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/testing/integration_helpers.d.ts +0 -5
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +6 -1
- package/dist/testing/stubs.d.ts +2 -2
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +1 -0
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/uuid.d.ts +12 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +9 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bridge functions to derive `RouteSpec` and `
|
|
2
|
+
* Bridge functions to derive `RouteSpec` and `EventSpec` from `ActionSpec`.
|
|
3
3
|
*
|
|
4
4
|
* Action specs define the contract (method, input/output, auth, side effects).
|
|
5
5
|
* Bridge functions produce transport-specific specs from them. HTTP-specific
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { z } from 'zod';
|
|
11
11
|
import type { ActionSpec, ActionAuth as ActionSpecAuth, ActionSideEffects } from './action_spec.js';
|
|
12
12
|
import type { RouteSpec, RouteAuth, RouteMethod, RouteHandler } from '../http/route_spec.js';
|
|
13
|
-
import type {
|
|
13
|
+
import type { EventSpec } from '../realtime/sse.js';
|
|
14
14
|
import type { RouteErrorSchemas } from '../http/error_schemas.js';
|
|
15
15
|
/** Options for deriving a `RouteSpec` from an `ActionSpec`. */
|
|
16
16
|
export interface ActionRouteOptions {
|
|
@@ -27,7 +27,7 @@ export interface ActionRouteOptions {
|
|
|
27
27
|
/** Handler-specific error schemas (HTTP status code → Zod schema). Transport-specific — not on ActionSpec. */
|
|
28
28
|
errors?: RouteErrorSchemas;
|
|
29
29
|
}
|
|
30
|
-
/** Options for deriving an `
|
|
30
|
+
/** Options for deriving an `EventSpec` from an `ActionSpec`. */
|
|
31
31
|
export interface ActionEventOptions {
|
|
32
32
|
channel?: string;
|
|
33
33
|
}
|
|
@@ -53,13 +53,13 @@ export declare const derive_http_method: (side_effects: ActionSideEffects) => Ro
|
|
|
53
53
|
*/
|
|
54
54
|
export declare const create_action_route_spec: (spec: ActionSpec, options: ActionRouteOptions) => RouteSpec;
|
|
55
55
|
/**
|
|
56
|
-
* Derive an `
|
|
56
|
+
* Derive an `EventSpec` from an `ActionSpec`.
|
|
57
57
|
*
|
|
58
|
-
* Only `remote_notification` actions can become
|
|
58
|
+
* Only `remote_notification` actions can become push events.
|
|
59
59
|
*
|
|
60
60
|
* @param spec - the action spec (must have `kind: 'remote_notification'`)
|
|
61
|
-
* @param options - optional
|
|
62
|
-
* @returns an `
|
|
61
|
+
* @param options - optional event-specific options (channel)
|
|
62
|
+
* @returns an `EventSpec` ready for `create_validated_broadcaster`
|
|
63
63
|
*/
|
|
64
|
-
export declare const create_action_event_spec: (spec: ActionSpec, options?: ActionEventOptions) =>
|
|
64
|
+
export declare const create_action_event_spec: (spec: ActionSpec, options?: ActionEventOptions) => EventSpec;
|
|
65
65
|
//# sourceMappingURL=action_bridge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,UAAU,EAAE,UAAU,IAAI,cAAc,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAClG,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,UAAU,EAAE,UAAU,IAAI,cAAc,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAClG,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,+IAA+I;IAC/I,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kDAAkD;AAClD,eAAO,MAAM,eAAe,GAAI,MAAM,cAAc,KAAG,SAKtD,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SAmBF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,SAYF,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bridge functions to derive `RouteSpec` and `
|
|
2
|
+
* Bridge functions to derive `RouteSpec` and `EventSpec` from `ActionSpec`.
|
|
3
3
|
*
|
|
4
4
|
* Action specs define the contract (method, input/output, auth, side effects).
|
|
5
5
|
* Bridge functions produce transport-specific specs from them. HTTP-specific
|
|
@@ -56,13 +56,13 @@ export const create_action_route_spec = (spec, options) => {
|
|
|
56
56
|
};
|
|
57
57
|
};
|
|
58
58
|
/**
|
|
59
|
-
* Derive an `
|
|
59
|
+
* Derive an `EventSpec` from an `ActionSpec`.
|
|
60
60
|
*
|
|
61
|
-
* Only `remote_notification` actions can become
|
|
61
|
+
* Only `remote_notification` actions can become push events.
|
|
62
62
|
*
|
|
63
63
|
* @param spec - the action spec (must have `kind: 'remote_notification'`)
|
|
64
|
-
* @param options - optional
|
|
65
|
-
* @returns an `
|
|
64
|
+
* @param options - optional event-specific options (channel)
|
|
65
|
+
* @returns an `EventSpec` ready for `create_validated_broadcaster`
|
|
66
66
|
*/
|
|
67
67
|
export const create_action_event_spec = (spec, options) => {
|
|
68
68
|
if (spec.kind !== 'remote_notification') {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import type { ActionSpecUnion, ActionEventPhase } from './action_spec.js';
|
|
2
3
|
/**
|
|
3
4
|
* Represents an import item with its kind (type, value, or namespace).
|
|
@@ -87,11 +88,27 @@ export declare const get_handler_return_type: (spec: ActionSpecUnion, phase: Act
|
|
|
87
88
|
/**
|
|
88
89
|
* Generates the phase handlers for an action spec using the unified ActionEvent type
|
|
89
90
|
* with the new phase/step type parameters.
|
|
91
|
+
*
|
|
92
|
+
* @param options.action_event_type - custom type name to use instead of `ActionEvent`
|
|
93
|
+
* (consumers can define a narrowed type that carries typed input/output via their codegen maps)
|
|
90
94
|
*/
|
|
91
|
-
export declare const generate_phase_handlers: (spec: ActionSpecUnion, executor: "frontend" | "backend", imports: ImportBuilder
|
|
95
|
+
export declare const generate_phase_handlers: (spec: ActionSpecUnion, executor: "frontend" | "backend", imports: ImportBuilder, options?: {
|
|
96
|
+
action_event_type?: string;
|
|
97
|
+
}) => string;
|
|
92
98
|
/**
|
|
93
99
|
* Creates a file banner comment.
|
|
94
100
|
*/
|
|
95
101
|
export declare const create_banner: (origin_path: string) => string;
|
|
102
|
+
export declare const to_action_spec_identifier: (method: string) => string;
|
|
103
|
+
export declare const to_action_spec_input_identifier: (method: string) => string;
|
|
104
|
+
export declare const to_action_spec_output_identifier: (method: string) => string;
|
|
105
|
+
/**
|
|
106
|
+
* Gets the innermost type of a Zod schema by unwrapping wrappers like transforms, `ZodOptional`, `ZodDefault`, etc.
|
|
107
|
+
*
|
|
108
|
+
* @param schema - the schema to unwrap
|
|
109
|
+
* @returns the innermost schema without wrappers
|
|
110
|
+
*/
|
|
111
|
+
export declare const get_innermost_type: (schema: z.ZodType) => z.ZodType;
|
|
112
|
+
export declare const get_innermost_type_name: (schema: z.ZodType) => string;
|
|
96
113
|
export {};
|
|
97
114
|
//# sourceMappingURL=action_codegen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action_codegen.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_codegen.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"action_codegen.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_codegen.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,KAAK,EAAC,eAAe,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAKxE;;GAEG;AACH,UAAU,UAAU;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;;IACzB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAa;IAE1D;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQrC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI1C;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAOrD;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI;IAgCtD;;;OAGG;IACH,KAAK,IAAI,MAAM;IAIf;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC;IAIxB;;OAEG;IACH,KAAK,IAAI,IAAI;CAqDb;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,KAC9B,KAAK,CAAC,gBAAgB,CA4DxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,OAAO,gBAAgB,EACvB,SAAS,aAAa,EACtB,aAAa,MAAM,KACjB,MAkBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,EAChC,SAAS,aAAa,EACtB,UAAU;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAC,KACpC,MA4BF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,MACU,CAAC;AAG/D,eAAO,MAAM,yBAAyB,GAAI,QAAQ,MAAM,KAAG,MAAiC,CAAC;AAC7F,eAAO,MAAM,+BAA+B,GAAI,QAAQ,MAAM,KAAG,MACpB,CAAC;AAC9C,eAAO,MAAM,gCAAgC,GAAI,QAAQ,MAAM,KAAG,MACpB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,CAAC,CAAC,OAwBxD,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,MAI3D,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { UnreachableError } from '@fuzdev/fuz_util/error.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { zod_to_subschema } from '@fuzdev/fuz_util/zod.js';
|
|
2
4
|
/**
|
|
3
5
|
* Manages imports for generated code, building them on demand.
|
|
4
6
|
* Automatically optimizes type-only imports to use `import type` syntax.
|
|
@@ -246,24 +248,29 @@ export const get_handler_return_type = (spec, phase, imports, path_prefix) => {
|
|
|
246
248
|
/**
|
|
247
249
|
* Generates the phase handlers for an action spec using the unified ActionEvent type
|
|
248
250
|
* with the new phase/step type parameters.
|
|
251
|
+
*
|
|
252
|
+
* @param options.action_event_type - custom type name to use instead of `ActionEvent`
|
|
253
|
+
* (consumers can define a narrowed type that carries typed input/output via their codegen maps)
|
|
249
254
|
*/
|
|
250
|
-
export const generate_phase_handlers = (spec, executor, imports) => {
|
|
255
|
+
export const generate_phase_handlers = (spec, executor, imports, options) => {
|
|
251
256
|
const { method } = spec;
|
|
252
257
|
const phases = get_executor_phases(spec, executor);
|
|
253
258
|
if (phases.length === 0) {
|
|
254
259
|
return `${method}?: never`;
|
|
255
260
|
}
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
const action_event_type = options?.action_event_type ?? 'ActionEvent';
|
|
262
|
+
// Only add the default ActionEvent import if using the default type name
|
|
263
|
+
if (action_event_type === 'ActionEvent') {
|
|
264
|
+
imports.add_type('@fuzdev/fuz_app/actions/action_event.js', 'ActionEvent');
|
|
265
|
+
}
|
|
260
266
|
// Generate handler definitions for each phase
|
|
267
|
+
const path_prefix = executor === 'frontend' ? './' : '../';
|
|
261
268
|
const phase_handlers = phases
|
|
262
269
|
.map((phase) => {
|
|
263
270
|
// Pass imports to get_handler_return_type so it can add necessary imports
|
|
264
271
|
const return_type = get_handler_return_type(spec, phase, imports, path_prefix);
|
|
265
272
|
return `${phase}?: (
|
|
266
|
-
action_event:
|
|
273
|
+
action_event: ${action_event_type}<'${method}', '${phase}', 'handling'>
|
|
267
274
|
) => ${return_type}`;
|
|
268
275
|
})
|
|
269
276
|
.join(';\n\t\t');
|
|
@@ -273,3 +280,39 @@ export const generate_phase_handlers = (spec, executor, imports) => {
|
|
|
273
280
|
* Creates a file banner comment.
|
|
274
281
|
*/
|
|
275
282
|
export const create_banner = (origin_path) => `generated by ${origin_path} - DO NOT EDIT OR RISK LOST DATA`;
|
|
283
|
+
// TODO rethink these, see also zzz `codegen.ts`
|
|
284
|
+
export const to_action_spec_identifier = (method) => `${method}_action_spec`;
|
|
285
|
+
export const to_action_spec_input_identifier = (method) => `${to_action_spec_identifier(method)}.input`;
|
|
286
|
+
export const to_action_spec_output_identifier = (method) => `${to_action_spec_identifier(method)}.output`;
|
|
287
|
+
/**
|
|
288
|
+
* Gets the innermost type of a Zod schema by unwrapping wrappers like transforms, `ZodOptional`, `ZodDefault`, etc.
|
|
289
|
+
*
|
|
290
|
+
* @param schema - the schema to unwrap
|
|
291
|
+
* @returns the innermost schema without wrappers
|
|
292
|
+
*/
|
|
293
|
+
export const get_innermost_type = (schema) => {
|
|
294
|
+
const def = schema.def;
|
|
295
|
+
// Handle wrapper types that need unwrapping
|
|
296
|
+
if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
|
|
297
|
+
return get_innermost_type(schema.unwrap());
|
|
298
|
+
}
|
|
299
|
+
if (schema instanceof z.ZodDefault) {
|
|
300
|
+
const subschema = zod_to_subschema(def);
|
|
301
|
+
if (subschema) {
|
|
302
|
+
return get_innermost_type(subschema);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Handle transforms, pipes, and other wrappers
|
|
306
|
+
if (def.type === 'transform' || def.type === 'pipe' || def.type === 'prefault') {
|
|
307
|
+
const subschema = zod_to_subschema(def);
|
|
308
|
+
if (subschema) {
|
|
309
|
+
return get_innermost_type(subschema);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return schema;
|
|
313
|
+
};
|
|
314
|
+
export const get_innermost_type_name = (schema) => {
|
|
315
|
+
const innermost = get_innermost_type(schema);
|
|
316
|
+
const def = innermost.def;
|
|
317
|
+
return def.type;
|
|
318
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActionEvent — state machine for action lifecycle management.
|
|
3
|
+
*
|
|
4
|
+
* Manages an action through its phases (send_request → receive_response, etc.)
|
|
5
|
+
* and steps (initial → parsed → handling → handled/failed).
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { ActionEventPhase, ActionSpecUnion } from './action_spec.js';
|
|
10
|
+
import type { JsonrpcRequest, JsonrpcResponseOrError, JsonrpcNotification } from '../http/jsonrpc.js';
|
|
11
|
+
import type { ActionEventEnvironment, ActionEventStep } from './action_event_types.js';
|
|
12
|
+
import { type ActionEventDataUnion } from './action_event_data.js';
|
|
13
|
+
export type ActionEventChangeObserver<TMethod extends string = string> = (new_data: ActionEventDataUnion<TMethod>, old_data: ActionEventDataUnion<TMethod>, event: ActionEvent<TMethod>) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Action event that manages the lifecycle of an action through its state machine.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ActionEvent<TMethod extends string = string, TPhase extends ActionEventPhase = ActionEventPhase, TStep extends ActionEventStep = ActionEventStep> {
|
|
18
|
+
#private;
|
|
19
|
+
readonly environment: ActionEventEnvironment;
|
|
20
|
+
readonly spec: ActionSpecUnion;
|
|
21
|
+
get data(): ActionEventDataUnion<TMethod> & {
|
|
22
|
+
phase: TPhase;
|
|
23
|
+
step: TStep;
|
|
24
|
+
};
|
|
25
|
+
constructor(environment: ActionEventEnvironment, spec: ActionSpecUnion, data: ActionEventDataUnion<TMethod>);
|
|
26
|
+
toJSON(): ActionEventDataUnion<TMethod>;
|
|
27
|
+
observe(listener: ActionEventChangeObserver<TMethod>): () => void;
|
|
28
|
+
set_data(new_data: ActionEventDataUnion<TMethod>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Parse input data according to the action's schema.
|
|
31
|
+
*/
|
|
32
|
+
parse(): this;
|
|
33
|
+
/**
|
|
34
|
+
* Execute the handler for the current phase.
|
|
35
|
+
*/
|
|
36
|
+
handle_async(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Execute handler synchronously (only for sync local_call actions).
|
|
39
|
+
*/
|
|
40
|
+
handle_sync(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Transition to a new phase.
|
|
43
|
+
*/
|
|
44
|
+
transition(phase: ActionEventPhase): void;
|
|
45
|
+
is_complete(): boolean;
|
|
46
|
+
update_progress(progress: unknown): void;
|
|
47
|
+
set_request(request: JsonrpcRequest): void;
|
|
48
|
+
set_response(response: JsonrpcResponseOrError): void;
|
|
49
|
+
set_notification(notification: JsonrpcNotification): void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create an action event from a spec and initial input.
|
|
53
|
+
*/
|
|
54
|
+
export declare const create_action_event: <TMethod extends string = string>(environment: ActionEventEnvironment, spec: ActionSpecUnion, input: unknown, initial_phase?: ActionEventPhase) => ActionEvent<TMethod>;
|
|
55
|
+
/**
|
|
56
|
+
* Reconstruct an action event from serialized JSON data.
|
|
57
|
+
*/
|
|
58
|
+
export declare const create_action_event_from_json: <TMethod extends string = string>(json: ActionEventDataUnion<TMethod>, environment: ActionEventEnvironment) => ActionEvent<TMethod>;
|
|
59
|
+
export declare const parse_action_event: (raw_json: unknown, environment: ActionEventEnvironment) => ActionEvent;
|
|
60
|
+
//# sourceMappingURL=action_event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action_event.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_event.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAC,gBAAgB,EAAc,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAWpF,OAAO,KAAK,EACX,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EAEnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,sBAAsB,EAAE,eAAe,EAAC,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAkB,KAAK,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAwBlF,MAAM,MAAM,yBAAyB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI,CACxE,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,EACvC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,KACvB,IAAI,CAAC;AAEV;;GAEG;AACH,qBAAa,WAAW,CACvB,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/B,MAAM,SAAS,gBAAgB,GAAG,gBAAgB,EAClD,KAAK,SAAS,eAAe,GAAG,eAAe;;IAK/C,QAAQ,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC7C,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAE/B,IAAI,IAAI,IAAI,oBAAoB,CAAC,OAAO,CAAC,GAAG;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAA;KAAC,CAEvE;gBAGA,WAAW,EAAE,sBAAsB,EACnC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC;IAOpC,MAAM,IAAI,oBAAoB,CAAC,OAAO,CAAC;IAMvC,OAAO,CAAC,QAAQ,EAAE,yBAAyB,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI;IAKjE,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,IAAI;IAUvD;;OAEG;IACH,KAAK,IAAI,IAAI;IA8Cb;;OAEG;IAGG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA0CnC;;OAEG;IACH,WAAW,IAAI,IAAI;IAkCnB;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAezC,WAAW,IAAI,OAAO;IAItB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIxC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAQ1C,YAAY,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAUpD,gBAAgB,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI;CAyKzD;AAGD;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAClE,aAAa,sBAAsB,EACnC,MAAM,eAAe,EACrB,OAAO,OAAO,EACd,gBAAgB,gBAAgB,KAC9B,WAAW,CAAC,OAAO,CAiBrB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,6BAA6B,GAAI,OAAO,SAAS,MAAM,GAAG,MAAM,EAC5E,MAAM,oBAAoB,CAAC,OAAO,CAAC,EACnC,aAAa,sBAAsB,KACjC,WAAW,CAAC,OAAO,CAOrB,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAC9B,UAAU,OAAO,EACjB,aAAa,sBAAsB,KACjC,WAGF,CAAC"}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActionEvent — state machine for action lifecycle management.
|
|
3
|
+
*
|
|
4
|
+
* Manages an action through its phases (send_request → receive_response, etc.)
|
|
5
|
+
* and steps (initial → parsed → handling → handled/failed).
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { create_jsonrpc_request, create_jsonrpc_response, create_jsonrpc_error_response, create_jsonrpc_notification, to_jsonrpc_params, to_jsonrpc_result, is_jsonrpc_error_response, } from '../http/jsonrpc_helpers.js';
|
|
11
|
+
import { jsonrpc_error_messages, ThrownJsonrpcError } from '../http/jsonrpc_errors.js';
|
|
12
|
+
import { ActionEventData } from './action_event_data.js';
|
|
13
|
+
import { validate_step_transition, validate_phase_transition, should_validate_output, is_action_complete, create_initial_data, get_initial_phase, is_request_response, is_send_request_with_parsed_input, is_notification_send_with_parsed_input, } from './action_event_helpers.js';
|
|
14
|
+
import { create_uuid } from '../uuid.js';
|
|
15
|
+
/** Formats a Zod validation error with field paths for clearer error messages. */
|
|
16
|
+
const format_zod_validation_error = (error) => error.issues
|
|
17
|
+
.map((i) => {
|
|
18
|
+
const path = i.path.length > 0 ? `${i.path.join('.')}: ` : '';
|
|
19
|
+
return `${path}${i.message}`;
|
|
20
|
+
})
|
|
21
|
+
.join(', ');
|
|
22
|
+
/**
|
|
23
|
+
* Action event that manages the lifecycle of an action through its state machine.
|
|
24
|
+
*/
|
|
25
|
+
export class ActionEvent {
|
|
26
|
+
#data;
|
|
27
|
+
#listeners = new Set();
|
|
28
|
+
environment;
|
|
29
|
+
spec;
|
|
30
|
+
get data() {
|
|
31
|
+
return this.#data;
|
|
32
|
+
}
|
|
33
|
+
constructor(environment, spec, data) {
|
|
34
|
+
this.environment = environment;
|
|
35
|
+
this.spec = spec;
|
|
36
|
+
this.#data = data;
|
|
37
|
+
}
|
|
38
|
+
toJSON() {
|
|
39
|
+
return structuredClone(this.#data);
|
|
40
|
+
}
|
|
41
|
+
// TODO rethink the reactivity of this class, maybe just use `$state` or `$state.raw`?
|
|
42
|
+
// does that have any negative implications when used on the backend?
|
|
43
|
+
observe(listener) {
|
|
44
|
+
this.#listeners.add(listener);
|
|
45
|
+
return () => this.#listeners.delete(listener);
|
|
46
|
+
}
|
|
47
|
+
set_data(new_data) {
|
|
48
|
+
const old_data = this.#data;
|
|
49
|
+
this.#data = new_data;
|
|
50
|
+
// Notify listeners
|
|
51
|
+
for (const listener of this.#listeners) {
|
|
52
|
+
listener(new_data, old_data, this);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parse input data according to the action's schema.
|
|
57
|
+
*/
|
|
58
|
+
parse() {
|
|
59
|
+
if (this.#data.step !== 'initial') {
|
|
60
|
+
throw new Error(`cannot parse from step '${this.#data.step}' - must be 'initial'`);
|
|
61
|
+
}
|
|
62
|
+
// Check for error in response - transition to receive_error instead of failing
|
|
63
|
+
if (is_jsonrpc_error_response(this.#data.response)) {
|
|
64
|
+
if (this.#data.kind === 'request_response' && this.#data.phase === 'receive_response') {
|
|
65
|
+
// Transition to receive_error instead of failing
|
|
66
|
+
this.#transition_to_error_phase('receive_error', this.#data.response.error);
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
// Fallback for unexpected phases
|
|
70
|
+
this.#fail(this.#data.response.error);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
// Input already validated in predecessor phase — skip re-parsing
|
|
74
|
+
if (this.#data.kind === 'request_response' &&
|
|
75
|
+
(this.#data.phase === 'receive_response' || this.#data.phase === 'send_response')) {
|
|
76
|
+
this.#transition_step('parsed');
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
const parsed = this.spec.input.safeParse(this.#data.input);
|
|
80
|
+
if (parsed.success) {
|
|
81
|
+
this.#transition_step('parsed', { input: parsed.data });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Input validation errors fail immediately without transitioning to error phases.
|
|
85
|
+
// Design decision: Input validation failures are client-side programming errors
|
|
86
|
+
// that should be caught during development, not runtime errors requiring error handlers.
|
|
87
|
+
// Handler errors (network, server, business logic) DO transition to error phases.
|
|
88
|
+
this.#fail(
|
|
89
|
+
// no need to protect this info
|
|
90
|
+
jsonrpc_error_messages.invalid_params(`failed to parse input: ${format_zod_validation_error(parsed.error)}`, { validation_errors: parsed.error.issues }));
|
|
91
|
+
}
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Execute the handler for the current phase.
|
|
96
|
+
*/
|
|
97
|
+
// TODO add timeout support
|
|
98
|
+
// TODO add cancellation support
|
|
99
|
+
async handle_async() {
|
|
100
|
+
if (this.#data.step === 'failed') {
|
|
101
|
+
return; // already failed, no-op
|
|
102
|
+
}
|
|
103
|
+
if (this.#data.step !== 'parsed') {
|
|
104
|
+
throw new Error(`cannot handle from step '${this.#data.step}' - must be 'parsed'`);
|
|
105
|
+
}
|
|
106
|
+
this.#transition_step('handling', this.#create_handling_updates());
|
|
107
|
+
const handler = this.environment.lookup_action_handler(this.spec.method, this.#data.phase);
|
|
108
|
+
if (!handler) {
|
|
109
|
+
this.#transition_step('handled');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const result = await handler(this);
|
|
114
|
+
this.#complete_handling(result);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
// Preserve ThrownJsonrpcError structure, wrap others as internal_error
|
|
118
|
+
const error_json = error instanceof ThrownJsonrpcError
|
|
119
|
+
? { code: error.code, message: error.message, data: error.data }
|
|
120
|
+
: jsonrpc_error_messages.internal_error('unknown error');
|
|
121
|
+
// If we're already in an error phase, transition to failed
|
|
122
|
+
// Otherwise, transition to appropriate error phase
|
|
123
|
+
if (this.#data.phase === 'send_error' || this.#data.phase === 'receive_error') {
|
|
124
|
+
this.#fail(error_json);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Transition to appropriate error phase
|
|
128
|
+
const error_phase = this.#get_error_phase_for_current_phase();
|
|
129
|
+
if (error_phase) {
|
|
130
|
+
this.#transition_to_error_phase(error_phase, error_json);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
this.#fail(error_json);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Execute handler synchronously (only for sync local_call actions).
|
|
140
|
+
*/
|
|
141
|
+
handle_sync() {
|
|
142
|
+
if (this.spec.kind !== 'local_call' || this.spec.async) {
|
|
143
|
+
throw new Error('handle_sync can only be used with synchronous local_call actions');
|
|
144
|
+
}
|
|
145
|
+
if (this.#data.step === 'failed') {
|
|
146
|
+
return; // already failed, no-op
|
|
147
|
+
}
|
|
148
|
+
if (this.#data.step !== 'parsed') {
|
|
149
|
+
throw new Error(`cannot handle from step '${this.#data.step}' - must be 'parsed'`);
|
|
150
|
+
}
|
|
151
|
+
this.#transition_step('handling', this.#create_handling_updates());
|
|
152
|
+
const handler = this.environment.lookup_action_handler(this.spec.method, this.#data.phase);
|
|
153
|
+
if (!handler) {
|
|
154
|
+
this.#transition_step('handled');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const result = handler(this);
|
|
159
|
+
this.#complete_handling(result);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
// Preserve ThrownJsonrpcError structure, wrap others as internal_error
|
|
163
|
+
const error_json = error instanceof ThrownJsonrpcError
|
|
164
|
+
? { code: error.code, message: error.message, data: error.data }
|
|
165
|
+
: jsonrpc_error_messages.internal_error('unknown error');
|
|
166
|
+
this.#fail(error_json);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Transition to a new phase.
|
|
171
|
+
*/
|
|
172
|
+
transition(phase) {
|
|
173
|
+
if (this.#data.step === 'failed') {
|
|
174
|
+
return; // already failed, no-op
|
|
175
|
+
}
|
|
176
|
+
if (this.#data.step !== 'handled') {
|
|
177
|
+
throw new Error(`cannot transition from step '${this.#data.step}' - must be 'handled'`);
|
|
178
|
+
}
|
|
179
|
+
validate_phase_transition(this.#data.phase, phase);
|
|
180
|
+
// Create new data for the phase
|
|
181
|
+
const new_data = this.#create_phase_data(phase);
|
|
182
|
+
this.set_data(new_data);
|
|
183
|
+
}
|
|
184
|
+
is_complete() {
|
|
185
|
+
return is_action_complete(this.#data);
|
|
186
|
+
}
|
|
187
|
+
update_progress(progress) {
|
|
188
|
+
this.#update_data({ progress });
|
|
189
|
+
}
|
|
190
|
+
set_request(request) {
|
|
191
|
+
this.#validate_protocol_setter('request', {
|
|
192
|
+
kind: 'request_response',
|
|
193
|
+
phase: 'receive_request',
|
|
194
|
+
});
|
|
195
|
+
this.#update_data({ request });
|
|
196
|
+
}
|
|
197
|
+
set_response(response) {
|
|
198
|
+
this.#validate_protocol_setter('response', {
|
|
199
|
+
kind: 'request_response',
|
|
200
|
+
phase: 'receive_response',
|
|
201
|
+
});
|
|
202
|
+
const output = 'result' in response ? response.result : null;
|
|
203
|
+
this.#update_data({ response, output });
|
|
204
|
+
}
|
|
205
|
+
set_notification(notification) {
|
|
206
|
+
this.#validate_protocol_setter('notification', {
|
|
207
|
+
kind: 'remote_notification',
|
|
208
|
+
phase: 'receive',
|
|
209
|
+
});
|
|
210
|
+
this.#update_data({ notification });
|
|
211
|
+
}
|
|
212
|
+
#transition_step(step, updates) {
|
|
213
|
+
validate_step_transition(this.#data.step, step);
|
|
214
|
+
this.#update_data({ ...updates, step });
|
|
215
|
+
}
|
|
216
|
+
/** Shallowly merge `updates` with the current data immutably. */
|
|
217
|
+
#update_data(updates) {
|
|
218
|
+
const new_data = { ...this.#data, ...updates };
|
|
219
|
+
this.set_data(new_data);
|
|
220
|
+
}
|
|
221
|
+
// TODO usage of this in this module is silently swallowing errors, maybe log on the environment?
|
|
222
|
+
#fail(error) {
|
|
223
|
+
this.#transition_step('failed', { error });
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Determine which error phase to transition to based on current phase.
|
|
227
|
+
*/
|
|
228
|
+
#get_error_phase_for_current_phase() {
|
|
229
|
+
if (this.#data.kind !== 'request_response')
|
|
230
|
+
return null;
|
|
231
|
+
switch (this.#data.phase) {
|
|
232
|
+
case 'send_request':
|
|
233
|
+
case 'receive_request':
|
|
234
|
+
return 'send_error';
|
|
235
|
+
case 'receive_response':
|
|
236
|
+
return 'receive_error';
|
|
237
|
+
default:
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Transition to an error phase instead of failing.
|
|
243
|
+
*/
|
|
244
|
+
#transition_to_error_phase(phase, error) {
|
|
245
|
+
const new_data = {
|
|
246
|
+
...this.#data,
|
|
247
|
+
phase,
|
|
248
|
+
step: 'parsed',
|
|
249
|
+
error,
|
|
250
|
+
output: null,
|
|
251
|
+
};
|
|
252
|
+
this.set_data(new_data);
|
|
253
|
+
}
|
|
254
|
+
#validate_protocol_setter(field, requirements) {
|
|
255
|
+
if (this.#data.kind !== requirements.kind || this.#data.phase !== requirements.phase) {
|
|
256
|
+
throw new Error(`can only set ${field} in ${requirements.phase} phase`);
|
|
257
|
+
}
|
|
258
|
+
if (this.#data.step !== 'initial') {
|
|
259
|
+
throw new Error(`can only set ${field} at initial step`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
#create_handling_updates() {
|
|
263
|
+
// Create protocol messages when transitioning to 'handling' step
|
|
264
|
+
// We check for 'parsed' state since this method is called before the transition
|
|
265
|
+
if (is_send_request_with_parsed_input(this.#data)) {
|
|
266
|
+
return {
|
|
267
|
+
request: create_jsonrpc_request(this.spec.method, to_jsonrpc_params(this.#data.input), create_uuid()),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (is_notification_send_with_parsed_input(this.#data)) {
|
|
271
|
+
return {
|
|
272
|
+
notification: create_jsonrpc_notification(this.spec.method, to_jsonrpc_params(this.#data.input)),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
#complete_handling(output) {
|
|
278
|
+
if (output !== undefined && should_validate_output(this.spec.kind, this.#data.phase)) {
|
|
279
|
+
const parsed = this.spec.output.safeParse(output);
|
|
280
|
+
if (parsed.success) {
|
|
281
|
+
this.#transition_step('handled', { output: parsed.data });
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
this.#fail(jsonrpc_error_messages.validation_error(`failed to parse output: ${format_zod_validation_error(parsed.error)}`, { output, validation_errors: parsed.error.issues }));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
this.#transition_step('handled');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
#create_phase_data(phase) {
|
|
292
|
+
const base_data = create_initial_data(this.#data.kind, phase, this.#data.method, this.#data.executor, this.#data.input);
|
|
293
|
+
// Carry forward data based on transition
|
|
294
|
+
if (is_request_response(this.#data)) {
|
|
295
|
+
if (phase === 'receive_response' && this.#data.request) {
|
|
296
|
+
// Carry forward the request when transitioning to receive_response
|
|
297
|
+
return { ...base_data, request: this.#data.request };
|
|
298
|
+
}
|
|
299
|
+
else if (phase === 'send_response' && this.#data.request) {
|
|
300
|
+
// Create the response when transitioning to send_response
|
|
301
|
+
const response = this.#create_response_from_data();
|
|
302
|
+
return {
|
|
303
|
+
...base_data,
|
|
304
|
+
output: this.#data.output,
|
|
305
|
+
request: this.#data.request,
|
|
306
|
+
response,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
else if (phase === 'send_error' && this.#data.error) {
|
|
310
|
+
// Carry forward error and request (if available) when transitioning to send_error
|
|
311
|
+
return {
|
|
312
|
+
...base_data,
|
|
313
|
+
error: this.#data.error,
|
|
314
|
+
request: this.#data.request || null,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
else if (phase === 'receive_error' && this.#data.error) {
|
|
318
|
+
// Carry forward error, request, and response when transitioning to receive_error
|
|
319
|
+
return {
|
|
320
|
+
...base_data,
|
|
321
|
+
error: this.#data.error,
|
|
322
|
+
request: this.#data.request,
|
|
323
|
+
response: this.#data.response,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return base_data;
|
|
328
|
+
}
|
|
329
|
+
#create_response_from_data() {
|
|
330
|
+
if (!is_request_response(this.#data) || !this.#data.request) {
|
|
331
|
+
throw new Error('cannot create response without request');
|
|
332
|
+
}
|
|
333
|
+
if (this.#data.error) {
|
|
334
|
+
return create_jsonrpc_error_response(this.#data.request.id, this.#data.error);
|
|
335
|
+
}
|
|
336
|
+
const result = to_jsonrpc_result(this.#data.output);
|
|
337
|
+
return create_jsonrpc_response(this.#data.request.id, result);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// TODO not sure about this helper's design/location (should it be internal to the class constructor? a static method?)
|
|
341
|
+
/**
|
|
342
|
+
* Create an action event from a spec and initial input.
|
|
343
|
+
*/
|
|
344
|
+
export const create_action_event = (environment, spec, input, initial_phase) => {
|
|
345
|
+
const phase = initial_phase || get_initial_phase(spec.kind, spec.initiator, environment.executor);
|
|
346
|
+
if (!phase) {
|
|
347
|
+
throw new Error(`executor '${environment.executor}' cannot initiate action '${spec.method}' with initiator '${spec.initiator}'`);
|
|
348
|
+
}
|
|
349
|
+
const initial_data = create_initial_data(spec.kind, phase, spec.method, environment.executor, input);
|
|
350
|
+
return new ActionEvent(environment, spec, initial_data);
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Reconstruct an action event from serialized JSON data.
|
|
354
|
+
*/
|
|
355
|
+
export const create_action_event_from_json = (json, environment) => {
|
|
356
|
+
const spec = environment.lookup_action_spec(json.method);
|
|
357
|
+
if (!spec) {
|
|
358
|
+
throw new Error(`no spec found for method '${json.method}'`);
|
|
359
|
+
}
|
|
360
|
+
return new ActionEvent(environment, spec, json);
|
|
361
|
+
};
|
|
362
|
+
// TODO this and the above one arent used atm, see the comment on `create_action_event` too
|
|
363
|
+
// TODO how to avoid casting? this should generally be safe but we dont have schemas for each possible action event state
|
|
364
|
+
export const parse_action_event = (raw_json, environment) => {
|
|
365
|
+
const json = ActionEventData.parse(raw_json);
|
|
366
|
+
return create_action_event_from_json(json, environment);
|
|
367
|
+
};
|