@pikku/inspector 0.11.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.
Files changed (189) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/OPTIMIZATION-PLAN.md +195 -0
  3. package/dist/add/add-ai-agent.d.ts +2 -0
  4. package/dist/add/add-ai-agent.js +314 -0
  5. package/dist/add/add-channel.js +69 -61
  6. package/dist/add/add-cli.js +36 -18
  7. package/dist/add/add-file-with-factory.js +2 -0
  8. package/dist/add/add-functions.js +327 -59
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +153 -44
  11. package/dist/add/add-http-routes.d.ts +5 -0
  12. package/dist/add/add-http-routes.js +159 -0
  13. package/dist/add/add-keyed-wiring.d.ts +12 -0
  14. package/dist/add/add-keyed-wiring.js +97 -0
  15. package/dist/add/add-mcp-prompt.js +14 -9
  16. package/dist/add/add-mcp-resource.js +14 -9
  17. package/dist/add/add-middleware.d.ts +1 -4
  18. package/dist/add/add-middleware.js +364 -79
  19. package/dist/add/add-permission.d.ts +1 -1
  20. package/dist/add/add-permission.js +152 -40
  21. package/dist/add/add-queue-worker.js +18 -12
  22. package/dist/add/add-rpc-invocations.d.ts +3 -0
  23. package/dist/add/add-rpc-invocations.js +65 -25
  24. package/dist/add/add-schedule.js +11 -5
  25. package/dist/add/add-secret.d.ts +3 -0
  26. package/dist/add/add-secret.js +82 -0
  27. package/dist/add/add-trigger.d.ts +2 -0
  28. package/dist/add/add-trigger.js +87 -0
  29. package/dist/add/add-variable.d.ts +1 -0
  30. package/dist/add/add-variable.js +8 -0
  31. package/dist/add/add-workflow-graph.d.ts +7 -0
  32. package/dist/add/add-workflow-graph.js +396 -0
  33. package/dist/add/add-workflow.js +124 -26
  34. package/dist/error-codes.d.ts +16 -1
  35. package/dist/error-codes.js +21 -1
  36. package/dist/index.d.ts +9 -5
  37. package/dist/index.js +5 -2
  38. package/dist/inspector.d.ts +1 -1
  39. package/dist/inspector.js +106 -13
  40. package/dist/schema-generator.d.ts +1 -0
  41. package/dist/schema-generator.js +1 -0
  42. package/dist/types-map.js +10 -1
  43. package/dist/types.d.ts +180 -30
  44. package/dist/utils/compute-required-schemas.d.ts +4 -0
  45. package/dist/utils/compute-required-schemas.js +41 -0
  46. package/dist/utils/contract-hashes.d.ts +35 -0
  47. package/dist/utils/contract-hashes.js +202 -0
  48. package/dist/utils/custom-types-generator.d.ts +9 -0
  49. package/dist/utils/custom-types-generator.js +71 -0
  50. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  51. package/dist/utils/detect-schema-vendor.js +76 -0
  52. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  53. package/dist/utils/ensure-function-metadata.js +220 -6
  54. package/dist/utils/extract-function-name.d.ts +5 -16
  55. package/dist/utils/extract-function-name.js +93 -298
  56. package/dist/utils/extract-services.d.ts +2 -1
  57. package/dist/utils/extract-services.js +25 -1
  58. package/dist/utils/filter-inspector-state.js +107 -23
  59. package/dist/utils/get-property-value.d.ts +8 -2
  60. package/dist/utils/get-property-value.js +33 -4
  61. package/dist/utils/hash.d.ts +2 -0
  62. package/dist/utils/hash.js +23 -0
  63. package/dist/utils/middleware.d.ts +7 -30
  64. package/dist/utils/middleware.js +80 -66
  65. package/dist/utils/permissions.d.ts +2 -2
  66. package/dist/utils/permissions.js +10 -10
  67. package/dist/utils/post-process.d.ts +9 -10
  68. package/dist/utils/post-process.js +231 -24
  69. package/dist/utils/resolve-external-package.d.ts +12 -0
  70. package/dist/utils/resolve-external-package.js +34 -0
  71. package/dist/utils/resolve-function-types.d.ts +6 -0
  72. package/dist/utils/resolve-function-types.js +29 -0
  73. package/dist/utils/resolve-identifier.d.ts +10 -0
  74. package/dist/utils/resolve-identifier.js +36 -0
  75. package/dist/utils/resolve-versions.d.ts +2 -0
  76. package/dist/utils/resolve-versions.js +78 -0
  77. package/dist/utils/schema-generator.d.ts +9 -0
  78. package/dist/utils/schema-generator.js +209 -0
  79. package/dist/utils/serialize-inspector-state.d.ts +73 -13
  80. package/dist/utils/serialize-inspector-state.js +102 -6
  81. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  82. package/dist/utils/serialize-mcp-json.js +99 -0
  83. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  84. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  85. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  86. package/dist/utils/serialize-openapi-json.js +151 -0
  87. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  88. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  89. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
  90. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
  91. package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
  92. package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
  93. package/dist/utils/workflow/dsl/index.d.ts +7 -0
  94. package/dist/utils/workflow/dsl/index.js +7 -0
  95. package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
  96. package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
  97. package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
  98. package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
  99. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
  100. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
  101. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  102. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  103. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  104. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  105. package/dist/utils/workflow/graph/index.d.ts +8 -0
  106. package/dist/utils/workflow/graph/index.js +8 -0
  107. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
  108. package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
  109. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
  110. package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
  111. package/dist/visit.js +13 -2
  112. package/package.json +26 -4
  113. package/src/add/add-ai-agent.ts +468 -0
  114. package/src/add/add-channel.ts +82 -79
  115. package/src/add/add-cli.ts +49 -20
  116. package/src/add/add-file-with-factory.ts +2 -0
  117. package/src/add/add-functions.ts +429 -71
  118. package/src/add/add-http-route.ts +246 -65
  119. package/src/add/add-http-routes.ts +228 -0
  120. package/src/add/add-keyed-wiring.ts +151 -0
  121. package/src/add/add-mcp-prompt.ts +26 -15
  122. package/src/add/add-mcp-resource.ts +27 -15
  123. package/src/add/add-middleware.ts +482 -80
  124. package/src/add/add-permission.ts +199 -40
  125. package/src/add/add-queue-worker.ts +24 -19
  126. package/src/add/add-rpc-invocations.ts +78 -31
  127. package/src/add/add-schedule.ts +16 -11
  128. package/src/add/add-secret.ts +140 -0
  129. package/src/add/add-trigger.ts +154 -0
  130. package/src/add/add-variable.ts +9 -0
  131. package/src/add/add-workflow-graph.ts +522 -0
  132. package/src/add/add-workflow.ts +117 -30
  133. package/src/error-codes.ts +26 -1
  134. package/src/index.ts +27 -8
  135. package/src/inspector.ts +145 -17
  136. package/src/schema-generator.ts +1 -0
  137. package/src/types-map.ts +12 -1
  138. package/src/types.ts +192 -51
  139. package/src/utils/compute-required-schemas.ts +49 -0
  140. package/src/utils/contract-hashes.test.ts +528 -0
  141. package/src/utils/contract-hashes.ts +290 -0
  142. package/src/utils/custom-types-generator.ts +88 -0
  143. package/src/utils/detect-schema-vendor.ts +90 -0
  144. package/src/utils/ensure-function-metadata.ts +324 -7
  145. package/src/utils/extract-function-name.ts +108 -358
  146. package/src/utils/extract-services.ts +35 -2
  147. package/src/utils/filter-inspector-state.test.ts +34 -20
  148. package/src/utils/filter-inspector-state.ts +140 -31
  149. package/src/utils/get-property-value.ts +50 -5
  150. package/src/utils/hash.ts +26 -0
  151. package/src/utils/middleware.test.ts +204 -0
  152. package/src/utils/middleware.ts +129 -67
  153. package/src/utils/permissions.test.ts +35 -12
  154. package/src/utils/permissions.ts +10 -10
  155. package/src/utils/post-process.ts +283 -43
  156. package/src/utils/resolve-external-package.ts +42 -0
  157. package/src/utils/resolve-function-types.ts +42 -0
  158. package/src/utils/resolve-identifier.ts +46 -0
  159. package/src/utils/resolve-versions.test.ts +249 -0
  160. package/src/utils/resolve-versions.ts +105 -0
  161. package/src/utils/schema-generator.ts +329 -0
  162. package/src/utils/serialize-inspector-state.ts +181 -20
  163. package/src/utils/serialize-mcp-json.ts +145 -0
  164. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  165. package/src/utils/serialize-openapi-json.ts +277 -0
  166. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  167. package/src/utils/test-data/inspector-state.json +69 -66
  168. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1104 -0
  169. package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
  170. package/src/utils/workflow/dsl/index.ts +11 -0
  171. package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
  172. package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
  173. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
  174. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  175. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  176. package/src/utils/workflow/graph/index.ts +11 -0
  177. package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
  178. package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
  179. package/src/visit.ts +14 -2
  180. package/tsconfig.tsbuildinfo +1 -1
  181. package/dist/add/add-mcp-tool.d.ts +0 -2
  182. package/dist/add/add-mcp-tool.js +0 -81
  183. package/dist/utils/extract-service-metadata.d.ts +0 -19
  184. package/dist/utils/extract-service-metadata.js +0 -244
  185. package/dist/utils/write-service-metadata.d.ts +0 -13
  186. package/dist/utils/write-service-metadata.js +0 -37
  187. package/src/add/add-mcp-tool.ts +0 -141
  188. package/src/utils/extract-service-metadata.ts +0 -353
  189. package/src/utils/write-service-metadata.ts +0 -51
