@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,2 @@
1
+ import { AddWiring } from '../types.js';
2
+ export declare const addTrigger: AddWiring;
@@ -0,0 +1,87 @@
1
+ import * as ts from 'typescript';
2
+ import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
+ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
4
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
+ import { resolveMiddleware } from '../utils/middleware.js';
6
+ import { extractWireNames } from '../utils/post-process.js';
7
+ import { resolveExternalPackageName } from '../utils/resolve-external-package.js';
8
+ import { ErrorCode } from '../error-codes.js';
9
+ export const addTrigger = (logger, node, checker, state, options) => {
10
+ if (!ts.isCallExpression(node)) {
11
+ return;
12
+ }
13
+ const args = node.arguments;
14
+ const firstArg = args[0];
15
+ const expression = node.expression;
16
+ if (!ts.isIdentifier(expression)) {
17
+ return;
18
+ }
19
+ if (expression.text === 'wireTrigger') {
20
+ addWireTrigger(logger, node, checker, state, firstArg);
21
+ }
22
+ else if (expression.text === 'wireTriggerSource') {
23
+ addWireTriggerSource(logger, node, checker, state, options, firstArg);
24
+ }
25
+ };
26
+ const addWireTrigger = (logger, node, checker, state, firstArg) => {
27
+ if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) {
28
+ return;
29
+ }
30
+ const obj = firstArg;
31
+ const nameValue = getPropertyValue(obj, 'name');
32
+ const { disabled, tags, summary, description, errors } = getCommonWireMetaData(obj, 'Trigger', nameValue, logger);
33
+ if (disabled)
34
+ return;
35
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
36
+ if (!funcInitializer) {
37
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for trigger '${nameValue}'.`);
38
+ return;
39
+ }
40
+ const extracted = extractFunctionName(funcInitializer, checker, state.rootDir);
41
+ let pikkuFuncId = extracted.pikkuFuncId;
42
+ if (pikkuFuncId.startsWith('__temp_') && nameValue) {
43
+ pikkuFuncId = makeContextBasedId('trigger', nameValue);
44
+ }
45
+ if (!nameValue) {
46
+ return;
47
+ }
48
+ // --- resolve middleware ---
49
+ const middleware = resolveMiddleware(state, obj, tags, checker);
50
+ // --- track used functions/middleware for service aggregation ---
51
+ state.serviceAggregation.usedFunctions.add(pikkuFuncId);
52
+ extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
53
+ state.triggers.files.add(node.getSourceFile().fileName);
54
+ state.triggers.meta[nameValue] = {
55
+ pikkuFuncId,
56
+ name: nameValue,
57
+ summary,
58
+ description,
59
+ errors,
60
+ tags,
61
+ middleware,
62
+ };
63
+ };
64
+ const addWireTriggerSource = (logger, node, checker, state, options, firstArg) => {
65
+ if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) {
66
+ return;
67
+ }
68
+ const obj = firstArg;
69
+ const nameValue = getPropertyValue(obj, 'name');
70
+ if (!nameValue) {
71
+ return;
72
+ }
73
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
74
+ if (!funcInitializer) {
75
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for trigger source '${nameValue}'.`);
76
+ return;
77
+ }
78
+ if (ts.isIdentifier(funcInitializer)) {
79
+ const packageName = resolveExternalPackageName(funcInitializer, checker, options.externalPackages);
80
+ state.triggers.sourceMeta[nameValue] = {
81
+ name: nameValue,
82
+ pikkuFuncId: funcInitializer.text,
83
+ packageName: packageName || undefined,
84
+ };
85
+ }
86
+ state.triggers.files.add(node.getSourceFile().fileName);
87
+ };
@@ -0,0 +1 @@
1
+ export declare const addVariable: import("../types.js").AddWiring;
@@ -0,0 +1,8 @@
1
+ import { createAddKeyedWiring } from './add-keyed-wiring.js';
2
+ export const addVariable = createAddKeyedWiring({
3
+ functionName: 'wireVariable',
4
+ idField: 'variableId',
5
+ label: 'Variable',
6
+ schemaPrefix: 'VariableSchema',
7
+ getState: (state) => state.variables,
8
+ });
@@ -0,0 +1,7 @@
1
+ import type { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspector for pikkuWorkflowGraph() calls
4
+ * Detects: pikkuWorkflowGraph({ nodes: {...}, config: {...} })
5
+ * or: export const x = pikkuWorkflowGraph({...}) where the call is found via variable declaration
6
+ */
7
+ export declare const addWorkflowGraph: AddWiring;
@@ -0,0 +1,396 @@
1
+ import * as ts from 'typescript';
2
+ import { ErrorCode } from '../error-codes.js';
3
+ import { extractStringLiteral } from '../utils/extract-node-value.js';
4
+ function extractAstValue(expr, refParamName, templateParamName) {
5
+ if (ts.isStringLiteral(expr)) {
6
+ return expr.text;
7
+ }
8
+ if (ts.isNumericLiteral(expr)) {
9
+ return Number(expr.text);
10
+ }
11
+ if (expr.kind === ts.SyntaxKind.TrueKeyword) {
12
+ return true;
13
+ }
14
+ if (expr.kind === ts.SyntaxKind.FalseKeyword) {
15
+ return false;
16
+ }
17
+ if (expr.kind === ts.SyntaxKind.NullKeyword) {
18
+ return null;
19
+ }
20
+ if (ts.isCallExpression(expr)) {
21
+ const callee = expr.expression;
22
+ if (ts.isIdentifier(callee)) {
23
+ if (callee.text === refParamName) {
24
+ const args = expr.arguments;
25
+ const nodeId = args[0] && ts.isStringLiteral(args[0]) ? args[0].text : 'unknown';
26
+ const path = args[1] && ts.isStringLiteral(args[1]) ? args[1].text : undefined;
27
+ return { $ref: nodeId, path };
28
+ }
29
+ if (templateParamName && callee.text === templateParamName) {
30
+ const templateStr = expr.arguments[0] && ts.isStringLiteral(expr.arguments[0])
31
+ ? expr.arguments[0].text
32
+ : '';
33
+ const refsArg = expr.arguments[1];
34
+ const refs = [];
35
+ if (refsArg && ts.isArrayLiteralExpression(refsArg)) {
36
+ for (const el of refsArg.elements) {
37
+ const resolved = extractAstValue(el, refParamName, templateParamName);
38
+ if (typeof resolved === 'object' &&
39
+ resolved !== null &&
40
+ '$ref' in resolved) {
41
+ refs.push(resolved);
42
+ }
43
+ }
44
+ }
45
+ const parts = [];
46
+ const expressions = [];
47
+ const regex = /\$(\d+)/g;
48
+ let lastIndex = 0;
49
+ let match;
50
+ while ((match = regex.exec(templateStr)) !== null) {
51
+ parts.push(templateStr.slice(lastIndex, match.index));
52
+ const refIndex = parseInt(match[1], 10);
53
+ expressions.push(refs[refIndex] ?? { $ref: 'unknown' });
54
+ lastIndex = regex.lastIndex;
55
+ }
56
+ parts.push(templateStr.slice(lastIndex));
57
+ return { $template: { parts, expressions } };
58
+ }
59
+ }
60
+ }
61
+ if (ts.isArrayLiteralExpression(expr)) {
62
+ return expr.elements.map((el) => extractAstValue(el, refParamName, templateParamName));
63
+ }
64
+ if (ts.isObjectLiteralExpression(expr)) {
65
+ const obj = {};
66
+ for (const prop of expr.properties) {
67
+ if (!ts.isPropertyAssignment(prop))
68
+ continue;
69
+ const key = ts.isIdentifier(prop.name)
70
+ ? prop.name.text
71
+ : ts.isStringLiteral(prop.name)
72
+ ? prop.name.text
73
+ : null;
74
+ if (!key)
75
+ continue;
76
+ obj[key] = extractAstValue(prop.initializer, refParamName, templateParamName);
77
+ }
78
+ return obj;
79
+ }
80
+ if (ts.isPrefixUnaryExpression(expr)) {
81
+ if (expr.operator === ts.SyntaxKind.MinusToken &&
82
+ ts.isNumericLiteral(expr.operand)) {
83
+ return -Number(expr.operand.text);
84
+ }
85
+ }
86
+ return undefined;
87
+ }
88
+ function extractInputMapping(node, _checker) {
89
+ if (!ts.isArrowFunction(node)) {
90
+ return {};
91
+ }
92
+ let bodyObj;
93
+ if (ts.isObjectLiteralExpression(node.body)) {
94
+ bodyObj = node.body;
95
+ }
96
+ else if (ts.isParenthesizedExpression(node.body)) {
97
+ if (ts.isObjectLiteralExpression(node.body.expression)) {
98
+ bodyObj = node.body.expression;
99
+ }
100
+ }
101
+ else if (ts.isBlock(node.body)) {
102
+ for (const stmt of node.body.statements) {
103
+ if (ts.isReturnStatement(stmt) && stmt.expression) {
104
+ if (ts.isObjectLiteralExpression(stmt.expression)) {
105
+ bodyObj = stmt.expression;
106
+ }
107
+ }
108
+ }
109
+ }
110
+ if (!bodyObj) {
111
+ return {};
112
+ }
113
+ const refParamName = node.parameters.length > 0 && ts.isIdentifier(node.parameters[0].name)
114
+ ? node.parameters[0].name.text
115
+ : 'ref';
116
+ const templateParamName = node.parameters.length > 1 && ts.isIdentifier(node.parameters[1].name)
117
+ ? node.parameters[1].name.text
118
+ : undefined;
119
+ const input = {};
120
+ for (const prop of bodyObj.properties) {
121
+ if (!ts.isPropertyAssignment(prop))
122
+ continue;
123
+ const key = ts.isIdentifier(prop.name)
124
+ ? prop.name.text
125
+ : ts.isStringLiteral(prop.name)
126
+ ? prop.name.text
127
+ : null;
128
+ if (!key)
129
+ continue;
130
+ const value = extractAstValue(prop.initializer, refParamName, templateParamName);
131
+ if (value !== undefined) {
132
+ input[key] = value;
133
+ }
134
+ }
135
+ return input;
136
+ }
137
+ /**
138
+ * Extract next config (string, array, or record)
139
+ */
140
+ function extractNextConfig(node, _checker) {
141
+ if (ts.isStringLiteral(node)) {
142
+ return node.text;
143
+ }
144
+ if (ts.isArrayLiteralExpression(node)) {
145
+ return node.elements
146
+ .filter(ts.isStringLiteral)
147
+ .map((el) => el.text);
148
+ }
149
+ if (ts.isObjectLiteralExpression(node)) {
150
+ const result = {};
151
+ for (const prop of node.properties) {
152
+ if (!ts.isPropertyAssignment(prop))
153
+ continue;
154
+ const key = ts.isIdentifier(prop.name)
155
+ ? prop.name.text
156
+ : ts.isStringLiteral(prop.name)
157
+ ? prop.name.text
158
+ : null;
159
+ if (!key)
160
+ continue;
161
+ if (ts.isStringLiteral(prop.initializer)) {
162
+ result[key] = prop.initializer.text;
163
+ }
164
+ else if (ts.isArrayLiteralExpression(prop.initializer)) {
165
+ result[key] = prop.initializer.elements
166
+ .filter(ts.isStringLiteral)
167
+ .map((el) => el.text);
168
+ }
169
+ }
170
+ return result;
171
+ }
172
+ return undefined;
173
+ }
174
+ /**
175
+ * Compute entry node IDs from graph nodes
176
+ */
177
+ function computeEntryNodeIds(graphNodes) {
178
+ const hasIncomingEdge = new Set();
179
+ for (const node of Object.values(graphNodes)) {
180
+ const next = node.next;
181
+ if (!next)
182
+ continue;
183
+ if (typeof next === 'string') {
184
+ hasIncomingEdge.add(next);
185
+ }
186
+ else if (Array.isArray(next)) {
187
+ next.forEach((n) => hasIncomingEdge.add(n));
188
+ }
189
+ else if (typeof next === 'object') {
190
+ for (const targets of Object.values(next)) {
191
+ if (typeof targets === 'string') {
192
+ hasIncomingEdge.add(targets);
193
+ }
194
+ else if (Array.isArray(targets)) {
195
+ ;
196
+ targets.forEach((n) => hasIncomingEdge.add(n));
197
+ }
198
+ }
199
+ }
200
+ }
201
+ return Object.keys(graphNodes).filter((nodeId) => !hasIncomingEdge.has(nodeId));
202
+ }
203
+ /**
204
+ * Extract pikkuWorkflowGraph config from an object literal argument
205
+ */
206
+ function extractWorkflowGraphConfig(configArg, checker) {
207
+ let name;
208
+ let description;
209
+ let tags;
210
+ let disabled;
211
+ let nodesNode;
212
+ let configNode;
213
+ for (const prop of configArg.properties) {
214
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
215
+ continue;
216
+ const propName = prop.name.text;
217
+ if (propName === 'name') {
218
+ name = extractStringLiteral(prop.initializer, checker);
219
+ }
220
+ else if (propName === 'description') {
221
+ description = extractStringLiteral(prop.initializer, checker);
222
+ }
223
+ else if (propName === 'disabled') {
224
+ if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) {
225
+ disabled = true;
226
+ }
227
+ }
228
+ else if (propName === 'tags' &&
229
+ ts.isArrayLiteralExpression(prop.initializer)) {
230
+ tags = prop.initializer.elements
231
+ .filter(ts.isStringLiteral)
232
+ .map((el) => el.text);
233
+ }
234
+ else if (propName === 'nodes' &&
235
+ ts.isObjectLiteralExpression(prop.initializer)) {
236
+ nodesNode = prop.initializer;
237
+ }
238
+ else if (propName === 'config' &&
239
+ ts.isObjectLiteralExpression(prop.initializer)) {
240
+ configNode = prop.initializer;
241
+ }
242
+ }
243
+ return { name, description, tags, disabled, nodesNode, configNode };
244
+ }
245
+ /**
246
+ * Extract graph nodes from the new pikkuWorkflowGraph format
247
+ * New format: { nodes: { entry: 'rpcName', ... }, config: { entry: { next: 'sendWelcome', ... }, ... } }
248
+ */
249
+ function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
250
+ const nodes = {};
251
+ if (!nodesNode) {
252
+ return nodes;
253
+ }
254
+ // Extract node ID to RPC name mapping from 'nodes' property
255
+ const nodeRpcMap = {};
256
+ for (const prop of nodesNode.properties) {
257
+ if (!ts.isPropertyAssignment(prop))
258
+ continue;
259
+ const nodeId = ts.isIdentifier(prop.name)
260
+ ? prop.name.text
261
+ : ts.isStringLiteral(prop.name)
262
+ ? prop.name.text
263
+ : null;
264
+ if (!nodeId)
265
+ continue;
266
+ const rpcName = extractStringLiteral(prop.initializer, checker);
267
+ if (rpcName) {
268
+ nodeRpcMap[nodeId] = rpcName;
269
+ state.rpc.invokedFunctions.add(rpcName);
270
+ const funcFile = state.functions.files.get(rpcName);
271
+ if (funcFile && !state.rpc.internalFiles.has(rpcName)) {
272
+ state.rpc.internalFiles.set(rpcName, funcFile);
273
+ }
274
+ }
275
+ }
276
+ // Initialize nodes with their RPC names
277
+ for (const [nodeId, rpcName] of Object.entries(nodeRpcMap)) {
278
+ nodes[nodeId] = {
279
+ nodeId,
280
+ rpcName,
281
+ input: {},
282
+ next: undefined,
283
+ onError: undefined,
284
+ };
285
+ }
286
+ // Extract config for each node from 'config' property
287
+ if (configNode) {
288
+ for (const prop of configNode.properties) {
289
+ if (!ts.isPropertyAssignment(prop))
290
+ continue;
291
+ const nodeId = ts.isIdentifier(prop.name)
292
+ ? prop.name.text
293
+ : ts.isStringLiteral(prop.name)
294
+ ? prop.name.text
295
+ : null;
296
+ if (!nodeId || !nodes[nodeId])
297
+ continue;
298
+ if (ts.isObjectLiteralExpression(prop.initializer)) {
299
+ const nodeConfig = extractNodeConfigFromObject(prop.initializer, checker);
300
+ if (nodeConfig) {
301
+ nodes[nodeId].next = nodeConfig.next;
302
+ nodes[nodeId].onError = nodeConfig.onError;
303
+ nodes[nodeId].input = nodeConfig.input;
304
+ }
305
+ }
306
+ }
307
+ }
308
+ return nodes;
309
+ }
310
+ /**
311
+ * Extract node config (next, onError, input) from object literal
312
+ */
313
+ function extractNodeConfigFromObject(obj, checker) {
314
+ let next = undefined;
315
+ let onError = undefined;
316
+ let input = {};
317
+ for (const prop of obj.properties) {
318
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
319
+ continue;
320
+ const propName = prop.name.text;
321
+ if (propName === 'next') {
322
+ next = extractNextConfig(prop.initializer, checker);
323
+ }
324
+ else if (propName === 'onError') {
325
+ onError = extractNextConfig(prop.initializer, checker);
326
+ }
327
+ else if (propName === 'input') {
328
+ input = extractInputMapping(prop.initializer, checker);
329
+ }
330
+ }
331
+ return { next, onError, input };
332
+ }
333
+ /**
334
+ * Inspector for pikkuWorkflowGraph() calls
335
+ * Detects: pikkuWorkflowGraph({ nodes: {...}, config: {...} })
336
+ * or: export const x = pikkuWorkflowGraph({...}) where the call is found via variable declaration
337
+ */
338
+ export const addWorkflowGraph = (logger, node, checker, state) => {
339
+ if (!ts.isCallExpression(node)) {
340
+ return;
341
+ }
342
+ const expression = node.expression;
343
+ if (!ts.isIdentifier(expression) ||
344
+ expression.text !== 'pikkuWorkflowGraph') {
345
+ return;
346
+ }
347
+ const args = node.arguments;
348
+ const firstArg = args[0];
349
+ if (!firstArg) {
350
+ logger.critical(ErrorCode.MISSING_FUNC, 'pikkuWorkflowGraph requires an argument');
351
+ return;
352
+ }
353
+ if (!ts.isObjectLiteralExpression(firstArg)) {
354
+ logger.critical(ErrorCode.MISSING_FUNC, 'pikkuWorkflowGraph requires an object argument');
355
+ return;
356
+ }
357
+ const graphConfig = extractWorkflowGraphConfig(firstArg, checker);
358
+ if (!graphConfig) {
359
+ logger.critical(ErrorCode.MISSING_NAME, 'pikkuWorkflowGraph: failed to extract config');
360
+ return;
361
+ }
362
+ // Resolve exportedName from variable declaration if this is `export const x = pikkuWorkflowGraph({...})`
363
+ const parent = node.parent;
364
+ if (!graphConfig.exportedName &&
365
+ parent &&
366
+ ts.isVariableDeclaration(parent) &&
367
+ ts.isIdentifier(parent.name)) {
368
+ graphConfig.exportedName = parent.name.text;
369
+ }
370
+ const workflowName = graphConfig.name || graphConfig.exportedName;
371
+ if (!workflowName) {
372
+ logger.critical(ErrorCode.MISSING_NAME, 'pikkuWorkflowGraph requires a name property or exported variable name');
373
+ return;
374
+ }
375
+ let graphNodes = {};
376
+ if (graphConfig.nodesNode) {
377
+ graphNodes = extractGraphFromNewFormat(graphConfig.nodesNode, graphConfig.configNode, checker, state);
378
+ }
379
+ const entryNodeIds = computeEntryNodeIds(graphNodes);
380
+ const serialized = {
381
+ name: workflowName,
382
+ pikkuFuncId: workflowName,
383
+ source: 'graph',
384
+ description: graphConfig.description,
385
+ tags: graphConfig.tags,
386
+ nodes: graphNodes,
387
+ entryNodeIds,
388
+ };
389
+ if (graphConfig.disabled)
390
+ return;
391
+ state.workflows.graphMeta[workflowName] = serialized;
392
+ state.workflows.graphFiles.set(workflowName, {
393
+ path: node.getSourceFile().fileName,
394
+ exportedName: graphConfig.exportedName || workflowName,
395
+ });
396
+ };