@pikku/inspector 0.11.2 → 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 (182) hide show
  1. package/CHANGELOG.md +11 -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 +250 -75
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +152 -66
  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.js +14 -0
  23. package/dist/add/add-schedule.js +11 -5
  24. package/dist/add/add-secret.d.ts +3 -0
  25. package/dist/add/add-secret.js +82 -0
  26. package/dist/add/add-trigger.d.ts +2 -0
  27. package/dist/add/add-trigger.js +87 -0
  28. package/dist/add/add-variable.d.ts +1 -0
  29. package/dist/add/add-variable.js +8 -0
  30. package/dist/add/add-workflow-graph.d.ts +3 -2
  31. package/dist/add/add-workflow-graph.js +143 -406
  32. package/dist/add/add-workflow.js +6 -4
  33. package/dist/error-codes.d.ts +14 -1
  34. package/dist/error-codes.js +19 -1
  35. package/dist/index.d.ts +9 -8
  36. package/dist/index.js +5 -4
  37. package/dist/inspector.d.ts +1 -1
  38. package/dist/inspector.js +91 -14
  39. package/dist/schema-generator.d.ts +1 -0
  40. package/dist/schema-generator.js +1 -0
  41. package/dist/types-map.js +10 -1
  42. package/dist/types.d.ts +163 -39
  43. package/dist/utils/compute-required-schemas.d.ts +4 -0
  44. package/dist/utils/compute-required-schemas.js +41 -0
  45. package/dist/utils/contract-hashes.d.ts +35 -0
  46. package/dist/utils/contract-hashes.js +202 -0
  47. package/dist/utils/custom-types-generator.d.ts +9 -0
  48. package/dist/utils/custom-types-generator.js +71 -0
  49. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  50. package/dist/utils/detect-schema-vendor.js +76 -0
  51. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  52. package/dist/utils/ensure-function-metadata.js +220 -6
  53. package/dist/utils/extract-function-name.d.ts +5 -16
  54. package/dist/utils/extract-function-name.js +86 -291
  55. package/dist/utils/extract-services.d.ts +2 -1
  56. package/dist/utils/extract-services.js +25 -1
  57. package/dist/utils/filter-inspector-state.js +107 -23
  58. package/dist/utils/get-property-value.d.ts +6 -1
  59. package/dist/utils/get-property-value.js +28 -3
  60. package/dist/utils/hash.d.ts +2 -0
  61. package/dist/utils/hash.js +23 -0
  62. package/dist/utils/middleware.d.ts +7 -30
  63. package/dist/utils/middleware.js +80 -66
  64. package/dist/utils/permissions.d.ts +2 -2
  65. package/dist/utils/permissions.js +10 -10
  66. package/dist/utils/post-process.d.ts +9 -10
  67. package/dist/utils/post-process.js +231 -24
  68. package/dist/utils/resolve-external-package.d.ts +12 -0
  69. package/dist/utils/resolve-external-package.js +34 -0
  70. package/dist/utils/resolve-function-types.d.ts +6 -0
  71. package/dist/utils/resolve-function-types.js +29 -0
  72. package/dist/utils/resolve-identifier.d.ts +10 -0
  73. package/dist/utils/resolve-identifier.js +36 -0
  74. package/dist/utils/resolve-versions.d.ts +2 -0
  75. package/dist/utils/resolve-versions.js +78 -0
  76. package/dist/utils/schema-generator.d.ts +9 -0
  77. package/dist/utils/schema-generator.js +209 -0
  78. package/dist/utils/serialize-inspector-state.d.ts +59 -22
  79. package/dist/utils/serialize-inspector-state.js +92 -20
  80. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  81. package/dist/utils/serialize-mcp-json.js +99 -0
  82. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  83. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  84. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  85. package/dist/utils/serialize-openapi-json.js +151 -0
  86. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  87. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  88. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
  89. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
  90. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
  91. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  92. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  93. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  94. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  95. package/dist/utils/workflow/graph/index.d.ts +2 -0
  96. package/dist/utils/workflow/graph/index.js +2 -0
  97. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
  98. package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
  99. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
  100. package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
  101. package/dist/visit.js +11 -6
  102. package/package.json +14 -4
  103. package/src/add/add-ai-agent.ts +468 -0
  104. package/src/add/add-channel.ts +82 -79
  105. package/src/add/add-cli.ts +49 -20
  106. package/src/add/add-file-with-factory.ts +2 -0
  107. package/src/add/add-functions.ts +330 -86
  108. package/src/add/add-http-route.ts +245 -88
  109. package/src/add/add-http-routes.ts +228 -0
  110. package/src/add/add-keyed-wiring.ts +151 -0
  111. package/src/add/add-mcp-prompt.ts +26 -15
  112. package/src/add/add-mcp-resource.ts +27 -15
  113. package/src/add/add-middleware.ts +482 -80
  114. package/src/add/add-permission.ts +199 -40
  115. package/src/add/add-queue-worker.ts +24 -19
  116. package/src/add/add-rpc-invocations.ts +17 -0
  117. package/src/add/add-schedule.ts +16 -11
  118. package/src/add/add-secret.ts +140 -0
  119. package/src/add/add-trigger.ts +154 -0
  120. package/src/add/add-variable.ts +9 -0
  121. package/src/add/add-workflow-graph.ts +180 -522
  122. package/src/add/add-workflow.ts +5 -4
  123. package/src/error-codes.ts +24 -1
  124. package/src/index.ts +22 -13
  125. package/src/inspector.ts +129 -17
  126. package/src/schema-generator.ts +1 -0
  127. package/src/types-map.ts +12 -1
  128. package/src/types.ts +175 -58
  129. package/src/utils/compute-required-schemas.ts +49 -0
  130. package/src/utils/contract-hashes.test.ts +528 -0
  131. package/src/utils/contract-hashes.ts +290 -0
  132. package/src/utils/custom-types-generator.ts +88 -0
  133. package/src/utils/detect-schema-vendor.ts +90 -0
  134. package/src/utils/ensure-function-metadata.ts +324 -7
  135. package/src/utils/extract-function-name.ts +101 -351
  136. package/src/utils/extract-services.ts +35 -2
  137. package/src/utils/filter-inspector-state.test.ts +34 -20
  138. package/src/utils/filter-inspector-state.ts +140 -31
  139. package/src/utils/get-property-value.ts +42 -4
  140. package/src/utils/hash.ts +26 -0
  141. package/src/utils/middleware.test.ts +204 -0
  142. package/src/utils/middleware.ts +129 -67
  143. package/src/utils/permissions.test.ts +35 -12
  144. package/src/utils/permissions.ts +10 -10
  145. package/src/utils/post-process.ts +283 -43
  146. package/src/utils/resolve-external-package.ts +42 -0
  147. package/src/utils/resolve-function-types.ts +42 -0
  148. package/src/utils/resolve-identifier.ts +46 -0
  149. package/src/utils/resolve-versions.test.ts +249 -0
  150. package/src/utils/resolve-versions.ts +105 -0
  151. package/src/utils/schema-generator.ts +329 -0
  152. package/src/utils/serialize-inspector-state.ts +163 -40
  153. package/src/utils/serialize-mcp-json.ts +145 -0
  154. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  155. package/src/utils/serialize-openapi-json.ts +277 -0
  156. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  157. package/src/utils/test-data/inspector-state.json +69 -66
  158. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
  159. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +24 -4
  160. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
  161. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  162. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  163. package/src/utils/workflow/graph/index.ts +5 -0
  164. package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
  165. package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
  166. package/src/visit.ts +12 -6
  167. package/tsconfig.tsbuildinfo +1 -1
  168. package/dist/add/add-forge-credential.d.ts +0 -8
  169. package/dist/add/add-forge-credential.js +0 -77
  170. package/dist/add/add-forge-node.d.ts +0 -7
  171. package/dist/add/add-forge-node.js +0 -77
  172. package/dist/add/add-mcp-tool.d.ts +0 -2
  173. package/dist/add/add-mcp-tool.js +0 -81
  174. package/dist/utils/extract-service-metadata.d.ts +0 -19
  175. package/dist/utils/extract-service-metadata.js +0 -244
  176. package/dist/utils/write-service-metadata.d.ts +0 -13
  177. package/dist/utils/write-service-metadata.js +0 -37
  178. package/src/add/add-forge-credential.ts +0 -119
  179. package/src/add/add-forge-node.ts +0 -132
  180. package/src/add/add-mcp-tool.ts +0 -141
  181. package/src/utils/extract-service-metadata.ts +0 -353
  182. package/src/utils/write-service-metadata.ts +0 -51
