@pikku/cli 0.9.5 → 0.9.7
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 +20 -0
- package/bin/pikku-all.ts +1 -2
- package/dist/bin/pikku-all.js +1 -2
- package/dist/src/pikku-cli-config.d.ts +0 -1
- package/dist/src/pikku-cli-config.js +0 -3
- package/dist/src/schema-generator.js +11 -9
- package/dist/src/serialize-import-map.js +5 -5
- package/dist/src/serialize-pikku-types.d.ts +1 -1
- package/dist/src/serialize-pikku-types.js +42 -7
- 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/dist/src/wirings/rpc/pikku-command-rpc-client.js +1 -1
- package/dist/src/wirings/rpc/pikku-command-rpc.js +1 -4
- package/package.json +3 -3
- package/src/pikku-cli-config.ts +0 -7
- package/src/schema-generator.ts +12 -9
- package/src/serialize-import-map.ts +6 -4
- package/src/serialize-pikku-types.ts +42 -6
- 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/src/wirings/rpc/pikku-command-rpc-client.ts +1 -6
- package/src/wirings/rpc/pikku-command-rpc.ts +1 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @pikku/cli
|
|
2
2
|
|
|
3
|
+
## 0.9.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4fd5e19: fix: removing rpcMeta and duplicate imports
|
|
8
|
+
- d1babed: fix: pikkuVoidFunc should use a sessionless function -- Since its used mostly by scheduled tasks
|
|
9
|
+
|
|
10
|
+
## 0.9.6
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 6059c87: refactor: move PikkuPermission to pikkuPermission and same for middleware for api consistency to to improve future features
|
|
15
|
+
- 6db63bb: perf: changing http meta to a lookup map to reduce loops
|
|
16
|
+
- Updated dependencies [6059c87]
|
|
17
|
+
- Updated dependencies [6db63bb]
|
|
18
|
+
- Updated dependencies [74f8634]
|
|
19
|
+
- Updated dependencies [766fef1]
|
|
20
|
+
- @pikku/inspector@0.9.4
|
|
21
|
+
- @pikku/core@0.9.6
|
|
22
|
+
|
|
3
23
|
## 0.9.5
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/bin/pikku-all.ts
CHANGED
|
@@ -115,7 +115,6 @@ const runAll = async (
|
|
|
115
115
|
await pikkuRPCInternalMap(logger, cliConfig, visitState)
|
|
116
116
|
await pikkuRPCExposedMap(logger, cliConfig, visitState)
|
|
117
117
|
await pikkuRPCClient(logger, cliConfig)
|
|
118
|
-
allImports.push(cliConfig.rpcWiringMetaFile)
|
|
119
118
|
|
|
120
119
|
const schemas = await pikkuSchemas(logger, cliConfig, visitState)
|
|
121
120
|
if (schemas) {
|
|
@@ -127,7 +126,7 @@ const runAll = async (
|
|
|
127
126
|
logger,
|
|
128
127
|
cliConfig,
|
|
129
128
|
cliConfig.bootstrapFiles.rpc,
|
|
130
|
-
[
|
|
129
|
+
[],
|
|
131
130
|
schemas
|
|
132
131
|
)
|
|
133
132
|
|
package/dist/bin/pikku-all.js
CHANGED
|
@@ -67,13 +67,12 @@ const runAll = async (logger, cliConfig, options) => {
|
|
|
67
67
|
await pikkuRPCInternalMap(logger, cliConfig, visitState);
|
|
68
68
|
await pikkuRPCExposedMap(logger, cliConfig, visitState);
|
|
69
69
|
await pikkuRPCClient(logger, cliConfig);
|
|
70
|
-
allImports.push(cliConfig.rpcWiringMetaFile);
|
|
71
70
|
const schemas = await pikkuSchemas(logger, cliConfig, visitState);
|
|
72
71
|
if (schemas) {
|
|
73
72
|
allImports.push(`${cliConfig.schemaDirectory}/register.gen.ts`);
|
|
74
73
|
}
|
|
75
74
|
// RPC bootstrap is always generated since RPC is always present
|
|
76
|
-
await generateBootstrapFile(logger, cliConfig, cliConfig.bootstrapFiles.rpc, [
|
|
75
|
+
await generateBootstrapFile(logger, cliConfig, cliConfig.bootstrapFiles.rpc, [], schemas);
|
|
77
76
|
const http = await pikkuHTTP(logger, cliConfig, visitState);
|
|
78
77
|
if (http) {
|
|
79
78
|
await pikkuHTTPMap(logger, cliConfig, visitState);
|
|
@@ -16,7 +16,6 @@ export interface PikkuCLICoreOutputFiles {
|
|
|
16
16
|
channelsMapDeclarationFile: string;
|
|
17
17
|
rpcInternalWiringMetaFile: string;
|
|
18
18
|
rpcInternalMapDeclarationFile: string;
|
|
19
|
-
rpcWiringMetaFile: string;
|
|
20
19
|
rpcMapDeclarationFile: string;
|
|
21
20
|
schedulersWiringFile: string;
|
|
22
21
|
schedulersWiringMetaFile: string;
|
|
@@ -111,9 +111,6 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, filter
|
|
|
111
111
|
result.rpcInternalMapDeclarationFile = join(internalRPCDirectory, 'pikku-rpc-wirings-map.internal.gen.d.ts');
|
|
112
112
|
}
|
|
113
113
|
// External
|
|
114
|
-
if (!result.rpcWiringMetaFile) {
|
|
115
|
-
result.rpcWiringMetaFile = join(externalRPCDirectory, 'pikku-rpc-wirings-meta.gen.ts');
|
|
116
|
-
}
|
|
117
114
|
if (!result.rpcMapDeclarationFile) {
|
|
118
115
|
result.rpcMapDeclarationFile = join(externalRPCDirectory, 'pikku-rpc-wirings-map.gen.d.ts');
|
|
119
116
|
}
|
|
@@ -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({
|
|
@@ -72,11 +72,11 @@ export const serializeImportMap = (relativeToPath, packageMappings, typesMap, re
|
|
|
72
72
|
if (uniqueName === '__object') {
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
variables.push(
|
|
75
|
+
const importName = originalName === uniqueName
|
|
76
|
+
? originalName
|
|
77
|
+
: `${originalName} as ${uniqueName}`;
|
|
78
|
+
if (!variables.includes(importName)) {
|
|
79
|
+
variables.push(importName);
|
|
80
80
|
}
|
|
81
81
|
paths.set(path, variables);
|
|
82
82
|
});
|
|
@@ -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,7 +1,7 @@
|
|
|
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
|
*/
|
|
@@ -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.
|
|
@@ -305,7 +340,7 @@ export const pikkuChannelFunc = <In = unknown, Out = unknown, ChannelData = unkn
|
|
|
305
340
|
export const pikkuVoidFunc = (
|
|
306
341
|
func:
|
|
307
342
|
| PikkuFunctionSessionless<void, void>
|
|
308
|
-
| CorePikkuFunctionConfig<
|
|
343
|
+
| CorePikkuFunctionConfig<PikkuFunctionSessionless<void, void>, PikkuPermission<void>>
|
|
309
344
|
) => {
|
|
310
345
|
return typeof func === 'function' ? func : func.func
|
|
311
346
|
}
|
|
@@ -516,7 +551,7 @@ export const pikkuMCPPromptFunc = <In>(
|
|
|
516
551
|
* const results = await fileSystem.search(input.query, input.directory)
|
|
517
552
|
* return [{
|
|
518
553
|
* type: 'text',
|
|
519
|
-
* text: \`Found \${results.length} files matching
|
|
554
|
+
* text: \`Found \${results.length} files matching "\${input.query}"\`
|
|
520
555
|
* }]
|
|
521
556
|
* }
|
|
522
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';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { serializeRPCWrapper } from './serialize-rpc-wrapper.js';
|
|
2
2
|
import { getFileImportRelativePath, logCommandInfoAndTime, writeFileInDir, } from '../../utils.js';
|
|
3
|
-
export const pikkuRPCClient = async (logger, { rpcWiringsFile, rpcMapDeclarationFile,
|
|
3
|
+
export const pikkuRPCClient = async (logger, { rpcWiringsFile, rpcMapDeclarationFile, packageMappings }) => {
|
|
4
4
|
return await logCommandInfoAndTime(logger, 'Generating RPC wrappers', 'Generated RPC wrappers', [
|
|
5
5
|
rpcWiringsFile === undefined || rpcWiringsFile === null,
|
|
6
6
|
"rpcWiringsFile isn't set in the pikku config",
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { logCommandInfoAndTime, writeFileInDir } from '../../utils.js';
|
|
2
|
-
export const pikkuRPC = async (logger, { rpcInternalWiringMetaFile
|
|
2
|
+
export const pikkuRPC = async (logger, { rpcInternalWiringMetaFile }, { rpc }) => {
|
|
3
3
|
return await logCommandInfoAndTime(logger, 'Finding RPCs tasks', 'Found RPCs', [false], async () => {
|
|
4
4
|
if (rpc.internalFiles.size > 0) {
|
|
5
5
|
await writeFileInDir(logger, rpcInternalWiringMetaFile, `import { pikkuState } from '@pikku/core'\npikkuState('rpc', 'meta', ${JSON.stringify(rpc.internalMeta, null, 2)})`);
|
|
6
6
|
}
|
|
7
|
-
if (rpc.exposedFiles.size > 0) {
|
|
8
|
-
await writeFileInDir(logger, rpcWiringMetaFile, `import { pikkuState } from '@pikku/core'\npikkuState('rpc', 'meta', ${JSON.stringify(rpc.exposedFiles, null, 2)})`);
|
|
9
|
-
}
|
|
10
7
|
});
|
|
11
8
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
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/pikku-cli-config.ts
CHANGED
|
@@ -32,7 +32,6 @@ export interface PikkuCLICoreOutputFiles {
|
|
|
32
32
|
rpcInternalMapDeclarationFile: string
|
|
33
33
|
|
|
34
34
|
// RPC Exposed
|
|
35
|
-
rpcWiringMetaFile: string
|
|
36
35
|
rpcMapDeclarationFile: string
|
|
37
36
|
|
|
38
37
|
// Schedulers
|
|
@@ -253,12 +252,6 @@ const _getPikkuCLIConfig = async (
|
|
|
253
252
|
}
|
|
254
253
|
|
|
255
254
|
// External
|
|
256
|
-
if (!result.rpcWiringMetaFile) {
|
|
257
|
-
result.rpcWiringMetaFile = join(
|
|
258
|
-
externalRPCDirectory,
|
|
259
|
-
'pikku-rpc-wirings-meta.gen.ts'
|
|
260
|
-
)
|
|
261
|
-
}
|
|
262
255
|
if (!result.rpcMapDeclarationFile) {
|
|
263
256
|
result.rpcMapDeclarationFile = join(
|
|
264
257
|
externalRPCDirectory,
|
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
|
|
|
@@ -86,10 +86,12 @@ export const serializeImportMap = (
|
|
|
86
86
|
return
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
const importName =
|
|
90
|
+
originalName === uniqueName
|
|
91
|
+
? originalName
|
|
92
|
+
: `${originalName} as ${uniqueName}`
|
|
93
|
+
if (!variables.includes(importName)) {
|
|
94
|
+
variables.push(importName)
|
|
93
95
|
}
|
|
94
96
|
paths.set(path, variables)
|
|
95
97
|
})
|
|
@@ -7,6 +7,7 @@ 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 `/**
|
|
@@ -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.
|
|
@@ -312,7 +348,7 @@ export const pikkuChannelFunc = <In = unknown, Out = unknown, ChannelData = unkn
|
|
|
312
348
|
export const pikkuVoidFunc = (
|
|
313
349
|
func:
|
|
314
350
|
| PikkuFunctionSessionless<void, void>
|
|
315
|
-
| CorePikkuFunctionConfig<
|
|
351
|
+
| CorePikkuFunctionConfig<PikkuFunctionSessionless<void, void>, PikkuPermission<void>>
|
|
316
352
|
) => {
|
|
317
353
|
return typeof func === 'function' ? func : func.func
|
|
318
354
|
}
|
|
@@ -523,7 +559,7 @@ export const pikkuMCPPromptFunc = <In>(
|
|
|
523
559
|
* const results = await fileSystem.search(input.query, input.directory)
|
|
524
560
|
* return [{
|
|
525
561
|
* type: 'text',
|
|
526
|
-
* text: \`Found \${results.length} files matching
|
|
562
|
+
* text: \`Found \${results.length} files matching "\${input.query}"\`
|
|
527
563
|
* }]
|
|
528
564
|
* }
|
|
529
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
|
|
|
@@ -8,12 +8,7 @@ import { PikkuCommandWithoutState } from '../../types.js'
|
|
|
8
8
|
|
|
9
9
|
export const pikkuRPCClient: PikkuCommandWithoutState = async (
|
|
10
10
|
logger,
|
|
11
|
-
{
|
|
12
|
-
rpcWiringsFile,
|
|
13
|
-
rpcMapDeclarationFile,
|
|
14
|
-
rpcInternalMapDeclarationFile,
|
|
15
|
-
packageMappings,
|
|
16
|
-
}
|
|
11
|
+
{ rpcWiringsFile, rpcMapDeclarationFile, packageMappings }
|
|
17
12
|
) => {
|
|
18
13
|
return await logCommandInfoAndTime(
|
|
19
14
|
logger,
|
|
@@ -3,7 +3,7 @@ import { PikkuCommand } from '../../types.js'
|
|
|
3
3
|
|
|
4
4
|
export const pikkuRPC: PikkuCommand = async (
|
|
5
5
|
logger,
|
|
6
|
-
{ rpcInternalWiringMetaFile
|
|
6
|
+
{ rpcInternalWiringMetaFile },
|
|
7
7
|
{ rpc }
|
|
8
8
|
) => {
|
|
9
9
|
return await logCommandInfoAndTime(
|
|
@@ -19,14 +19,6 @@ export const pikkuRPC: PikkuCommand = async (
|
|
|
19
19
|
`import { pikkuState } from '@pikku/core'\npikkuState('rpc', 'meta', ${JSON.stringify(rpc.internalMeta, null, 2)})`
|
|
20
20
|
)
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
if (rpc.exposedFiles.size > 0) {
|
|
24
|
-
await writeFileInDir(
|
|
25
|
-
logger,
|
|
26
|
-
rpcWiringMetaFile,
|
|
27
|
-
`import { pikkuState } from '@pikku/core'\npikkuState('rpc', 'meta', ${JSON.stringify(rpc.exposedFiles, null, 2)})`
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
22
|
}
|
|
31
23
|
)
|
|
32
24
|
}
|