@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.
- package/CHANGELOG.md +11 -1
- package/OPTIMIZATION-PLAN.md +195 -0
- package/dist/add/add-ai-agent.d.ts +2 -0
- package/dist/add/add-ai-agent.js +314 -0
- package/dist/add/add-channel.js +69 -61
- package/dist/add/add-cli.js +36 -18
- package/dist/add/add-file-with-factory.js +2 -0
- package/dist/add/add-functions.js +250 -75
- package/dist/add/add-http-route.d.ts +19 -10
- package/dist/add/add-http-route.js +152 -66
- package/dist/add/add-http-routes.d.ts +5 -0
- package/dist/add/add-http-routes.js +159 -0
- package/dist/add/add-keyed-wiring.d.ts +12 -0
- package/dist/add/add-keyed-wiring.js +97 -0
- package/dist/add/add-mcp-prompt.js +14 -9
- package/dist/add/add-mcp-resource.js +14 -9
- package/dist/add/add-middleware.d.ts +1 -4
- package/dist/add/add-middleware.js +364 -79
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +152 -40
- package/dist/add/add-queue-worker.js +18 -12
- package/dist/add/add-rpc-invocations.js +14 -0
- package/dist/add/add-schedule.js +11 -5
- package/dist/add/add-secret.d.ts +3 -0
- package/dist/add/add-secret.js +82 -0
- package/dist/add/add-trigger.d.ts +2 -0
- package/dist/add/add-trigger.js +87 -0
- package/dist/add/add-variable.d.ts +1 -0
- package/dist/add/add-variable.js +8 -0
- package/dist/add/add-workflow-graph.d.ts +3 -2
- package/dist/add/add-workflow-graph.js +143 -406
- package/dist/add/add-workflow.js +6 -4
- package/dist/error-codes.d.ts +14 -1
- package/dist/error-codes.js +19 -1
- package/dist/index.d.ts +9 -8
- package/dist/index.js +5 -4
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.js +91 -14
- package/dist/schema-generator.d.ts +1 -0
- package/dist/schema-generator.js +1 -0
- package/dist/types-map.js +10 -1
- package/dist/types.d.ts +163 -39
- package/dist/utils/compute-required-schemas.d.ts +4 -0
- package/dist/utils/compute-required-schemas.js +41 -0
- package/dist/utils/contract-hashes.d.ts +35 -0
- package/dist/utils/contract-hashes.js +202 -0
- package/dist/utils/custom-types-generator.d.ts +9 -0
- package/dist/utils/custom-types-generator.js +71 -0
- package/dist/utils/detect-schema-vendor.d.ts +22 -0
- package/dist/utils/detect-schema-vendor.js +76 -0
- package/dist/utils/ensure-function-metadata.d.ts +5 -2
- package/dist/utils/ensure-function-metadata.js +220 -6
- package/dist/utils/extract-function-name.d.ts +5 -16
- package/dist/utils/extract-function-name.js +86 -291
- package/dist/utils/extract-services.d.ts +2 -1
- package/dist/utils/extract-services.js +25 -1
- package/dist/utils/filter-inspector-state.js +107 -23
- package/dist/utils/get-property-value.d.ts +6 -1
- package/dist/utils/get-property-value.js +28 -3
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/utils/middleware.d.ts +7 -30
- package/dist/utils/middleware.js +80 -66
- package/dist/utils/permissions.d.ts +2 -2
- package/dist/utils/permissions.js +10 -10
- package/dist/utils/post-process.d.ts +9 -10
- package/dist/utils/post-process.js +231 -24
- package/dist/utils/resolve-external-package.d.ts +12 -0
- package/dist/utils/resolve-external-package.js +34 -0
- package/dist/utils/resolve-function-types.d.ts +6 -0
- package/dist/utils/resolve-function-types.js +29 -0
- package/dist/utils/resolve-identifier.d.ts +10 -0
- package/dist/utils/resolve-identifier.js +36 -0
- package/dist/utils/resolve-versions.d.ts +2 -0
- package/dist/utils/resolve-versions.js +78 -0
- package/dist/utils/schema-generator.d.ts +9 -0
- package/dist/utils/schema-generator.js +209 -0
- package/dist/utils/serialize-inspector-state.d.ts +59 -22
- package/dist/utils/serialize-inspector-state.js +92 -20
- package/dist/utils/serialize-mcp-json.d.ts +2 -0
- package/dist/utils/serialize-mcp-json.js +99 -0
- package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
- package/dist/utils/serialize-middleware-groups-meta.js +28 -0
- package/dist/utils/serialize-openapi-json.d.ts +85 -0
- package/dist/utils/serialize-openapi-json.js +151 -0
- package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
- package/dist/utils/serialize-permissions-groups-meta.js +31 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
- package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
- package/dist/utils/workflow/graph/index.d.ts +2 -0
- package/dist/utils/workflow/graph/index.js +2 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
- package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
- package/dist/visit.js +11 -6
- package/package.json +14 -4
- package/src/add/add-ai-agent.ts +468 -0
- package/src/add/add-channel.ts +82 -79
- package/src/add/add-cli.ts +49 -20
- package/src/add/add-file-with-factory.ts +2 -0
- package/src/add/add-functions.ts +330 -86
- package/src/add/add-http-route.ts +245 -88
- package/src/add/add-http-routes.ts +228 -0
- package/src/add/add-keyed-wiring.ts +151 -0
- package/src/add/add-mcp-prompt.ts +26 -15
- package/src/add/add-mcp-resource.ts +27 -15
- package/src/add/add-middleware.ts +482 -80
- package/src/add/add-permission.ts +199 -40
- package/src/add/add-queue-worker.ts +24 -19
- package/src/add/add-rpc-invocations.ts +17 -0
- package/src/add/add-schedule.ts +16 -11
- package/src/add/add-secret.ts +140 -0
- package/src/add/add-trigger.ts +154 -0
- package/src/add/add-variable.ts +9 -0
- package/src/add/add-workflow-graph.ts +180 -522
- package/src/add/add-workflow.ts +5 -4
- package/src/error-codes.ts +24 -1
- package/src/index.ts +22 -13
- package/src/inspector.ts +129 -17
- package/src/schema-generator.ts +1 -0
- package/src/types-map.ts +12 -1
- package/src/types.ts +175 -58
- package/src/utils/compute-required-schemas.ts +49 -0
- package/src/utils/contract-hashes.test.ts +528 -0
- package/src/utils/contract-hashes.ts +290 -0
- package/src/utils/custom-types-generator.ts +88 -0
- package/src/utils/detect-schema-vendor.ts +90 -0
- package/src/utils/ensure-function-metadata.ts +324 -7
- package/src/utils/extract-function-name.ts +101 -351
- package/src/utils/extract-services.ts +35 -2
- package/src/utils/filter-inspector-state.test.ts +34 -20
- package/src/utils/filter-inspector-state.ts +140 -31
- package/src/utils/get-property-value.ts +42 -4
- package/src/utils/hash.ts +26 -0
- package/src/utils/middleware.test.ts +204 -0
- package/src/utils/middleware.ts +129 -67
- package/src/utils/permissions.test.ts +35 -12
- package/src/utils/permissions.ts +10 -10
- package/src/utils/post-process.ts +283 -43
- package/src/utils/resolve-external-package.ts +42 -0
- package/src/utils/resolve-function-types.ts +42 -0
- package/src/utils/resolve-identifier.ts +46 -0
- package/src/utils/resolve-versions.test.ts +249 -0
- package/src/utils/resolve-versions.ts +105 -0
- package/src/utils/schema-generator.ts +329 -0
- package/src/utils/serialize-inspector-state.ts +163 -40
- package/src/utils/serialize-mcp-json.ts +145 -0
- package/src/utils/serialize-middleware-groups-meta.ts +33 -0
- package/src/utils/serialize-openapi-json.ts +277 -0
- package/src/utils/serialize-permissions-groups-meta.ts +35 -0
- package/src/utils/test-data/inspector-state.json +69 -66
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +24 -4
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
- package/src/utils/workflow/graph/index.ts +5 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
- package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
- package/src/visit.ts +12 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-forge-credential.d.ts +0 -8
- package/dist/add/add-forge-credential.js +0 -77
- package/dist/add/add-forge-node.d.ts +0 -7
- package/dist/add/add-forge-node.js +0 -77
- package/dist/add/add-mcp-tool.d.ts +0 -2
- package/dist/add/add-mcp-tool.js +0 -81
- package/dist/utils/extract-service-metadata.d.ts +0 -19
- package/dist/utils/extract-service-metadata.js +0 -244
- package/dist/utils/write-service-metadata.d.ts +0 -13
- package/dist/utils/write-service-metadata.js +0 -37
- package/src/add/add-forge-credential.ts +0 -119
- package/src/add/add-forge-node.ts +0 -132
- package/src/add/add-mcp-tool.ts +0 -141
- package/src/utils/extract-service-metadata.ts +0 -353
- 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
|
-
|
|
667
|
+
const refTracker = { hasRefs: false };
|
|
639
668
|
const lines = entries.map(([key, value]) => {
|
|
640
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
1178
|
-
obj[propName] = propSource
|
|
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
|
|
1190
|
-
arr.push(elemSource
|
|
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 =
|
|
45
|
-
const nextNodeId = index < steps.length - 1
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|