@@ -0,0 +1,28 @@
1
+ const serializeGroupMap = (groupMap) => {
2
+ const result = {};
3
+ for (const [key, meta] of groupMap.entries()) {
4
+ result[key] = {
5
+ exportName: meta.exportName,
6
+ sourceFile: meta.sourceFile,
7
+ position: meta.position,
8
+ services: meta.services,
9
+ count: meta.count,
10
+ instanceIds: meta.instanceIds,
11
+ isFactory: meta.isFactory,
12
+ };
13
+ }
14
+ return result;
15
+ };
16
+ export const serializeMiddlewareGroupsMeta = (state) => {
17
+ return {
18
+ definitions: state.middleware.definitions,
19
+ instances: state.middleware.instances,
20
+ httpGroups: serializeGroupMap(state.http.routeMiddleware),
21
+ tagGroups: serializeGroupMap(state.middleware.tagMiddleware),
22
+ channelMiddleware: {
23
+ definitions: state.channelMiddleware.definitions,
24
+ instances: state.channelMiddleware.instances,
25
+ tagGroups: serializeGroupMap(state.channelMiddleware.tagMiddleware),
26
+ },
27
+ };
28
+ };
@@ -0,0 +1,85 @@
1
+ import type { FunctionsMeta } from '@pikku/core';
2
+ import type { HTTPWiringsMeta } from '@pikku/core/http';
3
+ import type { InspectorLogger } from '../types.js';
4
+ interface ErrorDetails {
5
+ status: number;
6
+ message: string;
7
+ }
8
+ interface OpenAPISpec {
9
+ openapi: string;
10
+ info: {
11
+ title: string;
12
+ version: string;
13
+ description?: string;
14
+ termsOfService?: string;
15
+ contact?: {
16
+ name?: string;
17
+ url?: string;
18
+ email?: string;
19
+ };
20
+ license?: {
21
+ name: string;
22
+ url?: string;
23
+ };
24
+ };
25
+ servers: {
26
+ url: string;
27
+ description?: string;
28
+ }[];
29
+ paths: Record<string, any>;
30
+ components: {
31
+ schemas: Record<string, any>;
32
+ responses?: Record<string, any>;
33
+ parameters?: Record<string, any>;
34
+ examples?: Record<string, any>;
35
+ requestBodies?: Record<string, any>;
36
+ headers?: Record<string, any>;
37
+ securitySchemes?: Record<string, any>;
38
+ };
39
+ security?: {
40
+ [key: string]: any[];
41
+ }[];
42
+ tags?: {
43
+ name: string;
44
+ description?: string;
45
+ }[];
46
+ externalDocs?: {
47
+ description?: string;
48
+ url: string;
49
+ };
50
+ }
51
+ export interface OpenAPISpecInfo {
52
+ info: {
53
+ title: string;
54
+ version: string;
55
+ description: string;
56
+ termsOfService?: string;
57
+ contact?: {
58
+ name?: string;
59
+ url?: string;
60
+ email?: string;
61
+ };
62
+ license?: {
63
+ name: string;
64
+ url?: string;
65
+ };
66
+ };
67
+ servers: {
68
+ url: string;
69
+ description?: string;
70
+ }[];
71
+ tags?: {
72
+ name: string;
73
+ description?: string;
74
+ }[];
75
+ externalDocs?: {
76
+ description?: string;
77
+ url: string;
78
+ };
79
+ securitySchemes?: Record<string, any>;
80
+ security?: {
81
+ [key: string]: any[];
82
+ }[];
83
+ }
84
+ export declare function generateOpenAPISpec(logger: InspectorLogger, functionsMeta: FunctionsMeta, httpMeta: HTTPWiringsMeta, schemas: Record<string, any>, additionalInfo: OpenAPISpecInfo, errors?: Map<any, ErrorDetails>): Promise<OpenAPISpec>;
85
+ export {};
@@ -0,0 +1,151 @@
1
+ import _convertSchema from '@openapi-contrib/json-schema-to-openapi-schema';
2
+ const convertSchema = 'default' in _convertSchema ? _convertSchema.default : _convertSchema;
3
+ const getErrorResponseForConstructorName = (constructorName, errors) => {
4
+ const entries = Array.from(errors.entries());
5
+ const foundError = entries.find(([e]) => e.name === constructorName);
6
+ if (foundError) {
7
+ return foundError[1];
8
+ }
9
+ return undefined;
10
+ };
11
+ const convertSchemasToBodyPayloads = async (functionsMeta, routesMeta, schemas) => {
12
+ const requiredSchemas = new Set();
13
+ for (const routeMeta of Object.values(routesMeta)) {
14
+ for (const { inputTypes, pikkuFuncId } of Object.values(routeMeta)) {
15
+ const output = functionsMeta[pikkuFuncId]?.outputs?.[0];
16
+ if (inputTypes?.body) {
17
+ requiredSchemas.add(inputTypes?.body);
18
+ }
19
+ if (output) {
20
+ requiredSchemas.add(output);
21
+ }
22
+ }
23
+ }
24
+ const convertedEntries = await Promise.all(Object.entries(schemas).map(async ([key, schema]) => {
25
+ if (requiredSchemas.has(key)) {
26
+ const convertedSchema = await convertSchema(schema, {
27
+ convertUnreferencedDefinitions: false,
28
+ dereference: { circular: 'ignore' },
29
+ });
30
+ return [key, convertedSchema];
31
+ }
32
+ return;
33
+ }));
34
+ return Object.fromEntries(convertedEntries.filter((s) => !!s));
35
+ };
36
+ export async function generateOpenAPISpec(logger, functionsMeta, httpMeta, schemas, additionalInfo, errors) {
37
+ const paths = {};
38
+ const errorsMap = errors ?? new Map();
39
+ for (const routeMeta of Object.values(httpMeta)) {
40
+ for (const meta of Object.values(routeMeta)) {
41
+ const { route, method, inputTypes, pikkuFuncId, params, query, errors, description, tags, } = meta;
42
+ const functionMeta = functionsMeta[pikkuFuncId];
43
+ if (!functionMeta) {
44
+ logger.error(`• No function metadata found for '${pikkuFuncId}' in route '${route}'.`);
45
+ continue;
46
+ }
47
+ const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined;
48
+ const path = route.replace(/:(\w+)/g, '{$1}');
49
+ if (!paths[path]) {
50
+ paths[path] = {};
51
+ }
52
+ const responses = {};
53
+ errors?.forEach((error) => {
54
+ const errorResponse = getErrorResponseForConstructorName(error, errorsMap);
55
+ if (errorResponse) {
56
+ responses[errorResponse.status] = {
57
+ description: errorResponse.message,
58
+ };
59
+ }
60
+ });
61
+ const operation = {
62
+ description: description ||
63
+ `This endpoint handles the ${method.toUpperCase()} request for the route ${route}.`,
64
+ tags: tags || [route.split('/')[1] || 'default'],
65
+ parameters: [],
66
+ responses: {
67
+ ...responses,
68
+ '200': {
69
+ description: 'Successful response',
70
+ content: output
71
+ ? {
72
+ 'application/json': {
73
+ schema: typeof output === 'string' &&
74
+ ['boolean', 'string', 'number'].includes(output)
75
+ ? { type: output }
76
+ : { $ref: `#/components/schemas/${output}` },
77
+ },
78
+ }
79
+ : undefined,
80
+ },
81
+ },
82
+ };
83
+ const bodyType = inputTypes?.body;
84
+ if (bodyType) {
85
+ operation.requestBody = {
86
+ required: true,
87
+ content: {
88
+ 'application/json': {
89
+ schema: typeof bodyType === 'string' &&
90
+ ['boolean', 'string', 'number'].includes(bodyType)
91
+ ? { type: bodyType }
92
+ : { $ref: `#/components/schemas/${bodyType}` },
93
+ },
94
+ },
95
+ };
96
+ }
97
+ if (params) {
98
+ operation.parameters = params.map((param) => ({
99
+ name: param,
100
+ in: 'path',
101
+ required: true,
102
+ schema: { type: 'string' },
103
+ }));
104
+ }
105
+ if (query) {
106
+ operation.parameters.push(...query.map((query) => ({
107
+ name: query,
108
+ in: 'query',
109
+ required: false,
110
+ schema: { type: 'string' },
111
+ })));
112
+ }
113
+ paths[path][method] = operation;
114
+ }
115
+ }
116
+ return {
117
+ openapi: '3.1.0',
118
+ info: additionalInfo.info,
119
+ servers: additionalInfo.servers,
120
+ paths,
121
+ components: {
122
+ schemas: await convertSchemasToBodyPayloads(functionsMeta, httpMeta, schemas),
123
+ responses: {},
124
+ parameters: {},
125
+ examples: {},
126
+ requestBodies: {},
127
+ headers: {},
128
+ securitySchemes: additionalInfo.securitySchemes || {
129
+ ApiKeyAuth: {
130
+ type: 'apiKey',
131
+ in: 'header',
132
+ name: 'x-api-key',
133
+ },
134
+ BearerAuth: {
135
+ type: 'http',
136
+ scheme: 'bearer',
137
+ },
138
+ },
139
+ },
140
+ security: additionalInfo.security || [
141
+ {
142
+ ApiKeyAuth: [],
143
+ },
144
+ {
145
+ BearerAuth: [],
146
+ },
147
+ ],
148
+ tags: additionalInfo.tags,
149
+ externalDocs: additionalInfo.externalDocs,
150
+ };
151
+ }
@@ -0,0 +1,6 @@
1
+ import type { InspectorState } from '../types.js';
2
+ export declare const serializePermissionsGroupsMeta: (state: InspectorState) => {
3
+ definitions: Record<string, import("../types.js").InspectorPermissionDefinition>;
4
+ httpGroups: Record<string, any>;
5
+ tagGroups: Record<string, any>;
6
+ };
@@ -0,0 +1,31 @@
1
+ export const serializePermissionsGroupsMeta = (state) => {
2
+ const httpGroups = {};
3
+ for (const [pattern, meta] of state.http.routePermissions.entries()) {
4
+ httpGroups[pattern] = {
5
+ exportName: meta.exportName,
6
+ sourceFile: meta.sourceFile,
7
+ position: meta.position,
8
+ services: meta.services,
9
+ count: meta.count,
10
+ instanceIds: meta.instanceIds,
11
+ isFactory: meta.isFactory,
12
+ };
13
+ }
14
+ const tagGroups = {};
15
+ for (const [tag, meta] of state.permissions.tagPermissions.entries()) {
16
+ tagGroups[tag] = {
17
+ exportName: meta.exportName,
18
+ sourceFile: meta.sourceFile,
19
+ position: meta.position,
20
+ services: meta.services,
21
+ count: meta.count,
22
+ instanceIds: meta.instanceIds,
23
+ isFactory: meta.isFactory,
24
+ };
25
+ }
26
+ return {
27
+ definitions: state.permissions.definitions,
28
+ httpGroups,
29
+ tagGroups,
30
+ };
31
+ };
@@ -89,6 +89,15 @@ function valueToCode(value, itemVar) {
89
89
  if (isTemplateRef(value)) {
90
90
  return templateRefToCode(value, itemVar);
91
91
  }
92
+ if (Array.isArray(value)) {
93
+ const elements = value.map((v) => valueToCode(v, itemVar));
94
+ return `[${elements.join(', ')}]`;
95
+ }
96
+ if (typeof value === 'object' && value !== null) {
97
+ const entries = Object.entries(value);
98
+ const props = entries.map(([k, v]) => `${k}: ${valueToCode(v, itemVar)}`);
99
+ return `{ ${props.join(', ')} }`;
100
+ }
92
101
  return JSON.stringify(value);
93
102
  }