@@ -0,0 +1,276 @@
1
+ function parseWorkflowFuncId(pikkuFuncId) {
2
+ for (const prefix of ['workflowStart:', 'workflow:']) {
3
+ if (pikkuFuncId.startsWith(prefix)) {
4
+ return { workflowName: pikkuFuncId.slice(prefix.length) };
5
+ }
6
+ }
7
+ if (pikkuFuncId.startsWith('graphStart:')) {
8
+ const rest = pikkuFuncId.slice('graphStart:'.length);
9
+ const colonIdx = rest.indexOf(':');
10
+ if (colonIdx !== -1) {
11
+ return {
12
+ workflowName: rest.slice(0, colonIdx),
13
+ startNode: rest.slice(colonIdx + 1),
14
+ };
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+ function resolveStartNode(parsed, graph) {
20
+ return parsed.startNode ?? graph.entryNodeIds[0];
21
+ }
22
+ function getOrCreateWires(graph) {
23
+ if (!graph.wires) {
24
+ graph.wires = {};
25
+ }
26
+ return graph.wires;
27
+ }
28
+ export function finalizeWorkflowHelperTypes(state) {
29
+ const { functions, workflows } = state;
30
+ const graphMeta = workflows.graphMeta;
31
+ for (const meta of Object.values(functions.meta)) {
32
+ if (meta.functionType !== 'helper')
33
+ continue;
34
+ if (meta.pikkuFuncId.startsWith('workflowStatus:'))
35
+ continue;
36
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId);
37
+ if (!parsed)
38
+ continue;
39
+ const graph = graphMeta[parsed.workflowName];
40
+ if (!graph)
41
+ continue;
42
+ const startNodeId = resolveStartNode(parsed, graph);
43
+ const startNode = graph.nodes[startNodeId];
44
+ if (!startNode || !('rpcName' in startNode))
45
+ continue;
46
+ const rpcMeta = functions.meta[startNode.rpcName];
47
+ if (!rpcMeta)
48
+ continue;
49
+ if (rpcMeta.inputSchemaName) {
50
+ meta.inputSchemaName = rpcMeta.inputSchemaName;
51
+ }
52
+ if (rpcMeta.inputs && rpcMeta.inputs.length > 0) {
53
+ meta.inputs = rpcMeta.inputs;
54
+ }
55
+ }
56
+ }
57
+ export function finalizeWorkflowWires(state) {
58
+ const { workflows } = state;
59
+ const graphMeta = workflows.graphMeta;
60
+ scanHTTP(state, graphMeta);
61
+ scanScheduledTasks(state, graphMeta);
62
+ scanTriggers(state, graphMeta);
63
+ scanQueueWorkers(state, graphMeta);
64
+ scanChannels(state, graphMeta);
65
+ scanMCPEndpoints(state, graphMeta);
66
+ scanCLI(state, graphMeta);
67
+ }
68
+ function scanHTTP(state, graphMeta) {
69
+ for (const [method, routes] of Object.entries(state.http.meta)) {
70
+ for (const [route, meta] of Object.entries(routes)) {
71
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId);
72
+ if (!parsed)
73
+ continue;
74
+ const graph = graphMeta[parsed.workflowName];
75
+ if (!graph)
76
+ continue;
77
+ const wires = getOrCreateWires(graph);
78
+ if (!wires.http)
79
+ wires.http = [];
80
+ wires.http.push({
81
+ route,
82
+ method,
83
+ startNode: resolveStartNode(parsed, graph),
84
+ });
85
+ }
86
+ }
87
+ }
88
+ function scanScheduledTasks(state, graphMeta) {
89
+ for (const meta of Object.values(state.scheduledTasks.meta)) {
90
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId);
91
+ if (!parsed)
92
+ continue;
93
+ const graph = graphMeta[parsed.workflowName];
94
+ if (!graph)
95
+ continue;
96
+ const wires = getOrCreateWires(graph);
97
+ if (!wires.schedule)
98
+ wires.schedule = [];
99
+ wires.schedule.push({
100
+ cron: meta.schedule,
101
+ startNode: resolveStartNode(parsed, graph),
102
+ });
103
+ }
104
+ }
105
+ function scanTriggers(state, graphMeta) {
106
+ for (const meta of Object.values(state.triggers.meta)) {
107
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId);
108
+ if (!parsed)
109
+ continue;
110
+ const graph = graphMeta[parsed.workflowName];
111
+ if (!graph)
112
+ continue;
113
+ const wires = getOrCreateWires(graph);
114
+ if (!wires.trigger)
115
+ wires.trigger = [];
116
+ wires.trigger.push({
117
+ name: meta.name,
118
+ startNode: resolveStartNode(parsed, graph),
119
+ });
120
+ }
121
+ }
122
+ function scanQueueWorkers(state, graphMeta) {
123
+ for (const meta of Object.values(state.queueWorkers.meta)) {
124
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId);
125
+ if (!parsed)
126
+ continue;
127
+ const graph = graphMeta[parsed.workflowName];
128
+ if (!graph)
129
+ continue;
130
+ const wires = getOrCreateWires(graph);
131
+ if (!wires.queue)
132
+ wires.queue = [];
133
+ wires.queue.push({
134
+ name: meta.name,
135
+ startNode: resolveStartNode(parsed, graph),
136
+ });
137
+ }
138
+ }
139
+ function scanChannels(state, graphMeta) {
140
+ for (const channelMeta of Object.values(state.channels.meta)) {
141
+ const wire = {
142
+ name: channelMeta.name,
143
+ route: channelMeta.route,
144
+ };
145
+ let targetWorkflow;
146
+ if (channelMeta.connect) {
147
+ const parsed = parseWorkflowFuncId(channelMeta.connect.pikkuFuncId);
148
+ if (parsed) {
149
+ const graph = graphMeta[parsed.workflowName];
150
+ if (graph) {
151
+ targetWorkflow = graph;
152
+ wire.onConnect = resolveStartNode(parsed, graph);
153
+ }
154
+ }
155
+ }
156
+ if (channelMeta.disconnect) {
157
+ const parsed = parseWorkflowFuncId(channelMeta.disconnect.pikkuFuncId);
158
+ if (parsed) {
159
+ const graph = graphMeta[parsed.workflowName];
160
+ if (graph) {
161
+ targetWorkflow = targetWorkflow ?? graph;
162
+ wire.onDisconnect = resolveStartNode(parsed, graph);
163
+ }
164
+ }
165
+ }
166
+ if (channelMeta.message) {
167
+ const parsed = parseWorkflowFuncId(channelMeta.message.pikkuFuncId);
168
+ if (parsed) {
169
+ const graph = graphMeta[parsed.workflowName];
170
+ if (graph) {
171
+ targetWorkflow = targetWorkflow ?? graph;
172
+ wire.onMessage = resolveStartNode(parsed, graph);
173
+ }
174
+ }
175
+ }
176
+ for (const [routingProp, routeMap] of Object.entries(channelMeta.messageWirings)) {
177
+ for (const [routeValue, messageMeta] of Object.entries(routeMap)) {
178
+ const parsed = parseWorkflowFuncId(messageMeta.pikkuFuncId);
179
+ if (!parsed)
180
+ continue;
181
+ const graph = graphMeta[parsed.workflowName];
182
+ if (!graph)
183
+ continue;
184
+ targetWorkflow = targetWorkflow ?? graph;
185
+ if (!wire.onMessageRoute)
186
+ wire.onMessageRoute = {};
187
+ wire.onMessageRoute[`${routingProp}:${routeValue}`] = resolveStartNode(parsed, graph);
188
+ }
189
+ }
190
+ if (targetWorkflow) {
191
+ const wires = getOrCreateWires(targetWorkflow);
192
+ if (!wires.channel)
193
+ wires.channel = [];
194
+ wires.channel.push(wire);
195
+ }
196
+ }
197
+ }
198
+ function scanMCPEndpoints(state, graphMeta) {
199
+ for (const meta of Object.values(state.mcpEndpoints.toolsMeta)) {
200
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId);
201
+ if (!parsed)
202
+ continue;
203
+ const graph = graphMeta[parsed.workflowName];
204
+ if (!graph)
205
+ continue;
206
+ const wires = getOrCreateWires(graph);
207
+ if (!wires.mcp)
208
+ wires.mcp = {};
209
+ if (!wires.mcp.tool)
210
+ wires.mcp.tool = [];
211
+ wires.mcp.tool.push({
212
+ name: meta.name,
213
+ startNode: resolveStartNode(parsed, graph),
214
+ });
215
+ }
216
+ for (const meta of Object.values(state.mcpEndpoints.promptsMeta)) {
217
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId);
218
+ if (!parsed)
219
+ continue;
220
+ const graph = graphMeta[parsed.workflowName];
221
+ if (!graph)
222
+ continue;
223
+ const wires = getOrCreateWires(graph);
224
+ if (!wires.mcp)
225
+ wires.mcp = {};
226
+ if (!wires.mcp.prompt)
227
+ wires.mcp.prompt = [];
228
+ wires.mcp.prompt.push({
229
+ name: meta.name,
230
+ startNode: resolveStartNode(parsed, graph),
231
+ });
232
+ }
233
+ for (const meta of Object.values(state.mcpEndpoints.resourcesMeta)) {
234
+ const parsed = parseWorkflowFuncId(meta.pikkuFuncId);
235
+ if (!parsed)
236
+ continue;
237
+ const graph = graphMeta[parsed.workflowName];
238
+ if (!graph)
239
+ continue;
240
+ const wires = getOrCreateWires(graph);
241
+ if (!wires.mcp)
242
+ wires.mcp = {};
243
+ if (!wires.mcp.resource)
244
+ wires.mcp.resource = [];
245
+ wires.mcp.resource.push({
246
+ uri: meta.uri,
247
+ startNode: resolveStartNode(parsed, graph),
248
+ });
249
+ }
250
+ }
251
+ function visitCLICommands(commands, programName, path, graphMeta) {
252
+ for (const [name, command] of Object.entries(commands)) {
253
+ const currentPath = [...path, name];
254
+ const parsed = parseWorkflowFuncId(command.pikkuFuncId);
255
+ if (parsed) {
256
+ const graph = graphMeta[parsed.workflowName];
257
+ if (graph) {
258
+ const wires = getOrCreateWires(graph);
259
+ if (!wires.cli)
260
+ wires.cli = [];
261
+ wires.cli.push({
262
+ command: `${programName} ${currentPath.join(' ')}`,
263
+ startNode: resolveStartNode(parsed, graph),
264
+ });
265
+ }
266
+ }
267
+ if (command.subcommands) {
268
+ visitCLICommands(command.subcommands, programName, currentPath, graphMeta);
269
+ }
270
+ }
271
+ }
272
+ function scanCLI(state, graphMeta) {
273
+ for (const program of Object.values(state.cli.meta.programs)) {
274
+ visitCLICommands(program.commands, program.program, [], graphMeta);
275
+ }
276
+ }
@@ -0,0 +1,2 @@
1
+ import type { InspectorState } from '../../../types.js';
2
+ export declare function finalizeWorkflows(state: InspectorState): void;
@@ -0,0 +1,75 @@
1
+ import { isVersionedId, formatVersionedId, parseVersionedId } from '@pikku/core';
2
+ import { canonicalJSON, hashString } from '../../hash.js';
3
+ import { convertDslToGraph } from './convert-dsl-to-graph.js';
4
+ export function finalizeWorkflows(state) {
5
+ const { workflows, functions } = state;
6
+ const functionsMeta = functions.meta;
7
+ for (const [name, meta] of Object.entries(workflows.meta)) {
8
+ const graph = convertDslToGraph(name, meta);
9
+ stampVersionsOnGraph(graph, functionsMeta);
10
+ computeStepHashes(graph, functionsMeta);
11
+ graph.graphHash = computeGraphHash(graph);
12
+ workflows.graphMeta[name] = graph;
13
+ }
14
+ for (const graph of Object.values(workflows.graphMeta)) {
15
+ if (graph.graphHash) {
16
+ continue;
17
+ }
18
+ stampVersionsOnGraph(graph, functionsMeta);
19
+ computeStepHashes(graph, functionsMeta);
20
+ graph.graphHash = computeGraphHash(graph);
21
+ }
22
+ }
23
+ function stampVersionsOnGraph(graph, functionsMeta) {
24
+ for (const node of Object.values(graph.nodes)) {
25
+ if (!('rpcName' in node) || typeof node.rpcName !== 'string') {
26
+ continue;
27
+ }
28
+ if (isVersionedId(node.rpcName)) {
29
+ continue;
30
+ }
31
+ const meta = functionsMeta[node.rpcName];
32
+ if (meta?.version !== undefined) {
33
+ node.rpcName = formatVersionedId(node.rpcName, meta.version);
34
+ }
35
+ else {
36
+ const latestVersion = findLatestVersion(node.rpcName, functionsMeta);
37
+ if (latestVersion > 0) {
38
+ node.rpcName = formatVersionedId(node.rpcName, latestVersion);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ function findLatestVersion(baseName, functionsMeta) {
44
+ let max = 0;
45
+ for (const id of Object.keys(functionsMeta)) {
46
+ const parsed = parseVersionedId(id);
47
+ if (parsed.baseName === baseName && parsed.version !== null) {
48
+ max = Math.max(max, parsed.version);
49
+ }
50
+ }
51
+ return max;
52
+ }
53
+ function computeStepHashes(graph, functionsMeta) {
54
+ for (const node of Object.values(graph.nodes)) {
55
+ if (!('rpcName' in node) || typeof node.rpcName !== 'string') {
56
+ continue;
57
+ }
58
+ const rpcName = node.rpcName;
59
+ let meta = functionsMeta[rpcName];
60
+ if (!meta) {
61
+ const { baseName } = parseVersionedId(rpcName);
62
+ meta = functionsMeta[baseName];
63
+ }
64
+ ;
65
+ node.stepHash = hashString(`${node.nodeId}:${meta?.contractHash ?? ''}`, 12);
66
+ }
67
+ }
68
+ function computeGraphHash(graph) {
69
+ return hashString(canonicalJSON({
70
+ source: graph.source,
71
+ context: graph.context,
72
+ nodes: graph.nodes,
73
+ entryNodeIds: graph.entryNodeIds,
74
+ }), 12);
75
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Workflow graph serialization exports
3
+ */
4
+ export * from './workflow-graph.types.js';
5
+ export { serializeWorkflowGraph } from './serialize-workflow-graph.js';
6
+ export { convertDslToGraph } from './convert-dsl-to-graph.js';
7
+ export { finalizeWorkflows } from './finalize-workflows.js';
8
+ export { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './finalize-workflow-wires.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Workflow graph serialization exports
3
+ */
4
+ export * from './workflow-graph.types.js';
5
+ export { serializeWorkflowGraph } from './serialize-workflow-graph.js';
6
+ export { convertDslToGraph } from './convert-dsl-to-graph.js';
7
+ export { finalizeWorkflows } from './finalize-workflows.js';
8
+ export { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './finalize-workflow-wires.js';
@@ -0,0 +1,35 @@
1
+ import type { SerializedWorkflowGraph, DataRef, SerializedNext } from './workflow-graph.types.js';
2
+ /**
3
+ * Serialize a workflow graph definition (from runtime) to JSON format
4
+ *
5
+ * @param definition - The runtime definition (with callbacks evaluated)
6
+ * @param rpcNameLookup - Function to get RPC name from a node's func
7
+ */
8
+ export declare function serializeWorkflowGraph(definition: {
9
+ name: string;
10
+ graph: Record<string, {
11
+ func: {
12
+ name?: string;
13
+ };
14
+ input?: (ref: any) => Record<string, unknown>;
15
+ next?: string | string[] | Record<string, string | string[]>;
16
+ onError?: string | string[];
17
+ }>;
18
+ }, options?: {
19
+ description?: string;
20
+ tags?: string[];
21
+ }): SerializedWorkflowGraph;
22
+ /**
23
+ * Deserialize a workflow graph from JSON to runtime format
24
+ * This re-hydrates the JSON so it can be executed
25
+ */
26
+ export declare function deserializeWorkflowGraph(serialized: SerializedWorkflowGraph): {
27
+ name: string;
28
+ graph: Record<string, {
29
+ rpcName: string;
30
+ input: Record<string, unknown | DataRef>;
31
+ next?: SerializedNext;
32
+ onError?: string | string[];
33
+ }>;
34
+ entryNodeIds: string[];
35
+ };
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Convert a RefValue (from runtime) to DataRef (serialized)
3
+ */
4
+ function convertRef(ref) {
5
+ return {
6
+ $ref: ref.nodeId,
7
+ path: ref.path,
8
+ };
9
+ }
10
+ /**
11
+ * Check if a value is a runtime RefValue
12
+ */
13
+ function isRefValue(value) {
14
+ return (typeof value === 'object' &&
15
+ value !== null &&
16
+ '__isRef' in value &&
17
+ value.__isRef === true);
18
+ }
19
+ /**
20
+ * Convert input mapping from runtime format to serialized format
21
+ */
22
+ function serializeInputMapping(input) {
23
+ const result = {};
24
+ for (const [key, value] of Object.entries(input)) {
25
+ if (isRefValue(value)) {
26
+ result[key] = convertRef(value);
27
+ }
28
+ else {
29
+ result[key] = value;
30
+ }
31
+ }
32
+ return result;
33
+ }
34
+ /**
35
+ * Convert next config from runtime format to serialized format
36
+ * Runtime uses Record<string, string | string[]> for branching with graph.branch()
37
+ * Serialized uses { conditions: [...], default: ... } for UI-friendly branching
38
+ */
39
+ function serializeNext(next) {
40
+ if (!next)
41
+ return undefined;
42
+ if (typeof next === 'string')
43
+ return next;
44
+ if (Array.isArray(next))
45
+ return next;
46
+ // Record format - convert to conditions format
47
+ // For now, treat keys as branch identifiers (from graph.branch())
48
+ // UI can display these as condition labels
49
+ const conditions = Object.entries(next).map(([key, target]) => ({
50
+ expression: key, // The branch key becomes the expression
51
+ target,
52
+ }));
53
+ return { conditions };
54
+ }
55
+ /**
56
+ * Serialize a workflow graph definition (from runtime) to JSON format
57
+ *
58
+ * @param definition - The runtime definition (with callbacks evaluated)
59
+ * @param rpcNameLookup - Function to get RPC name from a node's func
60
+ */
61
+ export function serializeWorkflowGraph(definition, options) {
62
+ const nodes = {};
63
+ const entryNodeIds = [];
64
+ // Create a ref function that captures refs
65
+ const createRef = (nodeId, path) => ({
66
+ __isRef: true,
67
+ nodeId,
68
+ path,
69
+ });
70
+ // Track which nodes have incoming edges
71
+ const hasIncomingEdge = new Set();
72
+ // First pass: identify nodes with incoming edges
73
+ for (const [_nodeId, node] of Object.entries(definition.graph)) {
74
+ const next = node.next;
75
+ if (!next)
76
+ continue;
77
+ if (typeof next === 'string') {
78
+ hasIncomingEdge.add(next);
79
+ }
80
+ else if (Array.isArray(next)) {
81
+ next.forEach((n) => hasIncomingEdge.add(n));
82
+ }
83
+ else {
84
+ for (const targets of Object.values(next)) {
85
+ if (typeof targets === 'string') {
86
+ hasIncomingEdge.add(targets);
87
+ }
88
+ else {
89
+ targets.forEach((n) => hasIncomingEdge.add(n));
90
+ }
91
+ }
92
+ }
93
+ }
94
+ // Second pass: serialize nodes
95
+ for (const [nodeId, node] of Object.entries(definition.graph)) {
96
+ // Evaluate input callback to get the mapping
97
+ let input = {};
98
+ if (node.input) {
99
+ const rawInput = node.input(createRef);
100
+ input = serializeInputMapping(rawInput);
101
+ }
102
+ // Get RPC name from func
103
+ const rpcName = node.func?.name || 'unknown';
104
+ const funcNode = {
105
+ nodeId,
106
+ rpcName,
107
+ input,
108
+ next: serializeNext(node.next),
109
+ onError: node.onError,
110
+ };
111
+ nodes[nodeId] = funcNode;
112
+ // Entry nodes have no incoming edges
113
+ if (!hasIncomingEdge.has(nodeId)) {
114
+ entryNodeIds.push(nodeId);
115
+ }
116
+ }
117
+ return {
118
+ name: definition.name,
119
+ pikkuFuncId: definition.name, // For graph workflows, pikkuFuncId is the workflow name
120
+ source: 'graph',
121
+ description: options?.description,
122
+ tags: options?.tags,
123
+ nodes,
124
+ entryNodeIds,
125
+ };
126
+ }
127
+ /**
128
+ * Deserialize a workflow graph from JSON to runtime format
129
+ * This re-hydrates the JSON so it can be executed
130
+ */
131
+ export function deserializeWorkflowGraph(serialized) {
132
+ const graph = {};
133
+ for (const [nodeId, node] of Object.entries(serialized.nodes)) {
134
+ // Only include FunctionNode properties (nodes with rpcName)
135
+ if ('rpcName' in node) {
136
+ const funcNode = node;
137
+ graph[nodeId] = {
138
+ rpcName: funcNode.rpcName,
139
+ input: funcNode.input ?? {},
140
+ next: funcNode.next,
141
+ onError: funcNode.onError,
142
+ };
143
+ }
144
+ }
145
+ return {
146
+ name: serialized.name,
147
+ graph,
148
+ entryNodeIds: serialized.entryNodeIds,
149
+ };
150
+ }