@fuzdev/fuz_app 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/actions/action_bridge.d.ts +8 -8
  2. package/dist/actions/action_bridge.d.ts.map +1 -1
  3. package/dist/actions/action_bridge.js +5 -5
  4. package/dist/actions/action_codegen.d.ts +18 -1
  5. package/dist/actions/action_codegen.d.ts.map +1 -1
  6. package/dist/actions/action_codegen.js +49 -6
  7. package/dist/actions/action_event.d.ts +60 -0
  8. package/dist/actions/action_event.d.ts.map +1 -0
  9. package/dist/actions/action_event.js +361 -0
  10. package/dist/actions/action_event_data.d.ts +639 -0
  11. package/dist/actions/action_event_data.d.ts.map +1 -0
  12. package/dist/actions/action_event_data.js +29 -0
  13. package/dist/actions/action_event_helpers.d.ts +73 -0
  14. package/dist/actions/action_event_helpers.d.ts.map +1 -0
  15. package/dist/actions/action_event_helpers.js +96 -0
  16. package/dist/actions/action_event_types.d.ts +31 -0
  17. package/dist/actions/action_event_types.d.ts.map +1 -0
  18. package/dist/actions/action_event_types.js +38 -0
  19. package/dist/actions/action_peer.d.ts +30 -0
  20. package/dist/actions/action_peer.d.ts.map +1 -0
  21. package/dist/actions/action_peer.js +146 -0
  22. package/dist/actions/action_rpc.d.ts.map +1 -1
  23. package/dist/actions/action_rpc.js +6 -2
  24. package/dist/actions/action_spec.d.ts +1 -1
  25. package/dist/actions/action_spec.js +1 -1
  26. package/dist/actions/request_tracker.svelte.d.ts +69 -0
  27. package/dist/actions/request_tracker.svelte.d.ts.map +1 -0
  28. package/dist/actions/request_tracker.svelte.js +161 -0
  29. package/dist/actions/rpc_client.d.ts +43 -0
  30. package/dist/actions/rpc_client.d.ts.map +1 -0
  31. package/dist/actions/rpc_client.js +151 -0
  32. package/dist/actions/transports.d.ts +47 -0
  33. package/dist/actions/transports.d.ts.map +1 -0
  34. package/dist/actions/transports.js +108 -0
  35. package/dist/actions/transports_http.d.ts +16 -0
  36. package/dist/actions/transports_http.d.ts.map +1 -0
  37. package/dist/actions/transports_http.js +81 -0
  38. package/dist/actions/transports_ws.d.ts +26 -0
  39. package/dist/actions/transports_ws.d.ts.map +1 -0
  40. package/dist/actions/transports_ws.js +94 -0
  41. package/dist/actions/transports_ws_backend.d.ts +42 -0
  42. package/dist/actions/transports_ws_backend.d.ts.map +1 -0
  43. package/dist/actions/transports_ws_backend.js +133 -0
  44. package/dist/http/jsonrpc.d.ts +22 -97
  45. package/dist/http/jsonrpc.d.ts.map +1 -1
  46. package/dist/http/jsonrpc.js +11 -24
  47. package/dist/http/jsonrpc_errors.d.ts +2 -0
  48. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  49. package/dist/http/jsonrpc_errors.js +2 -0
  50. package/dist/http/surface.d.ts +3 -3
  51. package/dist/http/surface.d.ts.map +1 -1
  52. package/dist/realtime/sse.d.ts +5 -3
  53. package/dist/realtime/sse.d.ts.map +1 -1
  54. package/dist/realtime/sse_auth_guard.d.ts +2 -2
  55. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  56. package/dist/server/app_server.d.ts +2 -2
  57. package/dist/server/app_server.d.ts.map +1 -1
  58. package/dist/testing/stubs.d.ts +2 -2
  59. package/dist/testing/stubs.d.ts.map +1 -1
  60. package/dist/uuid.d.ts +12 -0
  61. package/dist/uuid.d.ts.map +1 -0
  62. package/dist/uuid.js +9 -0
  63. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Bridge functions to derive `RouteSpec` and `SseEventSpec` from `ActionSpec`.
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 { SseEventSpec } from '../realtime/sse.js';
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 `SseEventSpec` from an `ActionSpec`. */
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 `SseEventSpec` from an `ActionSpec`.
56
+ * Derive an `EventSpec` from an `ActionSpec`.
57
57
  *
