@pikku/core 0.11.1 → 0.11.2

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 (143) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/errors/error-handler.js +3 -3
  3. package/dist/function/function-runner.d.ts +4 -3
  4. package/dist/function/function-runner.js +60 -14
  5. package/dist/function/functions.types.d.ts +84 -5
  6. package/dist/function/functions.types.js +41 -1
  7. package/dist/index.d.ts +6 -2
  8. package/dist/index.js +5 -2
  9. package/dist/middleware/auth-apikey.d.ts +1 -1
  10. package/dist/middleware/auth-bearer.d.ts +1 -1
  11. package/dist/middleware/auth-cookie.d.ts +1 -1
  12. package/dist/middleware/timeout.d.ts +1 -1
  13. package/dist/middleware-runner.d.ts +3 -14
  14. package/dist/middleware-runner.js +8 -8
  15. package/dist/permissions.d.ts +3 -86
  16. package/dist/permissions.js +11 -67
  17. package/dist/pikku-state.d.ts +32 -104
  18. package/dist/pikku-state.js +140 -66
  19. package/dist/schema.d.ts +8 -6
  20. package/dist/schema.js +25 -13
  21. package/dist/services/workflow-service.d.ts +38 -0
  22. package/dist/services/workflow-service.js +1 -0
  23. package/dist/types/core.types.d.ts +12 -4
  24. package/dist/types/core.types.js +1 -1
  25. package/dist/types/state.types.d.ts +135 -0
  26. package/dist/types/state.types.js +1 -0
  27. package/dist/utils.d.ts +8 -0
  28. package/dist/utils.js +45 -0
  29. package/dist/wirings/channel/channel-handler.js +1 -1
  30. package/dist/wirings/channel/channel-runner.js +5 -5
  31. package/dist/wirings/channel/log-channels.js +1 -1
  32. package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -2
  33. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
  34. package/dist/wirings/cli/cli-runner.js +10 -10
  35. package/dist/wirings/cli/cli.types.d.ts +1 -0
  36. package/dist/wirings/forge-node/forge-node.types.d.ts +120 -0
  37. package/dist/wirings/forge-node/forge-node.types.js +38 -0
  38. package/dist/wirings/forge-node/index.d.ts +1 -0
  39. package/dist/wirings/forge-node/index.js +1 -0
  40. package/dist/wirings/http/http-runner.d.ts +2 -2
  41. package/dist/wirings/http/http-runner.js +21 -12
  42. package/dist/wirings/http/http.types.d.ts +14 -1
  43. package/dist/wirings/http/log-http-routes.js +1 -1
  44. package/dist/wirings/http/routers/path-to-regex.js +2 -2
  45. package/dist/wirings/mcp/mcp-runner.js +21 -21
  46. package/dist/wirings/queue/queue-runner.js +6 -6
  47. package/dist/wirings/rpc/index.d.ts +1 -1
  48. package/dist/wirings/rpc/index.js +1 -1
  49. package/dist/wirings/rpc/rpc-runner.d.ts +7 -8
  50. package/dist/wirings/rpc/rpc-runner.js +62 -14
  51. package/dist/wirings/rpc/rpc-types.d.ts +7 -0
  52. package/dist/wirings/scheduler/log-schedulers.js +1 -1
  53. package/dist/wirings/scheduler/scheduler-runner.js +5 -5
  54. package/dist/wirings/trigger/index.d.ts +2 -0
  55. package/dist/wirings/trigger/index.js +2 -0
  56. package/dist/wirings/trigger/trigger-runner.d.ts +29 -0
  57. package/dist/wirings/trigger/trigger-runner.js +57 -0
  58. package/dist/wirings/trigger/trigger.types.d.ts +42 -0
  59. package/dist/wirings/trigger/trigger.types.js +1 -0
  60. package/dist/wirings/workflow/dsl/index.d.ts +5 -0
  61. package/dist/wirings/workflow/dsl/index.js +4 -0
  62. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +286 -0
  63. package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
  64. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
  65. package/dist/wirings/workflow/dsl/workflow-runner.js +22 -0
  66. package/dist/wirings/workflow/graph/graph-node.d.ts +122 -0
  67. package/dist/wirings/workflow/graph/graph-node.js +58 -0
  68. package/dist/wirings/workflow/graph/graph-runner.d.ts +35 -0
  69. package/dist/wirings/workflow/graph/graph-runner.js +452 -0
  70. package/dist/wirings/workflow/graph/index.d.ts +3 -0
  71. package/dist/wirings/workflow/graph/index.js +3 -0
  72. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +95 -0
  73. package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
  74. package/dist/wirings/workflow/index.d.ts +7 -12
  75. package/dist/wirings/workflow/index.js +6 -11
  76. package/dist/wirings/workflow/pikku-workflow-service.d.ts +96 -6
  77. package/dist/wirings/workflow/pikku-workflow-service.js +230 -103
  78. package/dist/wirings/workflow/wire-workflow.d.ts +42 -0
  79. package/dist/wirings/workflow/wire-workflow.js +53 -0
  80. package/dist/wirings/workflow/workflow-utils.d.ts +23 -0
  81. package/dist/wirings/workflow/workflow-utils.js +66 -0
  82. package/dist/wirings/workflow/workflow.types.d.ts +133 -211
  83. package/package.json +10 -1
  84. package/src/errors/error-handler.ts +3 -3
  85. package/src/function/function-runner.test.ts +1 -1
  86. package/src/function/function-runner.ts +86 -16
  87. package/src/function/functions.types.ts +126 -4
  88. package/src/index.ts +10 -5
  89. package/src/middleware-runner.ts +16 -9
  90. package/src/permissions.test.ts +28 -86
  91. package/src/permissions.ts +21 -80
  92. package/src/pikku-state.ts +156 -201
  93. package/src/schema.ts +42 -13
  94. package/src/services/workflow-service.ts +74 -0
  95. package/src/types/core.types.ts +12 -3
  96. package/src/types/state.types.ts +195 -0
  97. package/src/utils.ts +55 -0
  98. package/src/wirings/channel/channel-handler.ts +1 -1
  99. package/src/wirings/channel/channel-runner.ts +5 -5
  100. package/src/wirings/channel/local/local-channel-runner.test.ts +3 -1
  101. package/src/wirings/channel/log-channels.ts +1 -1
  102. package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -2
  103. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
  104. package/src/wirings/cli/cli-runner.test.ts +19 -19
  105. package/src/wirings/cli/cli-runner.ts +10 -9
  106. package/src/wirings/cli/cli.types.ts +1 -0
  107. package/src/wirings/forge-node/forge-node.types.ts +135 -0
  108. package/src/wirings/forge-node/index.ts +1 -0
  109. package/src/wirings/http/http-runner.test.ts +2 -2
  110. package/src/wirings/http/http-runner.ts +30 -13
  111. package/src/wirings/http/http.types.ts +14 -0
  112. package/src/wirings/http/log-http-routes.ts +1 -1
  113. package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
  114. package/src/wirings/http/routers/path-to-regex.ts +2 -2
  115. package/src/wirings/mcp/mcp-runner.ts +21 -21
  116. package/src/wirings/queue/queue-runner.test.ts +19 -19
  117. package/src/wirings/queue/queue-runner.ts +6 -6
  118. package/src/wirings/rpc/index.ts +1 -1
  119. package/src/wirings/rpc/rpc-runner.ts +88 -22
  120. package/src/wirings/rpc/rpc-types.ts +8 -0
  121. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  122. package/src/wirings/scheduler/scheduler-runner.test.ts +27 -27
  123. package/src/wirings/scheduler/scheduler-runner.ts +5 -5
  124. package/src/wirings/trigger/index.ts +2 -0
  125. package/src/wirings/trigger/trigger-runner.ts +96 -0
  126. package/src/wirings/trigger/trigger.types.ts +56 -0
  127. package/src/wirings/workflow/dsl/index.ts +30 -0
  128. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +312 -0
  129. package/src/wirings/workflow/dsl/workflow-runner.ts +27 -0
  130. package/src/wirings/workflow/graph/graph-node.ts +227 -0
  131. package/src/wirings/workflow/graph/graph-runner.ts +694 -0
  132. package/src/wirings/workflow/graph/index.ts +3 -0
  133. package/src/wirings/workflow/graph/workflow-graph.types.ts +126 -0
  134. package/src/wirings/workflow/index.ts +53 -26
  135. package/src/wirings/workflow/pikku-workflow-service.ts +333 -117
  136. package/src/wirings/workflow/wire-workflow.ts +94 -0
  137. package/src/wirings/workflow/workflow-utils.ts +120 -0
  138. package/src/wirings/workflow/workflow.types.ts +166 -277
  139. package/tsconfig.tsbuildinfo +1 -1
  140. package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
  141. package/dist/wirings/workflow/workflow-runner.js +0 -65
  142. package/src/function/function-runner.ts.bak +0 -188
  143. package/src/wirings/workflow/workflow-runner.ts +0 -72