94
103
  /**
@@ -626,6 +635,26 @@ function templateRefToGraphCode(tmpl, outputVarToNodeId) {
626
635
  .replace(/\r/g, '\\r');
627
636
  return `template('${templateStr}', [${refs.join(', ')}])`;
628
637
  }
638
+ function valueToGraphCode(value, outputVarToNodeId, refTracker) {
639
+ if (isDataRef(value)) {
640
+ refTracker.hasRefs = true;
641
+ return dataRefToGraphRef(value, outputVarToNodeId);
642
+ }
643
+ if (isTemplateRef(value)) {
644
+ refTracker.hasRefs = true;
645
+ return templateRefToGraphCode(value, outputVarToNodeId);
646
+ }
647
+ if (Array.isArray(value)) {
648
+ const elements = value.map((v) => valueToGraphCode(v, outputVarToNodeId, refTracker));
649
+ return `[${elements.join(', ')}]`;
650
+ }
651
+ if (typeof value === 'object' && value !== null) {
652
+ const entries = Object.entries(value);
653
+ const props = entries.map(([k, v]) => `${k}: ${valueToGraphCode(v, outputVarToNodeId, refTracker)}`);
654
+ return `{ ${props.join(', ')} }`;
655
+ }
656
+ return JSON.stringify(value);
657
+ }
629
658
  /**
630
659
  * Convert input object to graph input code using ref()
631
660
  * @param input - The input mapping
@@ -635,73 +664,15 @@ function inputToGraphCode(input, outputVarToNodeId) {
635
664
  const entries = Object.entries(input);
636
665
  if (entries.length === 0)
637
666
  return { hasRefs: false, code: '{}' };
638
- let hasRefs = false;
667
+ const refTracker = { hasRefs: false };
639
668
  const lines = entries.map(([key, value]) => {
640
- if (isDataRef(value)) {
641
- hasRefs = true;
642
- return ` ${key}: ${dataRefToGraphRef(value, outputVarToNodeId)},`;
643
- }
644
- if (isTemplateRef(value)) {
645
- hasRefs = true;
646
- return ` ${key}: ${templateRefToGraphCode(value, outputVarToNodeId)},`;
647
- }
648
- return ` ${key}: ${JSON.stringify(value)},`;
669
+ return ` ${key}: ${valueToGraphCode(value, outputVarToNodeId, refTracker)},`;
649
670
  });
650
671
  return {
651
- hasRefs,
672
+ hasRefs: refTracker.hasRefs,
652
673
  code: `{\n${lines.join('\n')}\n }`,
653
674
  };
654
675
  }
655
- /**
656
- * Serialize wires to code
657
- */
658
- function wiresToCode(wires) {
659
- if (!wires || Object.keys(wires).length === 0)
660
- return '{}';
661
- const parts = [];
662
- if (wires.http && wires.http.length > 0) {
663
- const httpItems = wires.http.map((h) => `{ route: '${h.route}', method: '${h.method}', startNode: '${h.startNode}' }`);
664
- parts.push(`http: [${httpItems.join(', ')}]`);
665
- }
666
- if (wires.channel && wires.channel.length > 0) {
667
- const channelItems = wires.channel.map((c) => {
668
- const channelParts = [`name: '${c.name}'`];
669
- if (c.onConnect)
670
- channelParts.push(`onConnect: '${c.onConnect}'`);
671
- if (c.onDisconnect)
672
- channelParts.push(`onDisconnect: '${c.onDisconnect}'`);
673
- if (c.onMessage)
674
- channelParts.push(`onMessage: '${c.onMessage}'`);
675
- return `{ ${channelParts.join(', ')} }`;
676
- });
677
- parts.push(`channel: [${channelItems.join(', ')}]`);
678
- }
679
- if (wires.queue && wires.queue.length > 0) {
680
- const queueItems = wires.queue.map((q) => `{ name: '${q.name}', startNode: '${q.startNode}' }`);
681
- parts.push(`queue: [${queueItems.join(', ')}]`);
682
- }
683
- if (wires.cli && wires.cli.length > 0) {
684
- const cliItems = wires.cli.map((c) => `{ command: '${c.command}', startNode: '${c.startNode}' }`);
685
- parts.push(`cli: [${cliItems.join(', ')}]`);
686
- }
687
- if (wires.schedule && wires.schedule.length > 0) {
688
- const scheduleItems = wires.schedule.map((s) => {
689
- const scheduleParts = [];
690
- if (s.cron)
691
- scheduleParts.push(`cron: '${s.cron}'`);
692
- if (s.interval)
693
- scheduleParts.push(`interval: '${s.interval}'`);
694
- scheduleParts.push(`startNode: '${s.startNode}'`);
695
- return `{ ${scheduleParts.join(', ')} }`;
696
- });
697
- parts.push(`schedule: [${scheduleItems.join(', ')}]`);
698
- }
699
- if (wires.trigger && wires.trigger.length > 0) {
700
- const triggerItems = wires.trigger.map((t) => `{ name: '${t.name}', startNode: '${t.startNode}' }`);
701
- parts.push(`trigger: [${triggerItems.join(', ')}]`);
702
- }
703
- return `{ ${parts.join(', ')} }`;
704
- }
705
676
  /**
706
677
  * Check if a node is a flow node (non-RPC control flow)
707
678
  */