58
- * Only `remote_notification` actions can become SSE events.
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 SSE-specific options (channel)
62
- * @returns an `SseEventSpec` ready for `create_validated_broadcaster`
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) => SseEventSpec;
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,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,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,mEAAmE;AACnE,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,YAYF,CAAC"}
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 `SseEventSpec` from `ActionSpec`.
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 `SseEventSpec` from an `ActionSpec`.
59
+ * Derive an `EventSpec` from an `ActionSpec`.
60
60
  *
61
- * Only `remote_notification` actions can become SSE events.
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 SSE-specific options (channel)
65
- * @returns an `SseEventSpec` ready for `create_validated_broadcaster`
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) => string;
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":"AAEA,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;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,eAAe,EACrB,UAAU,UAAU,GAAG,SAAS,EAChC,SAAS,aAAa,KACpB,MAyBF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,MACU,CAAC"}
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
- // Add necessary imports for the unified system
257
- // Backend types file is in server/ subdirectory, so needs different relative paths
258
- const path_prefix = executor === 'frontend' ? './' : '../';
259
- imports.add_type(`${path_prefix}action_event.js`, 'ActionEvent');
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: ActionEvent<'${method}', '${phase}', 'handling'>
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;AAGH,OAAO,KAAK,EAAC,gBAAgB,EAAc,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAWpF,OAAO,KAAK,EACX,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EAEnB,MAAM,oBAAoB,CAAC;AAE5B,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;IAqCb;;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,361 @@
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
+ const parsed = this.spec.input.safeParse(this.#data.input);
74
+ if (parsed.success) {
75
+ this.#transition_step('parsed', { input: parsed.data });
76
+ }
77
+ else {
78
+ // Input validation errors fail immediately without transitioning to error phases.
79
+ // Design decision: Input validation failures are client-side programming errors
80
+ // that should be caught during development, not runtime errors requiring error handlers.
81
+ // Handler errors (network, server, business logic) DO transition to error phases.
82
+ this.#fail(
83
+ // no need to protect this info
84
+ jsonrpc_error_messages.invalid_params(`failed to parse input: ${format_zod_validation_error(parsed.error)}`, { validation_errors: parsed.error.issues }));
85
+ }
86
+ return this;
87
+ }
88
+ /**
89
+ * Execute the handler for the current phase.
90
+ */
91
+ // TODO add timeout support
92
+ // TODO add cancellation support
93
+ async handle_async() {
94
+ if (this.#data.step === 'failed') {
95
+ return; // already failed, no-op
96
+ }
97
+ if (this.#data.step !== 'parsed') {
98
+ throw new Error(`cannot handle from step '${this.#data.step}' - must be 'parsed'`);
99
+ }
100
+ this.#transition_step('handling', this.#create_handling_updates());
101
+ const handler = this.environment.lookup_action_handler(this.spec.method, this.#data.phase);
102
+ if (!handler) {
103
+ this.#transition_step('handled');
104
+ return;
105
+ }
106
+ try {
107
+ const result = await handler(this);
108
+ this.#complete_handling(result);
109
+ }
110
+ catch (error) {
111
+ // Preserve ThrownJsonrpcError structure, wrap others as internal_error
112
+ const error_json = error instanceof ThrownJsonrpcError
113
+ ? { code: error.code, message: error.message, data: error.data }
114
+ : jsonrpc_error_messages.internal_error('unknown error');
115
+ // If we're already in an error phase, transition to failed
116
+ // Otherwise, transition to appropriate error phase
117
+ if (this.#data.phase === 'send_error' || this.#data.phase === 'receive_error') {
118
+ this.#fail(error_json);
119
+ }
120
+ else {
121
+ // Transition to appropriate error phase
122
+ const error_phase = this.#get_error_phase_for_current_phase();
123
+ if (error_phase) {
124
+ this.#transition_to_error_phase(error_phase, error_json);
125
+ }
126
+ else {
127
+ this.#fail(error_json);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * Execute handler synchronously (only for sync local_call actions).
134
+ */
135
+ handle_sync() {
136
+ if (this.spec.kind !== 'local_call' || this.spec.async) {
137
+ throw new Error('handle_sync can only be used with synchronous local_call actions');
138
+ }
139
+ if (this.#data.step === 'failed') {
140
+ return; // already failed, no-op
141
+ }
142
+ if (this.#data.step !== 'parsed') {
143
+ throw new Error(`cannot handle from step '${this.#data.step}' - must be 'parsed'`);
144
+ }
145
+ this.#transition_step('handling', this.#create_handling_updates());
146
+ const handler = this.environment.lookup_action_handler(this.spec.method, this.#data.phase);
147
+ if (!handler) {
148
+ this.#transition_step('handled');
149
+ return;
150
+ }
151
+ try {
152
+ const result = handler(this);
153
+ this.#complete_handling(result);
154
+ }
155
+ catch (error) {
156
+ // Preserve ThrownJsonrpcError structure, wrap others as internal_error
157
+ const error_json = error instanceof ThrownJsonrpcError
158
+ ? { code: error.code, message: error.message, data: error.data }
159
+ : jsonrpc_error_messages.internal_error('unknown error');
160
+ this.#fail(error_json);
161
+ }
162
+ }
163
+ /**
164
+ * Transition to a new phase.
165
+ */
166
+ transition(phase) {
167
+ if (this.#data.step === 'failed') {
168
+ return; // already failed, no-op
169
+ }
170
+ if (this.#data.step !== 'handled') {
171
+ throw new Error(`cannot transition from step '${this.#data.step}' - must be 'handled'`);
172
+ }
173
+ validate_phase_transition(this.#data.phase, phase);
174
+ // Create new data for the phase
175
+ const new_data = this.#create_phase_data(phase);
176
+ this.set_data(new_data);
177
+ }
178
+ is_complete() {
179
+ return is_action_complete(this.#data);
180
+ }
181
+ update_progress(progress) {
182
+ this.#update_data({ progress });
183
+ }
184
+ set_request(request) {
185
+ this.#validate_protocol_setter('request', {
186
+ kind: 'request_response',
187
+ phase: 'receive_request',
188
+ });
189
+ this.#update_data({ request });
190
+ }
191
+ set_response(response) {
192
+ this.#validate_protocol_setter('response', {
193
+ kind: 'request_response',
194
+ phase: 'receive_response',
195
+ });
196
+ const output = 'result' in response ? response.result : null;
197
+ this.#update_data({ response, output });
198
+ }
199
+ set_notification(notification) {
200
+ this.#validate_protocol_setter('notification', {
201
+ kind: 'remote_notification',
202
+ phase: 'receive',
203
+ });
204
+ this.#update_data({ notification });
205
+ }
206
+ #transition_step(step, updates) {
207
+ validate_step_transition(this.#data.step, step);
208
+ this.#update_data({ ...updates, step });
209
+ }
210
+ /** Shallowly merge `updates` with the current data immutably. */
211
+ #update_data(updates) {
212
+ const new_data = { ...this.#data, ...updates };
213
+ this.set_data(new_data);
214
+ }
215
+ // TODO usage of this in this module is silently swallowing errors, maybe log on the environment?
216
+ #fail(error) {
217
+ this.#transition_step('failed', { error });
218
+ }
219
+ /**
220
+ * Determine which error phase to transition to based on current phase.
221
+ */
222
+ #get_error_phase_for_current_phase() {
223
+ if (this.#data.kind !== 'request_response')
224
+ return null;
225
+ switch (this.#data.phase) {
226
+ case 'send_request':
227
+ case 'receive_request':
228
+ return 'send_error';
229
+ case 'receive_response':
230
+ return 'receive_error';
231
+ default:
232
+ return null;
233
+ }
234
+ }
235
+ /**
236
+ * Transition to an error phase instead of failing.
237
+ */
238
+ #transition_to_error_phase(phase, error) {
239
+ const new_data = {
240
+ ...this.#data,
241
+ phase,
242
+ step: 'parsed',
243
+ error,
244
+ output: null,
245
+ };
246
+ this.set_data(new_data);
247
+ }
248
+ #validate_protocol_setter(field, requirements) {
249
+ if (this.#data.kind !== requirements.kind || this.#data.phase !== requirements.phase) {
250
+ throw new Error(`can only set ${field} in ${requirements.phase} phase`);
251
+ }
252
+ if (this.#data.step !== 'initial') {
253
+ throw new Error(`can only set ${field} at initial step`);
254
+ }
255
+ }
256
+ #create_handling_updates() {
257
+ // Create protocol messages when transitioning to 'handling' step
258
+ // We check for 'parsed' state since this method is called before the transition
259
+ if (is_send_request_with_parsed_input(this.#data)) {
260
+ return {
261
+ request: create_jsonrpc_request(this.spec.method, to_jsonrpc_params(this.#data.input), create_uuid()),
262
+ };
263
+ }
264
+ if (is_notification_send_with_parsed_input(this.#data)) {
265
+ return {
266
+ notification: create_jsonrpc_notification(this.spec.method, to_jsonrpc_params(this.#data.input)),
267
+ };
268
+ }
269
+ return undefined;
270
+ }
271
+ #complete_handling(output) {
272
+ if (output !== undefined && should_validate_output(this.spec.kind, this.#data.phase)) {
273
+ const parsed = this.spec.output.safeParse(output);
274
+ if (parsed.success) {
275
+ this.#transition_step('handled', { output: parsed.data });
276
+ }
277
+ else {
278
+ this.#fail(jsonrpc_error_messages.validation_error(`failed to parse output: ${format_zod_validation_error(parsed.error)}`, { output, validation_errors: parsed.error.issues }));
279
+ }
280
+ }
281
+ else {
282
+ this.#transition_step('handled');
283
+ }
284
+ }
285
+ #create_phase_data(phase) {
286
+ const base_data = create_initial_data(this.#data.kind, phase, this.#data.method, this.#data.executor, this.#data.input);
287
+ // Carry forward data based on transition
288
+ if (is_request_response(this.#data)) {
289
+ if (phase === 'receive_response' && this.#data.request) {
290
+ // Carry forward the request when transitioning to receive_response
291
+ return { ...base_data, request: this.#data.request };
292
+ }
293
+ else if (phase === 'send_response' && this.#data.request) {
294
+ // Create the response when transitioning to send_response
295
+ const response = this.#create_response_from_data();
296
+ return {
297
+ ...base_data,
298
+ output: this.#data.output,
299
+ request: this.#data.request,
300
+ response,
301
+ };
302
+ }
303
+ else if (phase === 'send_error' && this.#data.error) {
304
+ // Carry forward error and request (if available) when transitioning to send_error
305
+ return {
306
+ ...base_data,
307
+ error: this.#data.error,
308
+ request: this.#data.request || null,
309
+ };
310
+ }
311
+ else if (phase === 'receive_error' && this.#data.error) {
312
+ // Carry forward error, request, and response when transitioning to receive_error
313
+ return {
314
+ ...base_data,
315
+ error: this.#data.error,
316
+ request: this.#data.request,
317
+ response: this.#data.response,
318
+ };
319
+ }
320
+ }
321
+ return base_data;
322
+ }
323
+ #create_response_from_data() {
324
+ if (!is_request_response(this.#data) || !this.#data.request) {
325
+ throw new Error('cannot create response without request');
326
+ }
327
+ if (this.#data.error) {
328
+ return create_jsonrpc_error_response(this.#data.request.id, this.#data.error);
329
+ }
330
+ const result = to_jsonrpc_result(this.#data.output);
331
+ return create_jsonrpc_response(this.#data.request.id, result);
332
+ }
333
+ }
334
+ // TODO not sure about this helper's design/location (should it be internal to the class constructor? a static method?)
335
+ /**
336
+ * Create an action event from a spec and initial input.
337
+ */
338
+ export const create_action_event = (environment, spec, input, initial_phase) => {
339
+ const phase = initial_phase || get_initial_phase(spec.kind, spec.initiator, environment.executor);
340
+ if (!phase) {
341
+ throw new Error(`executor '${environment.executor}' cannot initiate action '${spec.method}' with initiator '${spec.initiator}'`);
342
+ }
343
+ const initial_data = create_initial_data(spec.kind, phase, spec.method, environment.executor, input);
344
+ return new ActionEvent(environment, spec, initial_data);
345
+ };
346
+ /**
347
+ * Reconstruct an action event from serialized JSON data.
348
+ */
349
+ export const create_action_event_from_json = (json, environment) => {
350
+ const spec = environment.lookup_action_spec(json.method);
351
+ if (!spec) {
352
+ throw new Error(`no spec found for method '${json.method}'`);
353
+ }
354
+ return new ActionEvent(environment, spec, json);
355
+ };
356
+ // TODO this and the above one arent used atm, see the comment on `create_action_event` too
357
+ // TODO how to avoid casting? this should generally be safe but we dont have schemas for each possible action event state
358
+ export const parse_action_event = (raw_json, environment) => {
359
+ const json = ActionEventData.parse(raw_json);
360
+ return create_action_event_from_json(json, environment);
361
+ };