@@ -0,0 +1,312 @@
1
+ /**
2
+ * DSL (Domain Specific Language) workflow types
3
+ * These types define the step-based workflow format extracted by the inspector
4
+ */
5
+
6
+ import type { WorkflowRun } from '../workflow.types.js'
7
+
8
+ /**
9
+ * Workflow step options
10
+ */
11
+ export interface WorkflowStepOptions {
12
+ /** Display name for logs/UI (optional, doesn't affect execution) */
13
+ description?: string
14
+ /** Number of retry attempts for failed steps (only applies to local execution) */
15
+ retries?: number
16
+ /** Delay between retry attempts (e.g., '1s', '2s', '2min') */
17
+ retryDelay?: string | number
18
+ // Future: timeout, failFast, priority
19
+ }
20
+
21
+ /**
22
+ * Type signature for workflow.do() RPC form - used by inspector
23
+ */
24
+ export type WorkflowWireDoRPC = <TOutput = any, TInput = any>(
25
+ stepName: string,
26
+ rpcName: string,
27
+ data: TInput,
28
+ options?: WorkflowStepOptions
29
+ ) => Promise<TOutput>
30
+
31
+ /**
32
+ * Type signature for workflow.do() inline form - used by inspector
33
+ */
34
+ export type WorkflowWireDoInline = <T>(
35
+ stepName: string,
36
+ fn: () => Promise<T> | T,
37
+ options?: WorkflowStepOptions
38
+ ) => Promise<T>
39
+
40
+ /**
41
+ * Type signature for workflow.sleep() - used by inspector
42
+ */
43
+ export type WorkflowWireSleep = (
44
+ stepName: string,
45
+ duration: string
46
+ ) => Promise<void>
47
+
48
+ /**
49
+ * Input source for step arguments in DSL workflows
50
+ */
51
+ export type InputSource =
52
+ | { from: 'input'; path: string }
53
+ | { from: 'outputVar'; name: string; path?: string }
54
+ | { from: 'item'; path: string }
55
+ | { from: 'literal'; value: unknown }
56
+ | { from: 'template'; parts: string[]; expressions: InputSource[] }
57
+
58
+ /**
59
+ * Output binding for return statements in DSL workflows
60
+ */
61
+ export type OutputBinding =
62
+ | { from: 'outputVar'; name: string; path?: string }
63
+ | { from: 'stateVar'; name: string; path?: string }
64
+ | { from: 'input'; path: string }
65
+ | { from: 'literal'; value: unknown }
66
+ | { from: 'expression'; expression: string }
67
+
68
+ /**
69
+ * RPC step metadata (base form)
70
+ */
71
+ export interface RpcStepMeta {
72
+ /** RPC form - generates queue worker */
73
+ type: 'rpc'
74
+ /** Cache key (stepName from workflow.do) */
75
+ stepName: string
76
+ /** RPC to invoke */
77
+ rpcName: string
78
+ /** Output variable name (if assigned) */
79
+ outputVar?: string
80
+ /** Input source mappings, or 'passthrough' when entire data is passed */
81
+ inputs?: Record<string, InputSource> | 'passthrough'
82
+ /** Step options */
83
+ options?: WorkflowStepOptions
84
+ }
85
+
86
+ /**
87
+ * Simple condition expression (leaf node)
88
+ */
89
+ export interface SimpleCondition {
90
+ type: 'simple'
91
+ expression: string
92
+ }
93
+
94
+ /**
95
+ * Nested condition structure supporting AND/OR operations
96
+ */
97
+ export type Condition =
98
+ | SimpleCondition
99
+ | { type: 'and'; conditions: Condition[] }
100
+ | { type: 'or'; conditions: Condition[] }
101
+
102
+ /**
103
+ * A single branch in an if/else-if chain
104
+ */
105
+ export interface BranchCase {
106
+ /** Condition for this branch */
107
+ condition: Condition
108
+ /** Steps to execute when condition is true */
109
+ steps: WorkflowStepMeta[]
110
+ }
111
+
112
+ /**
113
+ * Branch step metadata (if/else-if/else control flow)
114
+ */
115
+ export interface BranchStepMeta {
116
+ type: 'branch'
117
+ /** Branches in order: first is "if", rest are "else if" */
118
+ branches: BranchCase[]
119
+ /** Else branch steps (when no conditions match) */
120
+ elseSteps?: WorkflowStepMeta[]
121
+ }
122
+
123
+ /**
124
+ * Parallel group step metadata (Promise.all with multiple steps)
125
+ */
126
+ export interface ParallelGroupStepMeta {
127
+ type: 'parallel'
128
+ /** Child steps to execute in parallel */
129
+ children: RpcStepMeta[]
130
+ }
131
+
132
+ /**
133
+ * Fanout step metadata (parallel or sequential iteration)
134
+ */
135
+ export interface FanoutStepMeta {
136
+ type: 'fanout'
137
+ /** Step name for this fanout */
138
+ stepName: string
139
+ /** Source array variable name */
140
+ sourceVar: string
141
+ /** Iterator variable name */
142
+ itemVar: string
143
+ /** Execution mode */
144
+ mode: 'parallel' | 'sequential'
145
+ /** Child step to execute per iteration */
146
+ child: RpcStepMeta
147
+ /** Time between iterations (sequential mode only) */
148
+ timeBetween?: string
149
+ }
150
+
151
+ /**
152
+ * Return step metadata (workflow output)
153
+ */
154
+ export interface ReturnStepMeta {
155
+ type: 'return'
156
+ /** Output bindings */
157
+ outputs: Record<string, OutputBinding>
158
+ }
159
+
160
+ /**
161
+ * Inline step metadata (legacy support)
162
+ */
163
+ export interface InlineStepMeta {
164
+ /** Inline form - local execution */
165
+ type: 'inline'
166
+ /** Cache key (stepName from workflow.do) */
167
+ stepName: string
168
+ /** Display name */
169
+ description?: string
170
+ /** Step options */
171
+ options?: WorkflowStepOptions
172
+ }
173
+
174
+ /**
175
+ * Sleep step metadata
176
+ */
177
+ export interface SleepStepMeta {
178
+ /** Sleep step */
179
+ type: 'sleep'
180
+ /** Cache key (stepName from workflow.sleep) */
181
+ stepName: string
182
+ /** Sleep duration */
183
+ duration: string | number
184
+ }
185
+
186
+ /**
187
+ * Cancel step metadata
188
+ */
189
+ export interface CancelStepMeta {
190
+ /** Cancel step */
191
+ type: 'cancel'
192
+ /** Optional cancellation reason */
193
+ reason?: string
194
+ }
195
+
196
+ /**
197
+ * Set step metadata (context variable assignment)
198
+ */
199
+ export interface SetStepMeta {
200
+ /** Set step */
201
+ type: 'set'
202
+ /** Variable name to set (must be in context) */
203
+ variable: string
204
+ /** Value to assign (literal or expression) */
205
+ value: unknown
206
+ }
207
+
208
+ /**
209
+ * Switch case metadata
210
+ */
211
+ export interface SwitchCaseMeta {
212
+ /** Case value (literal) or expression */
213
+ value?: string | number | boolean | null
214
+ /** Case expression (for complex cases) */
215
+ expression?: string
216
+ /** Steps to execute for this case */
217
+ steps: WorkflowStepMeta[]
218
+ }
219
+
220
+ /**
221
+ * Switch step metadata (switch/case control flow)
222
+ */
223
+ export interface SwitchStepMeta {
224
+ type: 'switch'
225
+ /** Expression being switched on */
226
+ expression: string
227
+ /** Case branches */
228
+ cases: SwitchCaseMeta[]
229
+ /** Default case steps (optional) */
230
+ defaultSteps?: WorkflowStepMeta[]
231
+ }
232
+
233
+ /**
234
+ * Filter step metadata (array.filter)
235
+ */
236
+ export interface FilterStepMeta {
237
+ type: 'filter'
238
+ /** Source array variable name */
239
+ sourceVar: string
240
+ /** Iterator variable name */
241
+ itemVar: string
242
+ /** Filter condition */
243
+ condition: Condition
244
+ /** Output variable name (if assigned) */
245
+ outputVar?: string
246
+ }
247
+
248
+ /**
249
+ * Array predicate step metadata (array.some, array.every)
250
+ */
251
+ export interface ArrayPredicateStepMeta {
252
+ type: 'arrayPredicate'
253
+ /** Predicate mode */
254
+ mode: 'some' | 'every'
255
+ /** Source array variable name */
256
+ sourceVar: string
257
+ /** Iterator variable name */
258
+ itemVar: string
259
+ /** Predicate condition */
260
+ condition: Condition
261
+ /** Output variable name (if assigned) */
262
+ outputVar?: string
263
+ }
264
+
265
+ /**
266
+ * Workflow step metadata (extracted by inspector)
267
+ */
268
+ export type WorkflowStepMeta =
269
+ | RpcStepMeta
270
+ | BranchStepMeta
271
+ | ParallelGroupStepMeta
272
+ | FanoutStepMeta
273
+ | ReturnStepMeta
274
+ | InlineStepMeta
275
+ | SleepStepMeta
276
+ | CancelStepMeta
277
+ | SwitchStepMeta
278
+ | FilterStepMeta
279
+ | ArrayPredicateStepMeta
280
+ | SetStepMeta
281
+
282
+ /**
283
+ * Workflow step wire context for RPC functions
284
+ * Provides step-level metadata including retry attempt tracking
285
+ */
286
+ export interface WorkflowStepWire {
287
+ /** The workflow run ID */
288
+ runId: string
289
+ /** The unique step ID */
290
+ stepId: string
291
+ /** Current attempt number (1-indexed, increments on retry) */
292
+ attemptCount: number
293
+ }
294
+
295
+ /**
296
+ * Workflow wire object for DSL workflows
297
+ * Provides workflow-specific capabilities to function execution
298
+ */
299
+ export interface PikkuWorkflowWire {
300
+ /** The workflow name */
301
+ workflowName: string
302
+ /** The current run ID */
303
+ runId: string
304
+ /** Get the current workflow run */
305
+ getRun: () => Promise<WorkflowRun>
306
+
307
+ /** Execute a workflow step (overloaded - RPC or inline form) */
308
+ do: WorkflowWireDoRPC & WorkflowWireDoInline
309
+
310
+ /** Sleep for a duration */
311
+ sleep: WorkflowWireSleep
312
+ }
@@ -0,0 +1,27 @@
1
+ import { pikkuState } from '../../../pikku-state.js'
2
+ import { addFunction } from '../../../function/function-runner.js'
3
+
4
+ /**
5
+ * Add a workflow to the system
6
+ * This is called by the generated workflow wirings
7
+ */
8
+ export const addWorkflow = (workflowName: string, workflowFunc: any) => {
9
+ // Get workflow metadata from inspector
10
+ const meta = pikkuState(null, 'workflows', 'meta')
11
+ const workflowMeta = meta[workflowName]
12
+ if (!workflowMeta) {
13
+ throw new Error(
14
+ `Workflow metadata not found for '${workflowName}'. Make sure to run the CLI to generate metadata.`
15
+ )
16
+ }
17
+
18
+ // Store workflow definition in state
19
+ const registrations = pikkuState(null, 'workflows', 'registrations')
20
+ registrations.set(workflowName, {
21
+ name: workflowName,
22
+ func: workflowFunc,
23
+ })
24
+
25
+ // Register the function with pikku
26
+ addFunction(workflowMeta.pikkuFuncName, workflowFunc)
27
+ }
@@ -0,0 +1,227 @@
1
+ import type {
2
+ GraphNodeConfig,
3
+ NextConfig,
4
+ RefValue,
5
+ } from './workflow-graph.types.js'
6
+
7
+ /**
8
+ * Standard RPC handler interface (matches generated FlattenedRPCMap entries)
9
+ */
10
+ export interface RPCHandler<I = any, O = any> {
11
+ input: I
12
+ output: O
13
+ }
14
+
15
+ /**
16
+ * Compute output types for all nodes based on their func (RPC name)
17
+ */
18
+ export type NodeOutputMap<
19
+ Nodes extends Record<string, { func: string }>,
20
+ RPCMap extends Record<string, RPCHandler>,
21
+ > = {
22
+ [K in keyof Nodes]: Nodes[K]['func'] extends keyof RPCMap
23
+ ? RPCMap[Nodes[K]['func']]['output']
24
+ : unknown
25
+ }
26
+
27
+ /**
28
+ * Node definition with RPC name reference
29
+ */
30
+ export interface GraphNodeDef<
31
+ RPCMap extends Record<string, RPCHandler>,
32
+ NodeIds extends string = string,
33
+ NodeOutputs extends Record<string, any> = Record<string, any>,
34
+ > {
35
+ /** RPC function name (must be a key in RPCMap) */
36
+ func: keyof RPCMap & string
37
+ /** Input mapping - reference outputs from other nodes with autocomplete */
38
+ input?: (
39
+ ref: <N extends keyof NodeOutputs & string>(
40
+ nodeId: N,
41
+ path: keyof NodeOutputs[N] & string
42
+ ) => RefValue
43
+ ) => Record<string, unknown>
44
+ /** Next node(s) to execute */
45
+ next?: NextConfig<NodeIds>
46
+ /** Error handling - node(s) to execute on error */
47
+ onError?: NodeIds | NodeIds[]
48
+ }
49
+
50
+ /**
51
+ * Helper to create a type-safe graph definition with RPC autocomplete.
52
+ * Pass node IDs as first type parameter for type-safe next/ref/output key completion.
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // Import the typed graph from generated types
57
+ * import { pikkuWorkflowGraph, wireWorkflow } from './.pikku/workflow/pikku-workflow-types.gen.js'
58
+ *
59
+ * export const myGraph = pikkuWorkflowGraph({
60
+ * name: 'myWorkflow',
61
+ * nodes: {
62
+ * entry: 'createUserProfile', // autocompletes RPC names
63
+ * sendWelcome: 'sendEmail',
64
+ * },
65
+ * config: {
66
+ * entry: { next: 'sendWelcome' },
67
+ * sendWelcome: {
68
+ * input: (ref) => ({
69
+ * to: ref('entry', 'email'), // 'entry' and 'email' both autocomplete
70
+ * }),
71
+ * },
72
+ * },
73
+ * })
74
+ *
75
+ * wireWorkflow({
76
+ * wires: { http: { route: '/start', method: 'post' } },
77
+ * graph: myGraph,
78
+ * })
79
+ * ```
80
+ */
81
+ /**
82
+ * Type to compute output types for all nodes based on their func mapping
83
+ */
84
+ type ComputeNodeOutputs<
85
+ FuncMap extends Record<string, string>,
86
+ RPCMap extends Record<string, RPCHandler>,
87
+ > = {
88
+ [K in keyof FuncMap]: FuncMap[K] extends keyof RPCMap
89
+ ? RPCMap[FuncMap[K]]['output']
90
+ : unknown
91
+ }
92
+
93
+ /**
94
+ * Type to compute input types for all nodes based on their func mapping
95
+ */
96
+ type ComputeNodeInputs<
97
+ FuncMap extends Record<string, string>,
98
+ RPCMap extends Record<string, RPCHandler>,
99
+ > = {
100
+ [K in keyof FuncMap]: FuncMap[K] extends keyof RPCMap
101
+ ? RPCMap[FuncMap[K]]['input']
102
+ : unknown
103
+ }
104
+
105
+ /**
106
+ * Typed ref value - carries the type of the referenced field as a phantom type.
107
+ * At runtime this is just RefValue, but TypeScript tracks the type T.
108
+ */
109
+ export type TypedRef<T> = RefValue & { __phantomType?: T }
110
+
111
+ /**
112
+ * Map input type fields to allow TypedRef of matching type as an alternative
113
+ */
114
+ type InputWithRefs<T> = {
115
+ [K in keyof T]: T[K] | TypedRef<T[K]>
116
+ }
117
+
118
+ export function createGraph<RPCMap extends Record<string, RPCHandler>>() {
119
+ return <const FuncMap extends Record<string, keyof RPCMap & string>>(
120
+ funcMap: FuncMap,
121
+ nodesOrBuilder?:
122
+ | GraphNodeConfigMap<FuncMap, RPCMap>
123
+ | ((nodes: FuncMap) => GraphNodeConfigMap<FuncMap, RPCMap>)
124
+ ): Record<
125
+ Extract<keyof FuncMap, string>,
126
+ GraphNodeConfig<Extract<keyof FuncMap, string>>
127
+ > => {
128
+ type NodeIds = Extract<keyof FuncMap, string>
129
+
130
+ // If no nodes provided, return just the funcMap converted to graph nodes
131
+ if (!nodesOrBuilder) {
132
+ const result: Record<string, GraphNodeConfig<string>> = {}
133
+ for (const [nodeId, rpcName] of Object.entries(funcMap)) {
134
+ result[nodeId] = {
135
+ func: rpcName as string,
136
+ }
137
+ }
138
+ return result as Record<NodeIds, GraphNodeConfig<NodeIds>>
139
+ }
140
+
141
+ const nodes =
142
+ typeof nodesOrBuilder === 'function'
143
+ ? nodesOrBuilder(funcMap)
144
+ : nodesOrBuilder
145
+
146
+ const result: Record<string, GraphNodeConfig<string>> = {}
147
+
148
+ for (const [nodeId, def] of Object.entries(nodes) as [string, any][]) {
149
+ result[nodeId] = {
150
+ func: funcMap[nodeId] as string,
151
+ input: def?.input as any,
152
+ next: def?.next,
153
+ onError: def?.onError,
154
+ }
155
+ }
156
+
157
+ return result as Record<NodeIds, GraphNodeConfig<NodeIds>>
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Type helper for node configuration map
163
+ */
164
+ type GraphNodeConfigMap<
165
+ FuncMap extends Record<string, string>,
166
+ RPCMap extends Record<string, RPCHandler>,
167
+ > = {
168
+ [K in Extract<keyof FuncMap, string>]?: {
169
+ next?: NextConfig<Extract<keyof FuncMap, string>>
170
+ input?: (
171
+ ref: <
172
+ N extends Extract<keyof FuncMap, string>,
173
+ P extends keyof ComputeNodeOutputs<FuncMap, RPCMap>[N] & string,
174
+ >(
175
+ nodeId: N,
176
+ path: P
177
+ ) => TypedRef<ComputeNodeOutputs<FuncMap, RPCMap>[N][P]>
178
+ ) => InputWithRefs<ComputeNodeInputs<FuncMap, RPCMap>[K]>
179
+ onError?: Extract<keyof FuncMap, string> | Extract<keyof FuncMap, string>[]
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Untyped graph for use without RPC map.
185
+ * Pass node IDs as type parameter for type-safe next/ref, or omit for untyped usage.
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * // Typed usage - next and ref are type-checked
190
+ * graph<'entry' | 'sendWelcome'>({
191
+ * entry: { func: 'createUserProfile', next: 'sendWelcome' },
192
+ * sendWelcome: { func: 'sendEmail', input: (ref) => ({ to: ref('entry', 'email') }) },
193
+ * })
194
+ *
195
+ * // Untyped usage - no type checking on next/ref
196
+ * graph({
197
+ * entry: { func: 'createUserProfile', next: 'sendWelcome' },
198
+ * sendWelcome: { func: 'sendEmail' },
199
+ * })
200
+ * ```
201
+ */
202
+ export function graph<NodeIds extends string = string>(
203
+ nodes: Record<
204
+ NodeIds,
205
+ {
206
+ func: string
207
+ next?: NextConfig<NodeIds>
208
+ input?: (
209
+ ref: (nodeId: NodeIds, path: string) => RefValue
210
+ ) => Record<string, unknown>
211
+ onError?: NodeIds | NodeIds[]
212
+ }
213
+ >
214
+ ): Record<NodeIds, GraphNodeConfig<NodeIds>> {
215
+ const result: Record<string, GraphNodeConfig<string>> = {}
216
+
217
+ for (const [nodeId, def] of Object.entries(nodes) as [string, any][]) {
218
+ result[nodeId] = {
219
+ func: def.func,
220
+ input: def.input as any,
221
+ next: def.next,
222
+ onError: def.onError,
223
+ }
224
+ }
225
+
226
+ return result as Record<NodeIds, GraphNodeConfig<NodeIds>>
227
+ }