@@ -740,7 +711,7 @@ export function deserializeGraphWorkflow(workflow, options = {}) {
740
711
  const { pikkuImportPath = '../.pikku/workflow/pikku-workflow-types.gen.js' } = options;
741
712
  const lines = [];
742
713
  // Import statement
743
- lines.push(`import { pikkuWorkflowGraph, wireWorkflow } from '${pikkuImportPath}'`);
714
+ lines.push(`import { pikkuWorkflowGraph } from '${pikkuImportPath}'`);
744
715
  lines.push('');
745
716
  // Add description as comment if present
746
717
  if (workflow.description) {
@@ -811,26 +782,7 @@ export function deserializeGraphWorkflow(workflow, options = {}) {
811
782
  nodeConfigs.push(` ${nodeId}: {\n ${configParts.join(',\n ')},\n }`);
812
783
  }
813
784
  }
814
- // Compute entry node (first node with no incoming edges from RPC nodes)
815
- const rpcNodeIds = new Set(Object.keys(nodeRpcMap));
816
- const nodesWithIncomingEdges = new Set();
817
- for (const [nodeId, node] of Object.entries(workflow.nodes)) {
818
- if (!rpcNodeIds.has(nodeId))
819
- continue;
820
- if ('next' in node && node.next) {
821
- const nextId = node.next;
822
- // Follow through flow nodes to find the actual next RPC node
823
- const actualNextId = flowNodeIds.has(nextId)
824
- ? findNextRpcNode(nextId, workflow.nodes, flowNodeIds)
825
- : nextId;
826
- if (actualNextId && rpcNodeIds.has(actualNextId)) {
827
- nodesWithIncomingEdges.add(actualNextId);
828
- }
829
- }
830
- }
831
- // Entry node is the first RPC node with no incoming edges
832
- const entryNode = Object.keys(nodeRpcMap).find((id) => !nodesWithIncomingEdges.has(id));
833
- // Generate the pikkuWorkflowGraph call
785
+ // Generate the pikkuWorkflowGraph call (builds graph and registers with core)
834
786
  lines.push(`export const ${workflow.name} = pikkuWorkflowGraph({`);
835
787
  lines.push(` name: '${workflow.name}',`);
836
788
  if (workflow.description) {
@@ -851,12 +803,6 @@ export function deserializeGraphWorkflow(workflow, options = {}) {
851
803
  else {
852
804
  lines.push(` nodes: {},`);
853
805
  }
854
- // Generate wires with api entry point
855
- if (entryNode) {
856
- lines.push(` wires: {`);
857
- lines.push(` api: '${entryNode}',`);
858
- lines.push(` },`);
859
- }
860
806
  // Generate config (node configurations)
861
807
  if (nodeConfigs.length > 0) {
862
808
  lines.push(` config: {`);
@@ -865,20 +811,6 @@ export function deserializeGraphWorkflow(workflow, options = {}) {
865
811
  }
866
812
  lines.push(`})`);
867
813
  lines.push('');
868
- // Always generate wireWorkflow to register the graph workflow
869
- // (needed for testing even without explicit wires)
870
- if (workflow.wires && Object.keys(workflow.wires).length > 0) {
871
- lines.push(`wireWorkflow({`);
872
- lines.push(` wires: ${wiresToCode(workflow.wires)},`);
873
- lines.push(` graph: ${workflow.name},`);
874
- lines.push(`})`);
875
- }
876
- else {
877
- lines.push(`wireWorkflow({`);
878
- lines.push(` graph: ${workflow.name},`);
879
- lines.push(`})`);
880
- }
881
- lines.push('');
882
814
  return lines.join('\n');
883
815
  }
884
816
  /**
@@ -1112,6 +1112,25 @@ function extractInputSources(node, context) {
1112
1112
  }
1113
1113
  return Object.keys(inputs).length > 0 ? inputs : undefined;
1114
1114
  }
1115
+ function inputSourceToInlineValue(source) {
1116
+ switch (source.from) {
1117
+ case 'literal':
1118
+ return source.value;
1119
+ case 'input':
1120
+ return { $ref: 'trigger', path: source.path };
1121
+ case 'outputVar':
1122
+ return { $ref: source.name, path: source.path };
1123
+ case 'item':
1124
+ return { $ref: '$item', path: source.path };
1125
+ case 'template':
1126
+ return {
1127
+ $template: {
1128
+ parts: source.parts,
1129
+ expressions: source.expressions.map(inputSourceToInlineValue),
1130
+ },
1131
+ };
1132
+ }
1133
+ }
1115
1134
  /**
1116
1135
  * Extract a single input source
1117
1136
  */
@@ -1174,8 +1193,8 @@ function extractInputSource(node, context) {
1174
1193
  if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
1175
1194
  const propName = prop.name.text;
1176
1195
  const propSource = extractInputSource(prop.initializer, context);
1177
- if (propSource && propSource.from === 'literal') {
1178
- obj[propName] = propSource.value;
1196
+ if (propSource) {
1197
+ obj[propName] = inputSourceToInlineValue(propSource);
1179
1198
  }
1180
1199
  }
1181
1200
  }
@@ -1186,8 +1205,8 @@ function extractInputSource(node, context) {
1186
1205
  const arr = [];
1187
1206
  for (const elem of node.elements) {
1188
1207
  const elemSource = extractInputSource(elem, context);
1189
- if (elemSource && elemSource.from === 'literal') {
1190
- arr.push(elemSource.value);
1208
+ if (elemSource) {
1209
+ arr.push(inputSourceToInlineValue(elemSource));
1191
1210
  }
1192
1211
  }
1193
1212
  return { from: 'literal', value: arr };
@@ -1,3 +1,9 @@
1
+ function makeNodeId(step, index, prefix) {
2
+ if ('stepName' in step && step.stepName) {
3
+ return step.stepName;
4
+ }
5
+ return `${prefix}_${index}`;
6
+ }
1
7
  /**
2
8
  * Check if a node is a terminal flow (no next step should follow)
3
9
  */
@@ -41,14 +47,15 @@ function convertInputSource(source) {
41
47
  * Convert a single DSL step to graph node(s)
42
48
  */
43
49
  function convertStepToNode(step, index, steps, nodeIdPrefix = 'step') {
44
- const nodeId = `${nodeIdPrefix}_${index}`;
45
- const nextNodeId = index < steps.length - 1 ? `${nodeIdPrefix}_${index + 1}` : undefined;
50
+ const nodeId = makeNodeId(step, index, nodeIdPrefix);
51
+ const nextNodeId = index < steps.length - 1
52
+ ? makeNodeId(steps[index + 1], index + 1, nodeIdPrefix)
53
+ : undefined;
46
54
  switch (step.type) {
47
55
  case 'rpc': {
48
56
  const node = {
49
57
  nodeId,
50
58
  rpcName: step.rpcName,
51
- stepName: step.stepName,
52
59
  next: nextNodeId,
53
60
  };
54
61
  if (step.inputs) {
@@ -78,7 +85,6 @@ function convertStepToNode(step, index, steps, nodeIdPrefix = 'step') {
78
85
  const node = {
79
86
  nodeId,
80
87
  flow: 'sleep',
81
- stepName: step.stepName,
82
88
  duration: step.duration,
83
89
  next: nextNodeId,
84
90
  };
@@ -88,7 +94,6 @@ function convertStepToNode(step, index, steps, nodeIdPrefix = 'step') {
88
94
  const node = {
89
95
  nodeId,
90
96
  flow: 'inline',
91
- stepName: step.stepName,
92
97
  description: step.description,
93
98
  next: nextNodeId,
94
99
  };
@@ -201,7 +206,6 @@ function convertStepToNode(step, index, steps, nodeIdPrefix = 'step') {
201
206
  const node = {
202
207
  nodeId,
203
208
  flow: 'fanout',
204
- stepName: step.stepName,
205
209
  sourceVar: step.sourceVar,
206
210
  itemVar: step.itemVar,
207
211
  mode: step.mode,
@@ -286,20 +290,18 @@ export function convertDslToGraph(workflowName, meta) {
286
290
  for (const node of nodes) {
287
291
  nodesRecord[node.nodeId] = node;
288
292
  }
289
- // Find entry nodes (step_0 is always entry for sequential workflows)
290
- const entryNodeIds = nodes.length > 0 ? ['step_0'] : [];
293
+ const entryNodeIds = nodes.length > 0 ? [nodes[0].nodeId] : [];
291
294
  // Determine source type based on dsl flag:
292
295
  // - dsl === true: pure DSL workflow, can be serialized
293
296
  // - dsl === false: complex workflow with inline steps, not serializable
294
297
  const source = meta.dsl === false ? 'complex' : 'dsl';
295
298
  return {
296
299
  name: workflowName,
297
- pikkuFuncName: meta.pikkuFuncName,
300
+ pikkuFuncId: meta.pikkuFuncId,
298
301
  source,
299
302
  description: meta.description,
300
303
  tags: meta.tags,
301
304
  context: meta.context,
302
- wires: {}, // DSL workflows don't have explicit wires in meta
303
305
  nodes: nodesRecord,
304
306
  entryNodeIds,
305
307
  };
@@ -0,0 +1,3 @@
1
+ import type { InspectorState } from '../../../types.js';
2
+ export declare function finalizeWorkflowHelperTypes(state: InspectorState): void;
3
+ export declare function finalizeWorkflowWires(state: InspectorState): void;