@pikku/cli 0.9.4 → 0.9.6
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 +22 -0
- package/dist/src/schema-generator.js +11 -9
- package/dist/src/serialize-pikku-types.d.ts +1 -1
- package/dist/src/serialize-pikku-types.js +42 -29
- package/dist/src/utils.js +1 -1
- package/dist/src/wirings/functions/pikku-command-function-types.js +1 -1
- package/dist/src/wirings/functions/pikku-function-types.js +1 -1
- package/dist/src/wirings/http/openapi-spec-generator.d.ts +1 -1
- package/dist/src/wirings/http/openapi-spec-generator.js +86 -79
- package/dist/src/wirings/http/serialize-typed-http-map.js +25 -21
- package/package.json +3 -3
- package/src/schema-generator.ts +12 -9
- package/src/serialize-pikku-types.ts +42 -28
- package/src/utils.ts +1 -1
- package/src/wirings/functions/pikku-command-function-types.ts +1 -0
- package/src/wirings/functions/pikku-function-types.ts +1 -0
- package/src/wirings/http/openapi-spec-generator.ts +95 -88
- package/src/wirings/http/serialize-typed-http-map.ts +31 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @pikku/cli
|
|
2
2
|
|
|
3
|
+
## 0.9.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6059c87: refactor: move PikkuPermission to pikkuPermission and same for middleware for api consistency to to improve future features
|
|
8
|
+
- 6db63bb: perf: changing http meta to a lookup map to reduce loops
|
|
9
|
+
- Updated dependencies [6059c87]
|
|
10
|
+
- Updated dependencies [6db63bb]
|
|
11
|
+
- Updated dependencies [74f8634]
|
|
12
|
+
- Updated dependencies [766fef1]
|
|
13
|
+
- @pikku/inspector@0.9.4
|
|
14
|
+
- @pikku/core@0.9.6
|
|
15
|
+
|
|
16
|
+
## 0.9.5
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- b443405: feat: adding middleware and functions by tags
|
|
21
|
+
- Updated dependencies [7e1f5b3]
|
|
22
|
+
- Updated dependencies [b443405]
|
|
23
|
+
- @pikku/core@0.9.5
|
|
24
|
+
|
|
3
25
|
## 0.9.4
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -12,15 +12,17 @@ export async function generateSchemas(logger, tsconfig, typesMap, functionMeta,
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
for (const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
for (const wiringRoutes of Object.values(httpWiringsMeta)) {
|
|
16
|
+
for (const { inputTypes } of Object.values(wiringRoutes)) {
|
|
17
|
+
if (inputTypes?.body) {
|
|
18
|
+
schemasSet.add(inputTypes.body);
|
|
19
|
+
}
|
|
20
|
+
if (inputTypes?.query) {
|
|
21
|
+
schemasSet.add(inputTypes.query);
|
|
22
|
+
}
|
|
23
|
+
if (inputTypes?.params) {
|
|
24
|
+
schemasSet.add(inputTypes.params);
|
|
25
|
+
}
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
const generator = createGenerator({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
*
|
|
3
3
|
*/
|
|
4
|
-
export declare const serializePikkuTypes: (userSessionTypeImport: string, userSessionTypeName: string, singletonServicesTypeImport: string, singletonServicesTypeName: string, sessionServicesTypeImport: string, rpcMapTypeImport: string) => string;
|
|
4
|
+
export declare const serializePikkuTypes: (userSessionTypeImport: string, userSessionTypeName: string, singletonServicesTypeImport: string, singletonServicesTypeName: string, sessionServicesTypeImport: string, sessionServicesTypeName: string, rpcMapTypeImport: string) => string;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
*
|
|
3
3
|
*/
|
|
4
|
-
export const serializePikkuTypes = (userSessionTypeImport, userSessionTypeName, singletonServicesTypeImport, singletonServicesTypeName, sessionServicesTypeImport, rpcMapTypeImport) => {
|
|
4
|
+
export const serializePikkuTypes = (userSessionTypeImport, userSessionTypeName, singletonServicesTypeImport, singletonServicesTypeName, sessionServicesTypeImport, sessionServicesTypeName, rpcMapTypeImport) => {
|
|
5
5
|
return `/**
|
|
6
6
|
* This is used to provide the application types in the typescript project
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuMiddleware, addHTTPMiddleware, addMiddleware,
|
|
9
|
+
import { CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuMiddleware, addHTTPMiddleware, addMiddleware, addPermission } from '@pikku/core'
|
|
10
10
|
import { CorePikkuFunction, CorePikkuFunctionSessionless } from '@pikku/core/function'
|
|
11
11
|
import { CoreHTTPFunctionWiring, AssertHTTPWiringParams, wireHTTP as wireHTTPCore } from '@pikku/core/http'
|
|
12
12
|
import { CoreScheduledTask, wireScheduler as wireSchedulerCore } from '@pikku/core/scheduler'
|
|
@@ -19,6 +19,10 @@ ${singletonServicesTypeImport}
|
|
|
19
19
|
${sessionServicesTypeImport}
|
|
20
20
|
${rpcMapTypeImport}
|
|
21
21
|
|
|
22
|
+
${singletonServicesTypeName !== 'SingletonServices' ? `type SingletonServices = ${singletonServicesTypeName}` : ''}
|
|
23
|
+
${sessionServicesTypeName !== 'Services' ? `type Services = ${sessionServicesTypeName}` : ''}
|
|
24
|
+
${userSessionTypeName !== 'Session' ? `type Session = ${userSessionTypeName}` : ''}
|
|
25
|
+
|
|
22
26
|
/**
|
|
23
27
|
* Type-safe API permission definition that integrates with your application's session type.
|
|
24
28
|
* Use this to define authorization logic for your API endpoints.
|
|
@@ -26,7 +30,7 @@ ${rpcMapTypeImport}
|
|
|
26
30
|
* @template In - The input type that the permission check will receive
|
|
27
31
|
* @template RequiredServices - The services required for this permission check
|
|
28
32
|
*/
|
|
29
|
-
|
|
33
|
+
type PikkuPermission<In = unknown, RequiredServices extends Services = Services> = CorePikkuPermission<In, RequiredServices, Session>
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
36
|
* Type-safe middleware definition that can access your application's services and session.
|
|
@@ -34,7 +38,38 @@ export type PikkuPermission<In = unknown, RequiredServices extends ${singletonSe
|
|
|
34
38
|
*
|
|
35
39
|
* @template RequiredServices - The services required for this middleware
|
|
36
40
|
*/
|
|
37
|
-
|
|
41
|
+
type PikkuMiddleware<RequiredServices extends SingletonServices = SingletonServices> = CorePikkuMiddleware<RequiredServices, Session>
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Factory function for creating permissions with tree-shaking support.
|
|
45
|
+
* This enables the bundler to detect which services your permission actually uses.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* \`\`\`typescript
|
|
49
|
+
* const permission = pikkuPermission(({ logger }, data, session) => {
|
|
50
|
+
* return session?.isAdmin || false
|
|
51
|
+
* })
|
|
52
|
+
* \`\`\`
|
|
53
|
+
*/
|
|
54
|
+
export const pikkuPermission = <In>(func: PikkuPermission<In>) => {
|
|
55
|
+
return func
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Factory function for creating middleware with tree-shaking support.
|
|
60
|
+
* This enables the bundler to detect which services your middleware actually uses.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* \`\`\`typescript
|
|
64
|
+
* const middleware = pikkuMiddleware(({ logger }, interactions, next) => {
|
|
65
|
+
* logger.info('Middleware executed')
|
|
66
|
+
* await next()
|
|
67
|
+
* })
|
|
68
|
+
* \`\`\`
|
|
69
|
+
*/
|
|
70
|
+
export const pikkuMiddleware = (func: PikkuMiddleware) => {
|
|
71
|
+
return func
|
|
72
|
+
}
|
|
38
73
|
|
|
39
74
|
/**
|
|
40
75
|
* A sessionless API function that doesn't require user authentication.
|
|
@@ -60,7 +95,7 @@ type PikkuFunctionSessionless<
|
|
|
60
95
|
? { mcp?: PikkuMCP } // Optional MCP
|
|
61
96
|
: { mcp: PikkuMCP } // Required MCP
|
|
62
97
|
)
|
|
63
|
-
> = CorePikkuFunctionSessionless<In, Out, ChannelData, RequiredServices,
|
|
98
|
+
> = CorePikkuFunctionSessionless<In, Out, ChannelData, RequiredServices, Session>
|
|
64
99
|
|
|
65
100
|
/**
|
|
66
101
|
* A session-aware API function that requires user authentication.
|
|
@@ -86,7 +121,7 @@ type PikkuFunction<
|
|
|
86
121
|
? { mcp?: PikkuMCP } // Optional MCP
|
|
87
122
|
: { mcp: PikkuMCP } // Required MCP
|
|
88
123
|
)
|
|
89
|
-
> = CorePikkuFunction<In, Out, ChannelData, RequiredServices,
|
|
124
|
+
> = CorePikkuFunction<In, Out, ChannelData, RequiredServices, Session>
|
|
90
125
|
|
|
91
126
|
/**
|
|
92
127
|
* Type definition for HTTP API wirings with type-safe path parameters.
|
|
@@ -369,28 +404,6 @@ export { addHTTPMiddleware }
|
|
|
369
404
|
*/
|
|
370
405
|
export { addMiddleware }
|
|
371
406
|
|
|
372
|
-
/**
|
|
373
|
-
* Combines tag-based middleware with wiring-specific middleware.
|
|
374
|
-
*
|
|
375
|
-
* This helper function gets middleware for tags and combines it with any
|
|
376
|
-
* wiring-specific middleware, avoiding the need for manual spreading.
|
|
377
|
-
*
|
|
378
|
-
* @param wiringMiddleware - Wiring-specific middleware.
|
|
379
|
-
* @param tags - Array of tags to look up middleware for.
|
|
380
|
-
* @returns Combined array of tag-based and wiring-specific middleware.
|
|
381
|
-
*
|
|
382
|
-
* @example
|
|
383
|
-
* \`\`\`typescript
|
|
384
|
-
* // Instead of:
|
|
385
|
-
* const taggedMiddleware = getMiddlewareForTags(tags)
|
|
386
|
-
* const combined = [...taggedMiddleware, ...(middleware || [])]
|
|
387
|
-
*
|
|
388
|
-
* // Use:
|
|
389
|
-
* const combined = addMiddlewareForTags(middleware, tags)
|
|
390
|
-
* \`\`\`
|
|
391
|
-
*/
|
|
392
|
-
export { addMiddlewareForTags }
|
|
393
|
-
|
|
394
407
|
/**
|
|
395
408
|
* Adds global permissions for a specific tag.
|
|
396
409
|
*
|
|
@@ -538,7 +551,7 @@ export const pikkuMCPPromptFunc = <In>(
|
|
|
538
551
|
* const results = await fileSystem.search(input.query, input.directory)
|
|
539
552
|
* return [{
|
|
540
553
|
* type: 'text',
|
|
541
|
-
* text: \`Found \${results.length} files matching
|
|
554
|
+
* text: \`Found \${results.length} files matching "\${input.query}"\`
|
|
542
555
|
* }]
|
|
543
556
|
* }
|
|
544
557
|
* })
|
package/dist/src/utils.js
CHANGED
|
@@ -7,7 +7,7 @@ export const pikkuFunctionTypes = async (logger, { typesDeclarationFile: typesFi
|
|
|
7
7
|
sessionServiceType: true,
|
|
8
8
|
singletonServicesType: true,
|
|
9
9
|
});
|
|
10
|
-
const content = serializePikkuTypes(`import type { ${userSessionType.type} } from '${getFileImportRelativePath(typesFile, userSessionType.typePath, packageMappings)}'`, userSessionType.type, `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(typesFile, singletonServicesType.typePath, packageMappings)}'`, singletonServicesType.type, `import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(typesFile, sessionServicesType.typePath, packageMappings)}'`, `import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcInternalMapDeclarationFile, packageMappings)}'`);
|
|
10
|
+
const content = serializePikkuTypes(`import type { ${userSessionType.type} } from '${getFileImportRelativePath(typesFile, userSessionType.typePath, packageMappings)}'`, userSessionType.type, `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(typesFile, singletonServicesType.typePath, packageMappings)}'`, singletonServicesType.type, `import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(typesFile, sessionServicesType.typePath, packageMappings)}'`, sessionServicesType.type, `import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcInternalMapDeclarationFile, packageMappings)}'`);
|
|
11
11
|
await writeFileInDir(logger, typesFile, content);
|
|
12
12
|
});
|
|
13
13
|
};
|
|
@@ -7,7 +7,7 @@ export const pikkuFunctionTypes = async (logger, { typesDeclarationFile: typesFi
|
|
|
7
7
|
sessionServiceType: true,
|
|
8
8
|
singletonServicesType: true,
|
|
9
9
|
});
|
|
10
|
-
const content = serializePikkuTypes(`import type { ${userSessionType.type} } from '${getFileImportRelativePath(typesFile, userSessionType.typePath, packageMappings)}'`, userSessionType.type, `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(typesFile, singletonServicesType.typePath, packageMappings)}'`, singletonServicesType.type, `import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(typesFile, sessionServicesType.typePath, packageMappings)}'`, `import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcInternalMapDeclarationFile, packageMappings)}'`);
|
|
10
|
+
const content = serializePikkuTypes(`import type { ${userSessionType.type} } from '${getFileImportRelativePath(typesFile, userSessionType.typePath, packageMappings)}'`, userSessionType.type, `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(typesFile, singletonServicesType.typePath, packageMappings)}'`, singletonServicesType.type, `import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(typesFile, sessionServicesType.typePath, packageMappings)}'`, sessionServicesType.type, `import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcInternalMapDeclarationFile, packageMappings)}'`);
|
|
11
11
|
await writeFileInDir(logger, typesFile, content);
|
|
12
12
|
});
|
|
13
13
|
};
|
|
@@ -75,5 +75,5 @@ export interface OpenAPISpecInfo {
|
|
|
75
75
|
[key: string]: any[];
|
|
76
76
|
}[];
|
|
77
77
|
}
|
|
78
|
-
export declare function generateOpenAPISpec(functionsMeta: FunctionsMeta,
|
|
78
|
+
export declare function generateOpenAPISpec(functionsMeta: FunctionsMeta, httpMeta: HTTPWiringsMeta, schemas: Record<string, any>, additionalInfo: OpenAPISpecInfo): Promise<OpenAPISpec>;
|
|
79
79
|
export {};
|
|
@@ -10,13 +10,18 @@ const getErrorResponseForConstructorName = (constructorName) => {
|
|
|
10
10
|
return undefined;
|
|
11
11
|
};
|
|
12
12
|
const convertSchemasToBodyPayloads = async (functionsMeta, routesMeta, schemas) => {
|
|
13
|
-
const requiredSchemas = new Set(
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const requiredSchemas = new Set();
|
|
14
|
+
for (const routeMeta of Object.values(routesMeta)) {
|
|
15
|
+
for (const { inputTypes, pikkuFuncName } of Object.values(routeMeta)) {
|
|
16
|
+
const output = functionsMeta[pikkuFuncName]?.outputs?.[0];
|
|
17
|
+
if (inputTypes?.body) {
|
|
18
|
+
requiredSchemas.add(inputTypes?.body);
|
|
19
|
+
}
|
|
20
|
+
if (output) {
|
|
21
|
+
requiredSchemas.add(output);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
20
25
|
const convertedEntries = await Promise.all(Object.entries(schemas).map(async ([key, schema]) => {
|
|
21
26
|
if (requiredSchemas.has(key)) {
|
|
22
27
|
const convertedSchema = await convertSchema(schema, {
|
|
@@ -29,90 +34,92 @@ const convertSchemasToBodyPayloads = async (functionsMeta, routesMeta, schemas)
|
|
|
29
34
|
}));
|
|
30
35
|
return Object.fromEntries(convertedEntries.filter((s) => !!s));
|
|
31
36
|
};
|
|
32
|
-
export async function generateOpenAPISpec(functionsMeta,
|
|
37
|
+
export async function generateOpenAPISpec(functionsMeta, httpMeta, schemas, additionalInfo) {
|
|
33
38
|
const paths = {};
|
|
34
|
-
routeMeta.
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined;
|
|
42
|
-
const path = route.replace(/:(\w+)/g, '{$1}'); // Convert ":param" to "{param}"
|
|
43
|
-
if (!paths[path]) {
|
|
44
|
-
paths[path] = {};
|
|
45
|
-
}
|
|
46
|
-
const responses = {};
|
|
47
|
-
docs?.errors?.forEach((error) => {
|
|
48
|
-
const errorResponse = getErrorResponseForConstructorName(error);
|
|
49
|
-
if (errorResponse) {
|
|
50
|
-
responses[errorResponse.status] = {
|
|
51
|
-
description: errorResponse.message,
|
|
52
|
-
};
|
|
39
|
+
for (const routeMeta of Object.values(httpMeta)) {
|
|
40
|
+
for (const meta of Object.values(routeMeta)) {
|
|
41
|
+
const { route, method, inputTypes, pikkuFuncName, params, query, docs } = meta;
|
|
42
|
+
const functionMeta = functionsMeta[pikkuFuncName];
|
|
43
|
+
if (!functionMeta) {
|
|
44
|
+
console.error(`• No function metadata found for '${pikkuFuncName}' in route '${route}'.`);
|
|
45
|
+
continue;
|
|
53
46
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
:
|
|
47
|
+
const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined;
|
|
48
|
+
const path = route.replace(/:(\w+)/g, '{$1}'); // Convert ":param" to "{param}"
|
|
49
|
+
if (!paths[path]) {
|
|
50
|
+
paths[path] = {};
|
|
51
|
+
}
|
|
52
|
+
const responses = {};
|
|
53
|
+
docs?.errors?.forEach((error) => {
|
|
54
|
+
const errorResponse = getErrorResponseForConstructorName(error);
|
|
55
|
+
if (errorResponse) {
|
|
56
|
+
responses[errorResponse.status] = {
|
|
57
|
+
description: errorResponse.message,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const operation = {
|
|
62
|
+
description: docs?.description ||
|
|
63
|
+
`This endpoint handles the ${method.toUpperCase()} request for the route ${route}.`,
|
|
64
|
+
tags: docs?.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,
|
|
87
80
|
},
|
|
88
81
|
},
|
|
89
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;
|
|
90
114
|
}
|
|
91
|
-
|
|
92
|
-
operation.parameters = params.map((param) => ({
|
|
93
|
-
name: param,
|
|
94
|
-
in: 'path',
|
|
95
|
-
required: true,
|
|
96
|
-
schema: { type: 'string' },
|
|
97
|
-
}));
|
|
98
|
-
}
|
|
99
|
-
if (query) {
|
|
100
|
-
operation.parameters.push(...query.map((query) => ({
|
|
101
|
-
name: query,
|
|
102
|
-
in: 'query',
|
|
103
|
-
required: false,
|
|
104
|
-
schema: { type: 'string' },
|
|
105
|
-
})));
|
|
106
|
-
}
|
|
107
|
-
paths[path][method] = operation;
|
|
108
|
-
});
|
|
115
|
+
}
|
|
109
116
|
return {
|
|
110
117
|
openapi: '3.1.0',
|
|
111
118
|
info: additionalInfo.info,
|
|
112
119
|
servers: additionalInfo.servers,
|
|
113
120
|
paths,
|
|
114
121
|
components: {
|
|
115
|
-
schemas: await convertSchemasToBodyPayloads(functionsMeta,
|
|
122
|
+
schemas: await convertSchemasToBodyPayloads(functionsMeta, httpMeta, schemas),
|
|
116
123
|
responses: {},
|
|
117
124
|
parameters: {},
|
|
118
125
|
examples: {},
|
|
@@ -34,28 +34,32 @@ export type HTTPWiringsWithMethod<Method extends string> = {
|
|
|
34
34
|
function generateHTTPWirings(routesMeta, functionsMeta, typesMap, requiredTypes) {
|
|
35
35
|
// Initialize an object to collect routes
|
|
36
36
|
const routesObj = {};
|
|
37
|
-
for (const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
for (const methods of Object.values(routesMeta)) {
|
|
38
|
+
for (const meta of Object.values(methods)) {
|
|
39
|
+
const { route, method, pikkuFuncName } = meta;
|
|
40
|
+
const functionMeta = functionsMeta[pikkuFuncName];
|
|
41
|
+
if (!functionMeta) {
|
|
42
|
+
throw new Error(`Function ${pikkuFuncName} not found in functionsMeta. Please check your configuration.`);
|
|
43
|
+
}
|
|
44
|
+
const input = functionMeta.inputs ? functionMeta.inputs[0] : undefined;
|
|
45
|
+
const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined;
|
|
46
|
+
// Initialize the route entry if it doesn't exist
|
|
47
|
+
if (!routesObj[route]) {
|
|
48
|
+
routesObj[route] = {};
|
|
49
|
+
}
|
|
50
|
+
// Store the input and output types separately for RouteHandler
|
|
51
|
+
const inputType = input ? typesMap.getTypeMeta(input).uniqueName : 'null';
|
|
52
|
+
const outputType = output
|
|
53
|
+
? typesMap.getTypeMeta(output).uniqueName
|
|
54
|
+
: 'null';
|
|
55
|
+
requiredTypes.add(inputType);
|
|
56
|
+
requiredTypes.add(outputType);
|
|
57
|
+
// Add method entry
|
|
58
|
+
routesObj[route][method] = {
|
|
59
|
+
inputType,
|
|
60
|
+
outputType,
|
|
61
|
+
};
|
|
42
62
|
}
|
|
43
|
-
const input = functionMeta.inputs ? functionMeta.inputs[0] : undefined;
|
|
44
|
-
const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined;
|
|
45
|
-
// Initialize the route entry if it doesn't exist
|
|
46
|
-
if (!routesObj[route]) {
|
|
47
|
-
routesObj[route] = {};
|
|
48
|
-
}
|
|
49
|
-
// Store the input and output types separately for RouteHandler
|
|
50
|
-
const inputType = input ? typesMap.getTypeMeta(input).uniqueName : 'null';
|
|
51
|
-
const outputType = output ? typesMap.getTypeMeta(output).uniqueName : 'null';
|
|
52
|
-
requiredTypes.add(inputType);
|
|
53
|
-
requiredTypes.add(outputType);
|
|
54
|
-
// Add method entry
|
|
55
|
-
routesObj[route][method] = {
|
|
56
|
-
inputType,
|
|
57
|
-
outputType,
|
|
58
|
-
};
|
|
59
63
|
}
|
|
60
64
|
// Build the routes object as a string
|
|
61
65
|
let routesStr = 'export type HTTPWiringsMap = {\n';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@openapi-contrib/json-schema-to-openapi-schema": "^4.0.2",
|
|
25
|
-
"@pikku/core": "^0.9.
|
|
26
|
-
"@pikku/inspector": "^0.9.
|
|
25
|
+
"@pikku/core": "^0.9.6",
|
|
26
|
+
"@pikku/inspector": "^0.9.4",
|
|
27
27
|
"@types/cookie": "^1.0.0",
|
|
28
28
|
"@types/uuid": "^10.0.0",
|
|
29
29
|
"chalk": "^5.5.0",
|
package/src/schema-generator.ts
CHANGED
|
@@ -22,15 +22,18 @@ export async function generateSchemas(
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
|
|
26
|
+
for (const wiringRoutes of Object.values(httpWiringsMeta)) {
|
|
27
|
+
for (const { inputTypes } of Object.values(wiringRoutes)) {
|
|
28
|
+
if (inputTypes?.body) {
|
|
29
|
+
schemasSet.add(inputTypes.body)
|
|
30
|
+
}
|
|
31
|
+
if (inputTypes?.query) {
|
|
32
|
+
schemasSet.add(inputTypes.query)
|
|
33
|
+
}
|
|
34
|
+
if (inputTypes?.params) {
|
|
35
|
+
schemasSet.add(inputTypes.params)
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -7,13 +7,14 @@ export const serializePikkuTypes = (
|
|
|
7
7
|
singletonServicesTypeImport: string,
|
|
8
8
|
singletonServicesTypeName: string,
|
|
9
9
|
sessionServicesTypeImport: string,
|
|
10
|
+
sessionServicesTypeName: string,
|
|
10
11
|
rpcMapTypeImport: string
|
|
11
12
|
) => {
|
|
12
13
|
return `/**
|
|
13
14
|
* This is used to provide the application types in the typescript project
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
|
-
import { CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuMiddleware, addHTTPMiddleware, addMiddleware,
|
|
17
|
+
import { CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuMiddleware, addHTTPMiddleware, addMiddleware, addPermission } from '@pikku/core'
|
|
17
18
|
import { CorePikkuFunction, CorePikkuFunctionSessionless } from '@pikku/core/function'
|
|
18
19
|
import { CoreHTTPFunctionWiring, AssertHTTPWiringParams, wireHTTP as wireHTTPCore } from '@pikku/core/http'
|
|
19
20
|
import { CoreScheduledTask, wireScheduler as wireSchedulerCore } from '@pikku/core/scheduler'
|
|
@@ -26,6 +27,10 @@ ${singletonServicesTypeImport}
|
|
|
26
27
|
${sessionServicesTypeImport}
|
|
27
28
|
${rpcMapTypeImport}
|
|
28
29
|
|
|
30
|
+
${singletonServicesTypeName !== 'SingletonServices' ? `type SingletonServices = ${singletonServicesTypeName}` : ''}
|
|
31
|
+
${sessionServicesTypeName !== 'Services' ? `type Services = ${sessionServicesTypeName}` : ''}
|
|
32
|
+
${userSessionTypeName !== 'Session' ? `type Session = ${userSessionTypeName}` : ''}
|
|
33
|
+
|
|
29
34
|
/**
|
|
30
35
|
* Type-safe API permission definition that integrates with your application's session type.
|
|
31
36
|
* Use this to define authorization logic for your API endpoints.
|
|
@@ -33,7 +38,7 @@ ${rpcMapTypeImport}
|
|
|
33
38
|
* @template In - The input type that the permission check will receive
|
|
34
39
|
* @template RequiredServices - The services required for this permission check
|
|
35
40
|
*/
|
|
36
|
-
|
|
41
|
+
type PikkuPermission<In = unknown, RequiredServices extends Services = Services> = CorePikkuPermission<In, RequiredServices, Session>
|
|
37
42
|
|
|
38
43
|
/**
|
|
39
44
|
* Type-safe middleware definition that can access your application's services and session.
|
|
@@ -41,7 +46,38 @@ export type PikkuPermission<In = unknown, RequiredServices extends ${singletonSe
|
|
|
41
46
|
*
|
|
42
47
|
* @template RequiredServices - The services required for this middleware
|
|
43
48
|
*/
|
|
44
|
-
|
|
49
|
+
type PikkuMiddleware<RequiredServices extends SingletonServices = SingletonServices> = CorePikkuMiddleware<RequiredServices, Session>
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Factory function for creating permissions with tree-shaking support.
|
|
53
|
+
* This enables the bundler to detect which services your permission actually uses.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* \`\`\`typescript
|
|
57
|
+
* const permission = pikkuPermission(({ logger }, data, session) => {
|
|
58
|
+
* return session?.isAdmin || false
|
|
59
|
+
* })
|
|
60
|
+
* \`\`\`
|
|
61
|
+
*/
|
|
62
|
+
export const pikkuPermission = <In>(func: PikkuPermission<In>) => {
|
|
63
|
+
return func
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Factory function for creating middleware with tree-shaking support.
|
|
68
|
+
* This enables the bundler to detect which services your middleware actually uses.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* \`\`\`typescript
|
|
72
|
+
* const middleware = pikkuMiddleware(({ logger }, interactions, next) => {
|
|
73
|
+
* logger.info('Middleware executed')
|
|
74
|
+
* await next()
|
|
75
|
+
* })
|
|
76
|
+
* \`\`\`
|
|
77
|
+
*/
|
|
78
|
+
export const pikkuMiddleware = (func: PikkuMiddleware) => {
|
|
79
|
+
return func
|
|
80
|
+
}
|
|
45
81
|
|
|
46
82
|
/**
|
|
47
83
|
* A sessionless API function that doesn't require user authentication.
|
|
@@ -67,7 +103,7 @@ type PikkuFunctionSessionless<
|
|
|
67
103
|
? { mcp?: PikkuMCP } // Optional MCP
|
|
68
104
|
: { mcp: PikkuMCP } // Required MCP
|
|
69
105
|
)
|
|
70
|
-
> = CorePikkuFunctionSessionless<In, Out, ChannelData, RequiredServices,
|
|
106
|
+
> = CorePikkuFunctionSessionless<In, Out, ChannelData, RequiredServices, Session>
|
|
71
107
|
|
|
72
108
|
/**
|
|
73
109
|
* A session-aware API function that requires user authentication.
|
|
@@ -93,7 +129,7 @@ type PikkuFunction<
|
|
|
93
129
|
? { mcp?: PikkuMCP } // Optional MCP
|
|
94
130
|
: { mcp: PikkuMCP } // Required MCP
|
|
95
131
|
)
|
|
96
|
-
> = CorePikkuFunction<In, Out, ChannelData, RequiredServices,
|
|
132
|
+
> = CorePikkuFunction<In, Out, ChannelData, RequiredServices, Session>
|
|
97
133
|
|
|
98
134
|
/**
|
|
99
135
|
* Type definition for HTTP API wirings with type-safe path parameters.
|
|
@@ -376,28 +412,6 @@ export { addHTTPMiddleware }
|
|
|
376
412
|
*/
|
|
377
413
|
export { addMiddleware }
|
|
378
414
|
|
|
379
|
-
/**
|
|
380
|
-
* Combines tag-based middleware with wiring-specific middleware.
|
|
381
|
-
*
|
|
382
|
-
* This helper function gets middleware for tags and combines it with any
|
|
383
|
-
* wiring-specific middleware, avoiding the need for manual spreading.
|
|
384
|
-
*
|
|
385
|
-
* @param wiringMiddleware - Wiring-specific middleware.
|
|
386
|
-
* @param tags - Array of tags to look up middleware for.
|
|
387
|
-
* @returns Combined array of tag-based and wiring-specific middleware.
|
|
388
|
-
*
|
|
389
|
-
* @example
|
|
390
|
-
* \`\`\`typescript
|
|
391
|
-
* // Instead of:
|
|
392
|
-
* const taggedMiddleware = getMiddlewareForTags(tags)
|
|
393
|
-
* const combined = [...taggedMiddleware, ...(middleware || [])]
|
|
394
|
-
*
|
|
395
|
-
* // Use:
|
|
396
|
-
* const combined = addMiddlewareForTags(middleware, tags)
|
|
397
|
-
* \`\`\`
|
|
398
|
-
*/
|
|
399
|
-
export { addMiddlewareForTags }
|
|
400
|
-
|
|
401
415
|
/**
|
|
402
416
|
* Adds global permissions for a specific tag.
|
|
403
417
|
*
|
|
@@ -545,7 +559,7 @@ export const pikkuMCPPromptFunc = <In>(
|
|
|
545
559
|
* const results = await fileSystem.search(input.query, input.directory)
|
|
546
560
|
* return [{
|
|
547
561
|
* type: 'text',
|
|
548
|
-
* text: \`Found \${results.length} files matching
|
|
562
|
+
* text: \`Found \${results.length} files matching "\${input.query}"\`
|
|
549
563
|
* }]
|
|
550
564
|
* }
|
|
551
565
|
* })
|
package/src/utils.ts
CHANGED
|
@@ -43,6 +43,7 @@ export const pikkuFunctionTypes: PikkuCommand = async (
|
|
|
43
43
|
`import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(typesFile, singletonServicesType.typePath, packageMappings)}'`,
|
|
44
44
|
singletonServicesType.type,
|
|
45
45
|
`import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(typesFile, sessionServicesType.typePath, packageMappings)}'`,
|
|
46
|
+
sessionServicesType.type,
|
|
46
47
|
`import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcInternalMapDeclarationFile, packageMappings)}'`
|
|
47
48
|
)
|
|
48
49
|
|
|
@@ -43,6 +43,7 @@ export const pikkuFunctionTypes: PikkuCommand = async (
|
|
|
43
43
|
`import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(typesFile, singletonServicesType.typePath, packageMappings)}'`,
|
|
44
44
|
singletonServicesType.type,
|
|
45
45
|
`import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(typesFile, sessionServicesType.typePath, packageMappings)}'`,
|
|
46
|
+
sessionServicesType.type,
|
|
46
47
|
`import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcInternalMapDeclarationFile, packageMappings)}'`
|
|
47
48
|
)
|
|
48
49
|
await writeFileInDir(logger, typesFile, content)
|
|
@@ -79,15 +79,19 @@ const convertSchemasToBodyPayloads = async (
|
|
|
79
79
|
routesMeta: HTTPWiringsMeta,
|
|
80
80
|
schemas: Record<string, any>
|
|
81
81
|
) => {
|
|
82
|
-
const requiredSchemas = new Set(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
const requiredSchemas = new Set<string>()
|
|
83
|
+
for (const routeMeta of Object.values(routesMeta)) {
|
|
84
|
+
for (const { inputTypes, pikkuFuncName } of Object.values(routeMeta)) {
|
|
85
|
+
const output = functionsMeta[pikkuFuncName]?.outputs?.[0]
|
|
86
|
+
if (inputTypes?.body) {
|
|
87
|
+
requiredSchemas.add(inputTypes?.body)
|
|
88
|
+
}
|
|
89
|
+
if (output) {
|
|
90
|
+
requiredSchemas.add(output)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
91
95
|
const convertedEntries = await Promise.all(
|
|
92
96
|
Object.entries(schemas).map(async ([key, schema]) => {
|
|
93
97
|
if (requiredSchemas.has(key)) {
|
|
@@ -105,103 +109,106 @@ const convertSchemasToBodyPayloads = async (
|
|
|
105
109
|
|
|
106
110
|
export async function generateOpenAPISpec(
|
|
107
111
|
functionsMeta: FunctionsMeta,
|
|
108
|
-
|
|
112
|
+
httpMeta: HTTPWiringsMeta,
|
|
109
113
|
schemas: Record<string, any>,
|
|
110
114
|
additionalInfo: OpenAPISpecInfo
|
|
111
115
|
): Promise<OpenAPISpec> {
|
|
112
116
|
const paths: Record<string, any> = {}
|
|
113
117
|
|
|
114
|
-
routeMeta.
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
for (const routeMeta of Object.values(httpMeta)) {
|
|
119
|
+
for (const meta of Object.values(routeMeta)) {
|
|
120
|
+
const { route, method, inputTypes, pikkuFuncName, params, query, docs } =
|
|
121
|
+
meta
|
|
122
|
+
const functionMeta = functionsMeta[pikkuFuncName]
|
|
123
|
+
if (!functionMeta) {
|
|
124
|
+
console.error(
|
|
125
|
+
`• No function metadata found for '${pikkuFuncName}' in route '${route}'.`
|
|
126
|
+
)
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
125
129
|
|
|
126
|
-
|
|
130
|
+
const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
paths[path] = {}
|
|
130
|
-
}
|
|
132
|
+
const path = route.replace(/:(\w+)/g, '{$1}') // Convert ":param" to "{param}"
|
|
131
133
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const errorResponse = getErrorResponseForConstructorName(error)
|
|
135
|
-
if (errorResponse) {
|
|
136
|
-
responses[errorResponse.status] = {
|
|
137
|
-
description: errorResponse.message,
|
|
138
|
-
}
|
|
134
|
+
if (!paths[path]) {
|
|
135
|
+
paths[path] = {}
|
|
139
136
|
}
|
|
140
|
-
})
|
|
141
137
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
description: 'Successful response',
|
|
152
|
-
content: output
|
|
153
|
-
? {
|
|
154
|
-
'application/json': {
|
|
155
|
-
schema:
|
|
156
|
-
typeof output === 'string' &&
|
|
157
|
-
['boolean', 'string', 'number'].includes(output)
|
|
158
|
-
? { type: output }
|
|
159
|
-
: { $ref: `#/components/schemas/${output}` },
|
|
160
|
-
},
|
|
161
|
-
}
|
|
162
|
-
: undefined,
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
}
|
|
138
|
+
const responses = {}
|
|
139
|
+
docs?.errors?.forEach((error) => {
|
|
140
|
+
const errorResponse = getErrorResponseForConstructorName(error)
|
|
141
|
+
if (errorResponse) {
|
|
142
|
+
responses[errorResponse.status] = {
|
|
143
|
+
description: errorResponse.message,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
})
|
|
166
147
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
148
|
+
const operation: any = {
|
|
149
|
+
description:
|
|
150
|
+
docs?.description ||
|
|
151
|
+
`This endpoint handles the ${method.toUpperCase()} request for the route ${route}.`,
|
|
152
|
+
tags: docs?.tags || [route.split('/')[1] || 'default'],
|
|
153
|
+
parameters: [],
|
|
154
|
+
responses: {
|
|
155
|
+
...responses,
|
|
156
|
+
'200': {
|
|
157
|
+
description: 'Successful response',
|
|
158
|
+
content: output
|
|
159
|
+
? {
|
|
160
|
+
'application/json': {
|
|
161
|
+
schema:
|
|
162
|
+
typeof output === 'string' &&
|
|
163
|
+
['boolean', 'string', 'number'].includes(output)
|
|
164
|
+
? { type: output }
|
|
165
|
+
: { $ref: `#/components/schemas/${output}` },
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
: undefined,
|
|
178
169
|
},
|
|
179
170
|
},
|
|
180
171
|
}
|
|
181
|
-
}
|
|
182
172
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
173
|
+
const bodyType = inputTypes?.body
|
|
174
|
+
if (bodyType) {
|
|
175
|
+
operation.requestBody = {
|
|
176
|
+
required: true,
|
|
177
|
+
content: {
|
|
178
|
+
'application/json': {
|
|
179
|
+
schema:
|
|
180
|
+
typeof bodyType === 'string' &&
|
|
181
|
+
['boolean', 'string', 'number'].includes(bodyType)
|
|
182
|
+
? { type: bodyType }
|
|
183
|
+
: { $ref: `#/components/schemas/${bodyType}` },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
}
|
|
191
188
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
required: false,
|
|
189
|
+
if (params) {
|
|
190
|
+
operation.parameters = params.map((param) => ({
|
|
191
|
+
name: param,
|
|
192
|
+
in: 'path',
|
|
193
|
+
required: true,
|
|
198
194
|
schema: { type: 'string' },
|
|
199
195
|
}))
|
|
200
|
-
|
|
201
|
-
}
|
|
196
|
+
}
|
|
202
197
|
|
|
203
|
-
|
|
204
|
-
|
|
198
|
+
if (query) {
|
|
199
|
+
operation.parameters.push(
|
|
200
|
+
...query.map((query) => ({
|
|
201
|
+
name: query,
|
|
202
|
+
in: 'query',
|
|
203
|
+
required: false,
|
|
204
|
+
schema: { type: 'string' },
|
|
205
|
+
}))
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
paths[path][method] = operation
|
|
210
|
+
}
|
|
211
|
+
}
|
|
205
212
|
|
|
206
213
|
return {
|
|
207
214
|
openapi: '3.1.0',
|
|
@@ -211,7 +218,7 @@ export async function generateOpenAPISpec(
|
|
|
211
218
|
components: {
|
|
212
219
|
schemas: await convertSchemasToBodyPayloads(
|
|
213
220
|
functionsMeta,
|
|
214
|
-
|
|
221
|
+
httpMeta,
|
|
215
222
|
schemas
|
|
216
223
|
),
|
|
217
224
|
responses: {},
|
|
@@ -67,33 +67,37 @@ function generateHTTPWirings(
|
|
|
67
67
|
Record<string, { inputType: string; outputType: string }>
|
|
68
68
|
> = {}
|
|
69
69
|
|
|
70
|
-
for (const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
routesObj[route]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
70
|
+
for (const methods of Object.values(routesMeta)) {
|
|
71
|
+
for (const meta of Object.values(methods)) {
|
|
72
|
+
const { route, method, pikkuFuncName } = meta
|
|
73
|
+
const functionMeta = functionsMeta[pikkuFuncName]
|
|
74
|
+
if (!functionMeta) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Function ${pikkuFuncName} not found in functionsMeta. Please check your configuration.`
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
const input = functionMeta.inputs ? functionMeta.inputs[0] : undefined
|
|
80
|
+
const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined
|
|
81
|
+
|
|
82
|
+
// Initialize the route entry if it doesn't exist
|
|
83
|
+
if (!routesObj[route]) {
|
|
84
|
+
routesObj[route] = {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Store the input and output types separately for RouteHandler
|
|
88
|
+
const inputType = input ? typesMap.getTypeMeta(input).uniqueName : 'null'
|
|
89
|
+
const outputType = output
|
|
90
|
+
? typesMap.getTypeMeta(output).uniqueName
|
|
91
|
+
: 'null'
|
|
92
|
+
|
|
93
|
+
requiredTypes.add(inputType)
|
|
94
|
+
requiredTypes.add(outputType)
|
|
95
|
+
|
|
96
|
+
// Add method entry
|
|
97
|
+
routesObj[route][method] = {
|
|
98
|
+
inputType,
|
|
99
|
+
outputType,
|
|
100
|
+
}
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
103
|
|