@pikku/cli 0.6.19 → 0.7.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 +12 -0
- package/cli.schema.json +2 -2
- package/dist/bin/pikku-all.js +34 -16
- package/dist/bin/pikku-channels-map.js +4 -4
- package/dist/bin/pikku-channels.js +6 -10
- package/dist/bin/pikku-fetch.d.ts +1 -1
- package/dist/bin/pikku-fetch.js +3 -3
- package/dist/bin/pikku-function-types.js +4 -4
- package/dist/bin/pikku-functions.d.ts +5 -0
- package/dist/bin/pikku-functions.js +24 -0
- package/dist/bin/pikku-http-map.d.ts +5 -0
- package/dist/bin/pikku-http-map.js +23 -0
- package/dist/bin/pikku-http-routes.d.ts +5 -0
- package/dist/bin/pikku-http-routes.js +24 -0
- package/dist/bin/pikku-http.js +3 -5
- package/dist/bin/pikku-nextjs.d.ts +1 -1
- package/dist/bin/pikku-nextjs.js +8 -8
- package/dist/bin/pikku-openapi.d.ts +1 -1
- package/dist/bin/pikku-openapi.js +6 -6
- package/dist/bin/pikku-routes-map.d.ts +1 -1
- package/dist/bin/pikku-routes-map.js +3 -3
- package/dist/bin/pikku-scheduler.js +7 -10
- package/dist/bin/pikku-schemas.d.ts +1 -1
- package/dist/bin/pikku-schemas.js +6 -7
- package/dist/bin/pikku-websocket.js +1 -1
- package/dist/bin/pikku.js +2 -2
- package/dist/src/channels/serialize-typed-channel-map.js +5 -2
- package/dist/src/core/serialize-import-map.js +1 -0
- package/dist/src/core/serialize-pikku-types.js +90 -6
- package/dist/src/events/channels/serialize-channels.d.ts +3 -0
- package/dist/src/events/channels/serialize-channels.js +19 -0
- package/dist/src/events/channels/serialize-typed-channel-map.d.ts +3 -0
- package/dist/src/events/channels/serialize-typed-channel-map.js +90 -0
- package/dist/src/events/channels/serialize-websocket-wrapper.d.ts +1 -0
- package/{src/channels/serialize-websocket-wrapper.ts → dist/src/events/channels/serialize-websocket-wrapper.js} +4 -4
- package/dist/src/events/http/serialize-fetch-wrapper.d.ts +1 -0
- package/{src/http/serialize-fetch-wrapper.ts → dist/src/events/http/serialize-fetch-wrapper.js} +4 -4
- package/dist/src/events/http/serialize-route-imports.d.ts +1 -0
- package/dist/src/events/http/serialize-route-imports.js +13 -0
- package/dist/src/events/http/serialize-route-meta.d.ts +2 -0
- package/dist/src/events/http/serialize-route-meta.js +6 -0
- package/dist/src/events/http/serialize-typed-route-map.d.ts +4 -0
- package/dist/src/events/http/serialize-typed-route-map.js +107 -0
- package/dist/src/events/scheduler/serialize-schedulers.d.ts +3 -0
- package/dist/src/events/scheduler/serialize-schedulers.js +23 -0
- package/dist/src/http/serialize-typed-route-map.js +1 -1
- package/dist/src/inspector-glob.d.ts +1 -1
- package/dist/src/inspector-glob.js +2 -2
- package/dist/src/openapi-spec-generator.d.ts +79 -0
- package/dist/src/openapi-spec-generator.js +136 -0
- package/dist/src/pikku-cli-config.d.ts +9 -4
- package/dist/src/pikku-cli-config.js +19 -4
- package/dist/src/scheduler/serialize-schedulers.js +3 -2
- package/dist/src/schema/schema-generator.d.ts +1 -1
- package/dist/src/schema/schema-generator.js +20 -7
- package/dist/src/schema-generator.d.ts +5 -0
- package/dist/src/schema-generator.js +98 -0
- package/dist/src/serialize-fetch-wrapper.d.ts +1 -0
- package/dist/src/serialize-fetch-wrapper.js +67 -0
- package/dist/src/serialize-import-map.d.ts +2 -0
- package/dist/src/serialize-import-map.js +24 -0
- package/dist/src/serialize-nextjs-backend-wrapper.d.ts +1 -0
- package/{src/nextjs/serialize-nextjs-backend-wrapper.ts → dist/src/serialize-nextjs-backend-wrapper.js} +4 -11
- package/dist/src/serialize-nextjs-http-wrapper.d.ts +1 -0
- package/{src/nextjs/serialize-nextjs-http-wrapper.ts → dist/src/serialize-nextjs-http-wrapper.js} +4 -7
- package/dist/src/serialize-pikku-types.d.ts +4 -0
- package/dist/src/serialize-pikku-types.js +136 -0
- package/dist/src/serialize-scheduler-meta.d.ts +2 -0
- package/dist/src/serialize-scheduler-meta.js +10 -0
- package/dist/src/serialize-typed-channel-map.d.ts +3 -0
- package/dist/src/serialize-typed-channel-map.js +93 -0
- package/dist/src/serialize-typed-function-map.d.ts +4 -0
- package/dist/src/serialize-typed-function-map.js +107 -0
- package/dist/src/serialize-typed-http-map.d.ts +4 -0
- package/dist/src/serialize-typed-http-map.js +107 -0
- package/dist/src/serialize-typed-route-map.d.ts +4 -0
- package/dist/src/serialize-typed-route-map.js +107 -0
- package/dist/src/serialize-websocket-wrapper.d.ts +1 -0
- package/dist/src/serialize-websocket-wrapper.js +61 -0
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.js +17 -5
- package/package.json +3 -3
- package/bin/pikku-all.ts +0 -180
- package/bin/pikku-channels-map.ts +0 -55
- package/bin/pikku-channels.ts +0 -58
- package/bin/pikku-fetch.ts +0 -55
- package/bin/pikku-function-types.ts +0 -84
- package/bin/pikku-http.ts +0 -56
- package/bin/pikku-nextjs.test.ts +0 -279
- package/bin/pikku-nextjs.ts +0 -152
- package/bin/pikku-openapi.ts +0 -70
- package/bin/pikku-routes-map.ts +0 -55
- package/bin/pikku-scheduler.ts +0 -62
- package/bin/pikku-schemas.ts +0 -53
- package/bin/pikku-websocket.ts +0 -58
- package/bin/pikku.ts +0 -26
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/channels/serialize-channels.ts +0 -34
- package/src/channels/serialize-typed-channel-map.ts +0 -136
- package/src/core/serialize-import-map.ts +0 -33
- package/src/core/serialize-pikku-types.ts +0 -55
- package/src/http/serialize-route-imports.ts +0 -24
- package/src/http/serialize-route-meta.ts +0 -10
- package/src/http/serialize-typed-route-map.ts +0 -150
- package/src/inspector-glob.ts +0 -28
- package/src/openapi/openapi-spec-generator.ts +0 -227
- package/src/pikku-cli-config.ts +0 -204
- package/src/scheduler/serialize-schedulers.ts +0 -41
- package/src/schema/schema-generator.ts +0 -114
- package/src/utils.ts +0 -260
- package/tsconfig.json +0 -21
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { pikkuState } from '@pikku/core';
|
|
2
|
+
import _convertSchema from '@openapi-contrib/json-schema-to-openapi-schema';
|
|
3
|
+
const convertSchema = 'default' in _convertSchema ? _convertSchema.default : _convertSchema;
|
|
4
|
+
const getErrorResponseForConstructorName = (constructorName) => {
|
|
5
|
+
const errors = Array.from(pikkuState('misc', 'errors').entries());
|
|
6
|
+
const foundError = errors.find(([e]) => e.name === constructorName);
|
|
7
|
+
if (foundError) {
|
|
8
|
+
return foundError[1];
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
};
|
|
12
|
+
const convertSchemasToBodyPayloads = async (routesMeta, schemas) => {
|
|
13
|
+
const requiredSchemas = new Set(routesMeta
|
|
14
|
+
.map(({ inputTypes, output }) => [inputTypes?.body, output])
|
|
15
|
+
.flat()
|
|
16
|
+
.filter((schema) => !!schema));
|
|
17
|
+
const convertedEntries = await Promise.all(Object.entries(schemas).map(async ([key, schema]) => {
|
|
18
|
+
if (requiredSchemas.has(key)) {
|
|
19
|
+
const convertedSchema = await convertSchema(schema, {
|
|
20
|
+
convertUnreferencedDefinitions: false,
|
|
21
|
+
dereference: { circular: 'ignore' },
|
|
22
|
+
});
|
|
23
|
+
return [key, convertedSchema];
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}));
|
|
27
|
+
return Object.fromEntries(convertedEntries.filter((s) => !!s));
|
|
28
|
+
};
|
|
29
|
+
export async function generateOpenAPISpec(routeMeta, schemas, additionalInfo) {
|
|
30
|
+
const paths = {};
|
|
31
|
+
routeMeta.forEach((meta) => {
|
|
32
|
+
const { route, method, inputTypes, output, params, query, docs } = meta;
|
|
33
|
+
const path = route.replace(/:(\w+)/g, '{$1}'); // Convert ":param" to "{param}"
|
|
34
|
+
if (!paths[path]) {
|
|
35
|
+
paths[path] = {};
|
|
36
|
+
}
|
|
37
|
+
const responses = {};
|
|
38
|
+
docs?.errors?.forEach((error) => {
|
|
39
|
+
const errorResponse = getErrorResponseForConstructorName(error);
|
|
40
|
+
if (errorResponse) {
|
|
41
|
+
responses[errorResponse.status] = {
|
|
42
|
+
description: errorResponse.message,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
const operation = {
|
|
47
|
+
description: docs?.description ||
|
|
48
|
+
`This endpoint handles the ${method.toUpperCase()} request for the route ${route}.`,
|
|
49
|
+
tags: docs?.tags || [route.split('/')[1] || 'default'],
|
|
50
|
+
parameters: [],
|
|
51
|
+
responses: {
|
|
52
|
+
...responses,
|
|
53
|
+
'200': {
|
|
54
|
+
description: 'Successful response',
|
|
55
|
+
content: output
|
|
56
|
+
? {
|
|
57
|
+
'application/json': {
|
|
58
|
+
schema: typeof output === 'string' &&
|
|
59
|
+
['boolean', 'string', 'number'].includes(output)
|
|
60
|
+
? { type: output }
|
|
61
|
+
: { $ref: `#/components/schemas/${output}` },
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
: undefined,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const bodyType = inputTypes?.body;
|
|
69
|
+
if (bodyType) {
|
|
70
|
+
operation.requestBody = {
|
|
71
|
+
required: true,
|
|
72
|
+
content: {
|
|
73
|
+
'application/json': {
|
|
74
|
+
schema: typeof bodyType === 'string' &&
|
|
75
|
+
['boolean', 'string', 'number'].includes(bodyType)
|
|
76
|
+
? { type: bodyType }
|
|
77
|
+
: { $ref: `#/components/schemas/${bodyType}` },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (params) {
|
|
83
|
+
operation.parameters = params.map((param) => ({
|
|
84
|
+
name: param,
|
|
85
|
+
in: 'path',
|
|
86
|
+
required: true,
|
|
87
|
+
schema: { type: 'string' },
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
if (query) {
|
|
91
|
+
operation.parameters.push(...query.map((query) => ({
|
|
92
|
+
name: query,
|
|
93
|
+
in: 'query',
|
|
94
|
+
required: false,
|
|
95
|
+
schema: { type: 'string' },
|
|
96
|
+
})));
|
|
97
|
+
}
|
|
98
|
+
paths[path][method] = operation;
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
openapi: '3.1.0',
|
|
102
|
+
info: additionalInfo.info,
|
|
103
|
+
servers: additionalInfo.servers,
|
|
104
|
+
paths,
|
|
105
|
+
components: {
|
|
106
|
+
schemas: await convertSchemasToBodyPayloads(routeMeta, schemas),
|
|
107
|
+
responses: {},
|
|
108
|
+
parameters: {},
|
|
109
|
+
examples: {},
|
|
110
|
+
requestBodies: {},
|
|
111
|
+
headers: {},
|
|
112
|
+
securitySchemes: additionalInfo.securitySchemes || {
|
|
113
|
+
ApiKeyAuth: {
|
|
114
|
+
type: 'apiKey',
|
|
115
|
+
in: 'header',
|
|
116
|
+
name: 'x-api-key',
|
|
117
|
+
},
|
|
118
|
+
BearerAuth: {
|
|
119
|
+
type: 'http',
|
|
120
|
+
scheme: 'bearer',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
security: additionalInfo.security || [
|
|
125
|
+
{
|
|
126
|
+
ApiKeyAuth: [],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
BearerAuth: [],
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
tags: additionalInfo.tags,
|
|
133
|
+
externalDocs: additionalInfo.externalDocs,
|
|
134
|
+
// definitions
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { OpenAPISpecInfo } from './openapi
|
|
1
|
+
import { OpenAPISpecInfo } from './openapi-spec-generator.js';
|
|
2
2
|
import { InspectorFilters } from '@pikku/inspector';
|
|
3
3
|
export interface PikkuCLICoreOutputFiles {
|
|
4
4
|
outDir?: string;
|
|
5
|
-
|
|
5
|
+
functionsFile: string;
|
|
6
|
+
functionsMetaFile: string;
|
|
7
|
+
httpRoutesFile: string;
|
|
8
|
+
httpRoutesMetaFile: string;
|
|
6
9
|
channelsFile: string;
|
|
10
|
+
channelsMetaFile: string;
|
|
7
11
|
schedulersFile: string;
|
|
12
|
+
schedulersMetaFile: string;
|
|
8
13
|
schemaDirectory: string;
|
|
9
14
|
typesDeclarationFile: string;
|
|
10
|
-
|
|
15
|
+
httpRoutesMapDeclarationFile: string;
|
|
11
16
|
channelsMapDeclarationFile: string;
|
|
12
17
|
bootstrapFile: string;
|
|
13
18
|
}
|
|
@@ -15,7 +20,7 @@ export type PikkuCLIConfig = {
|
|
|
15
20
|
$schema?: string;
|
|
16
21
|
extends?: string;
|
|
17
22
|
rootDir: string;
|
|
18
|
-
|
|
23
|
+
srcDirectories: string[];
|
|
19
24
|
packageMappings: Record<string, string>;
|
|
20
25
|
supportsImportAttributes: boolean;
|
|
21
26
|
configDir: string;
|
|
@@ -56,20 +56,35 @@ const _getPikkuCLIConfig = async (configFile = undefined, requiredFields, tags =
|
|
|
56
56
|
if (!result.schemaDirectory) {
|
|
57
57
|
result.schemaDirectory = join(result.outDir, 'pikku-schemas');
|
|
58
58
|
}
|
|
59
|
-
if (!result.
|
|
60
|
-
result.
|
|
59
|
+
if (!result.functionsFile) {
|
|
60
|
+
result.functionsFile = join(result.outDir, 'pikku-functions.gen.ts');
|
|
61
|
+
}
|
|
62
|
+
if (!result.functionsMetaFile) {
|
|
63
|
+
result.functionsMetaFile = join(result.outDir, 'pikku-functions-meta.gen.ts');
|
|
64
|
+
}
|
|
65
|
+
if (!result.httpRoutesFile) {
|
|
66
|
+
result.httpRoutesFile = join(result.outDir, 'pikku-http-routes.gen.ts');
|
|
67
|
+
}
|
|
68
|
+
if (!result.httpRoutesMetaFile) {
|
|
69
|
+
result.httpRoutesMetaFile = join(result.outDir, 'pikku-http-routes-meta.gen.ts');
|
|
61
70
|
}
|
|
62
71
|
if (!result.schedulersFile) {
|
|
63
72
|
result.schedulersFile = join(result.outDir, 'pikku-schedules.gen.ts');
|
|
64
73
|
}
|
|
74
|
+
if (!result.schedulersMetaFile) {
|
|
75
|
+
result.schedulersMetaFile = join(result.outDir, 'pikku-schedules-meta.gen.ts');
|
|
76
|
+
}
|
|
65
77
|
if (!result.channelsFile) {
|
|
66
78
|
result.channelsFile = join(result.outDir, 'pikku-channels.gen.ts');
|
|
67
79
|
}
|
|
80
|
+
if (!result.channelsMetaFile) {
|
|
81
|
+
result.channelsMetaFile = join(result.outDir, 'pikku-channels-meta.gen.ts');
|
|
82
|
+
}
|
|
68
83
|
if (!result.typesDeclarationFile) {
|
|
69
84
|
result.typesDeclarationFile = join(result.outDir, 'pikku-types.gen.ts');
|
|
70
85
|
}
|
|
71
|
-
if (!result.
|
|
72
|
-
result.
|
|
86
|
+
if (!result.httpRoutesMapDeclarationFile) {
|
|
87
|
+
result.httpRoutesMapDeclarationFile = join(result.outDir, 'pikku-routes-map.gen.d.ts');
|
|
73
88
|
}
|
|
74
89
|
if (!result.channelsMapDeclarationFile) {
|
|
75
90
|
result.channelsMapDeclarationFile = join(result.outDir, 'pikku-channels-map.gen.d.ts');
|
|
@@ -15,8 +15,9 @@ export const serializeSchedulerMeta = (scheduledTasksMeta) => {
|
|
|
15
15
|
const serializedOutput = [];
|
|
16
16
|
serializedOutput.push("import { pikkuState } from '@pikku/core'");
|
|
17
17
|
serializedOutput.push(`pikkuState('scheduler', 'meta', ${JSON.stringify(scheduledTasksMeta, null, 2)})`);
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const scheduledTasksMetaValues = Object.values(scheduledTasksMeta);
|
|
19
|
+
if (scheduledTasksMetaValues.length > 0) {
|
|
20
|
+
serializedOutput.push(`export type ScheduledTaskNames = '${scheduledTasksMetaValues.map((s) => s.name).join("' | '")}'`);
|
|
20
21
|
}
|
|
21
22
|
return serializedOutput.join('\n');
|
|
22
23
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JSONValue } from '@pikku/core';
|
|
2
2
|
import { HTTPRoutesMeta } from '@pikku/core/http';
|
|
3
3
|
import { TypesMap } from '@pikku/inspector';
|
|
4
|
-
export declare function generateSchemas(tsconfig: string,
|
|
4
|
+
export declare function generateSchemas(tsconfig: string, typesMaps: TypesMap[], httpRoutesMeta: HTTPRoutesMeta): Promise<Record<string, JSONValue>>;
|
|
5
5
|
export declare function saveSchemas(schemaParentDir: string, schemas: Record<string, JSONValue>, typesMap: TypesMap, routesMeta: HTTPRoutesMeta, supportsImportAttributes: boolean): Promise<void>;
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import { createGenerator, RootlessError } from 'ts-json-schema-generator';
|
|
2
2
|
import { logInfo, writeFileInDir } from '../utils.js';
|
|
3
3
|
import { mkdir, writeFile } from 'fs/promises';
|
|
4
|
-
export async function generateSchemas(tsconfig,
|
|
5
|
-
const schemasSet = new Set(
|
|
6
|
-
for (const { input,
|
|
4
|
+
export async function generateSchemas(tsconfig, typesMaps, httpRoutesMeta) {
|
|
5
|
+
const schemasSet = new Set(typesMaps.flatMap((tm) => [...tm.customTypes.keys()]));
|
|
6
|
+
for (const { input, inputTypes } of httpRoutesMeta) {
|
|
7
7
|
if (input) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
for (const typesMap of typesMaps) {
|
|
9
|
+
const uniqueName = typesMap.getUniqueName(input);
|
|
10
|
+
if (uniqueName) {
|
|
11
|
+
schemasSet.add(uniqueName);
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
12
15
|
}
|
|
16
|
+
// if (output) {
|
|
17
|
+
// for (const typesMap of typesMaps) {
|
|
18
|
+
// const uniqueName = typesMap.getUniqueName(output)
|
|
19
|
+
// if (uniqueName) {
|
|
20
|
+
// console.log('Adding output schema:', uniqueName)
|
|
21
|
+
// schemasSet.add(uniqueName)
|
|
22
|
+
// break
|
|
23
|
+
// }
|
|
24
|
+
// }
|
|
25
|
+
// }
|
|
13
26
|
if (inputTypes?.body) {
|
|
14
27
|
schemasSet.add(inputTypes.body);
|
|
15
28
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { JSONValue } from '@pikku/core';
|
|
2
|
+
import { HTTPRoutesMeta } from '@pikku/core/http';
|
|
3
|
+
import { TypesMap } from '@pikku/inspector';
|
|
4
|
+
export declare function generateSchemas(tsconfig: string, typesMaps: TypesMap[], httpRoutesMeta: HTTPRoutesMeta): Promise<Record<string, JSONValue>>;
|
|
5
|
+
export declare function saveSchemas(schemaParentDir: string, schemas: Record<string, JSONValue>, typesMap: TypesMap, routesMeta: HTTPRoutesMeta, supportsImportAttributes: boolean): Promise<void>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createGenerator, RootlessError } from 'ts-json-schema-generator';
|
|
2
|
+
import { logInfo, writeFileInDir } from './utils.js';
|
|
3
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
4
|
+
export async function generateSchemas(tsconfig, typesMaps, httpRoutesMeta) {
|
|
5
|
+
const schemasSet = new Set(typesMaps.flatMap((tm) => [...tm.customTypes.keys()]));
|
|
6
|
+
for (const { input, inputTypes } of httpRoutesMeta) {
|
|
7
|
+
if (input) {
|
|
8
|
+
let found = false;
|
|
9
|
+
for (const typesMap of typesMaps) {
|
|
10
|
+
try {
|
|
11
|
+
const uniqueName = typesMap.getUniqueName(input);
|
|
12
|
+
if (uniqueName) {
|
|
13
|
+
found = true;
|
|
14
|
+
schemasSet.add(uniqueName);
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!found) {
|
|
22
|
+
console.error('Input type not found in any types map:', input);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// if (output) {
|
|
26
|
+
// for (const typesMap of typesMaps) {
|
|
27
|
+
// const uniqueName = typesMap.getUniqueName(output)
|
|
28
|
+
// if (uniqueName) {
|
|
29
|
+
// console.log('Adding output schema:', uniqueName)
|
|
30
|
+
// schemasSet.add(uniqueName)
|
|
31
|
+
// break
|
|
32
|
+
// }
|
|
33
|
+
// }
|
|
34
|
+
// }
|
|
35
|
+
if (inputTypes?.body) {
|
|
36
|
+
schemasSet.add(inputTypes.body);
|
|
37
|
+
}
|
|
38
|
+
if (inputTypes?.query) {
|
|
39
|
+
schemasSet.add(inputTypes.query);
|
|
40
|
+
}
|
|
41
|
+
if (inputTypes?.params) {
|
|
42
|
+
schemasSet.add(inputTypes.params);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const generator = createGenerator({
|
|
46
|
+
tsconfig,
|
|
47
|
+
skipTypeCheck: true,
|
|
48
|
+
topRef: false,
|
|
49
|
+
discriminatorType: 'open-api',
|
|
50
|
+
});
|
|
51
|
+
const schemas = {};
|
|
52
|
+
schemasSet.forEach((schema) => {
|
|
53
|
+
try {
|
|
54
|
+
schemas[schema] = generator.createSchema(schema);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
// Ignore rootless errors
|
|
58
|
+
if (e instanceof RootlessError) {
|
|
59
|
+
console.error('Error generating schema since it has no root:', schema);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return schemas;
|
|
66
|
+
}
|
|
67
|
+
export async function saveSchemas(schemaParentDir, schemas, typesMap, routesMeta, supportsImportAttributes) {
|
|
68
|
+
await writeFileInDir(`${schemaParentDir}/register.gen.ts`, 'export const empty = null;');
|
|
69
|
+
const desiredSchemas = new Set([
|
|
70
|
+
...routesMeta
|
|
71
|
+
.map(({ input, output }) => [
|
|
72
|
+
input ? typesMap.getUniqueName(input) : undefined,
|
|
73
|
+
output ? typesMap.getUniqueName(output) : undefined,
|
|
74
|
+
])
|
|
75
|
+
.flat()
|
|
76
|
+
.filter((s) => !!s &&
|
|
77
|
+
!['boolean', 'string', 'number', 'null', 'undefined'].includes(s)),
|
|
78
|
+
...typesMap.customTypes.keys(),
|
|
79
|
+
]);
|
|
80
|
+
if (desiredSchemas.size === 0) {
|
|
81
|
+
logInfo(`• Skipping schemas since none found.\x1b[0m`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await mkdir(`${schemaParentDir}/schemas`, { recursive: true });
|
|
85
|
+
await Promise.all(Object.entries(schemas).map(async ([schemaName, schema]) => {
|
|
86
|
+
if (desiredSchemas.has(schemaName)) {
|
|
87
|
+
await writeFile(`${schemaParentDir}/schemas/${schemaName}.schema.json`, JSON.stringify(schema), 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
const schemaImports = Array.from(desiredSchemas)
|
|
91
|
+
.map((schema) => `
|
|
92
|
+
import * as ${schema} from './schemas/${schema}.schema.json' ${supportsImportAttributes ? `with { type: 'json' }` : ''}
|
|
93
|
+
addSchema('${schema}', ${schema})
|
|
94
|
+
`)
|
|
95
|
+
.join('\n');
|
|
96
|
+
await writeFileInDir(`${schemaParentDir}/register.gen.ts`, `import { addSchema } from '@pikku/core/schema'
|
|
97
|
+
${schemaImports}`);
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const serializeFetchWrapper: (routesMapPath: string) => string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const serializeFetchWrapper = (routesMapPath) => {
|
|
2
|
+
return `
|
|
3
|
+
import { CorePikkuFetch, HTTPMethod } from '@pikku/fetch'
|
|
4
|
+
import type { RoutesMap, RouteHandlerOf, RoutesWithMethod } from '${routesMapPath}'
|
|
5
|
+
|
|
6
|
+
export class PikkuFetch extends CorePikkuFetch {
|
|
7
|
+
public async post<Route extends RoutesWithMethod<'POST'>>(
|
|
8
|
+
route: Route,
|
|
9
|
+
...args: null extends RouteHandlerOf<Route, 'POST'>['input']
|
|
10
|
+
? [data?: Exclude<RouteHandlerOf<Route, 'POST'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
|
11
|
+
: [data: RouteHandlerOf<Route, 'POST'>['input'], options?: Omit<RequestInit, 'body'>]
|
|
12
|
+
): Promise<RouteHandlerOf<Route, 'POST'>['output']> {
|
|
13
|
+
const [data, options] = args;
|
|
14
|
+
return super.api(route, 'POST', data, options);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async get<Route extends RoutesWithMethod<'GET'>>(
|
|
18
|
+
route: Route,
|
|
19
|
+
...args: null extends RouteHandlerOf<Route, 'GET'>['input']
|
|
20
|
+
? [data?: Exclude<RouteHandlerOf<Route, 'GET'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
|
21
|
+
: [data: RouteHandlerOf<Route, 'GET'>['input'], options?: Omit<RequestInit, 'body'>]
|
|
22
|
+
): Promise<RouteHandlerOf<Route, 'GET'>['output']> {
|
|
23
|
+
const [data, options] = args;
|
|
24
|
+
return super.api(route, 'GET', data, options);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async patch<Route extends RoutesWithMethod<'PATCH'>>(
|
|
28
|
+
route: Route,
|
|
29
|
+
...args: null extends RouteHandlerOf<Route, 'PATCH'>['input']
|
|
30
|
+
? [data?: Exclude<RouteHandlerOf<Route, 'PATCH'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
|
31
|
+
: [data: RouteHandlerOf<Route, 'PATCH'>['input'], options?: Omit<RequestInit, 'body'>]
|
|
32
|
+
): Promise<RouteHandlerOf<Route, 'PATCH'>['output']> {
|
|
33
|
+
const [data, options] = args;
|
|
34
|
+
return super.api(route, 'PATCH', data, options);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async head<Route extends RoutesWithMethod<'HEAD'>>(
|
|
38
|
+
route: Route,
|
|
39
|
+
...args: null extends RouteHandlerOf<Route, 'HEAD'>['input']
|
|
40
|
+
? [data?: Exclude<RouteHandlerOf<Route, 'HEAD'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
|
41
|
+
: [data: RouteHandlerOf<Route, 'HEAD'>['input'], options?: Omit<RequestInit, 'body'>]
|
|
42
|
+
): Promise<RouteHandlerOf<Route, 'HEAD'>['output']> {
|
|
43
|
+
const [data, options] = args;
|
|
44
|
+
return super.api(route, 'HEAD', data, options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async delete<Route extends RoutesWithMethod<'DELETE'>>(
|
|
48
|
+
route: Route,
|
|
49
|
+
...args: null extends RouteHandlerOf<Route, 'DELETE'>['input']
|
|
50
|
+
? [data?: Exclude<RouteHandlerOf<Route, 'DELETE'>['input'], null>, options?: Omit<RequestInit, 'body'>]
|
|
51
|
+
: [data: RouteHandlerOf<Route, 'DELETE'>['input'], options?: Omit<RequestInit, 'body'>]
|
|
52
|
+
): Promise<RouteHandlerOf<Route, 'DELETE'>['output']> {
|
|
53
|
+
const [data, options] = args;
|
|
54
|
+
return super.api(route, 'DELETE', data, options);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public async fetch<
|
|
58
|
+
Route extends keyof RoutesMap,
|
|
59
|
+
Method extends keyof RoutesMap[Route]
|
|
60
|
+
>(route: Route, method: Method, data: RouteHandlerOf<Route, Method>['input'], options?: Omit<RequestInit, 'body'>): Promise<Response> {
|
|
61
|
+
return await super.fetch(route, method as HTTPMethod, data, options);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const pikkuFetch = new PikkuFetch();
|
|
66
|
+
`;
|
|
67
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getFileImportRelativePath } from './utils.js';
|
|
2
|
+
export const serializeImportMap = (relativeToPath, packageMappings, typesMap, requiredTypes) => {
|
|
3
|
+
const paths = new Map();
|
|
4
|
+
Array.from(requiredTypes).forEach((requiredType) => {
|
|
5
|
+
const { originalName, uniqueName, path } = typesMap.getTypeMeta(requiredType);
|
|
6
|
+
if (!path) {
|
|
7
|
+
// This is a custom type that exists in file, so we don't need to import it
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const variables = paths.get(path) || [];
|
|
11
|
+
if (originalName === uniqueName) {
|
|
12
|
+
variables.push(originalName);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
variables.push(`${originalName} as ${uniqueName}`);
|
|
16
|
+
}
|
|
17
|
+
paths.set(path, variables);
|
|
18
|
+
});
|
|
19
|
+
const imports = [];
|
|
20
|
+
for (const [path, variables] of paths.entries()) {
|
|
21
|
+
imports.push(`import type { ${variables.join(', ')} } from '${getFileImportRelativePath(relativeToPath, path, packageMappings)}'`);
|
|
22
|
+
}
|
|
23
|
+
return imports.join('\n');
|
|
24
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const serializeNextJsBackendWrapper: (routesPath: string, routesMapPath: string, schemasPath: string, configImport: string, singleServicesFactoryImport: string, sessionServicesImport: string) => string;
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
export const serializeNextJsBackendWrapper = (
|
|
2
|
-
|
|
3
|
-
routesMapPath: string,
|
|
4
|
-
schemasPath: string,
|
|
5
|
-
configImport: string,
|
|
6
|
-
singleServicesFactoryImport: string,
|
|
7
|
-
sessionServicesImport: string
|
|
8
|
-
) => {
|
|
9
|
-
return `'server-only'
|
|
1
|
+
export const serializeNextJsBackendWrapper = (routesPath, routesMapPath, schemasPath, configImport, singleServicesFactoryImport, sessionServicesImport) => {
|
|
2
|
+
return `'server-only'
|
|
10
3
|
|
|
11
4
|
/**
|
|
12
5
|
* This file provides a wrapper around the PikkuNextJS class to allow for methods to be type checked against your routes.
|
|
@@ -183,5 +176,5 @@ export const pikku = (_options?: any) => {
|
|
|
183
176
|
staticPost
|
|
184
177
|
}
|
|
185
178
|
}
|
|
186
|
-
|
|
187
|
-
}
|
|
179
|
+
`;
|
|
180
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const serializeNextJsHTTPWrapper: (routesMapPath: string, pikkuFetchImport: string) => string;
|
package/{src/nextjs/serialize-nextjs-http-wrapper.ts → dist/src/serialize-nextjs-http-wrapper.js}
RENAMED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
export const serializeNextJsHTTPWrapper = (
|
|
2
|
-
|
|
3
|
-
pikkuFetchImport: string
|
|
4
|
-
) => {
|
|
5
|
-
return `'server-only'
|
|
1
|
+
export const serializeNextJsHTTPWrapper = (routesMapPath, pikkuFetchImport) => {
|
|
2
|
+
return `'server-only'
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
5
|
* This file provides a wrapper around the PikkuNextJS class to allow for methods to be type checked against your routes.
|
|
@@ -159,5 +156,5 @@ export const pikku = (options?: CorePikkuFetchOptions) => {
|
|
|
159
156
|
staticPost
|
|
160
157
|
}
|
|
161
158
|
}
|
|
162
|
-
|
|
163
|
-
}
|
|
159
|
+
`;
|
|
160
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
*/
|
|
4
|
+
export const serializePikkuTypes = (userSessionTypeImport, userSessionTypeName, singletonServicesTypeImport, singletonServicesTypeName, sessionServicesTypeImport, servicesTypeName) => {
|
|
5
|
+
return `/**
|
|
6
|
+
* This is used to provide the application types in the typescript project
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { CoreAPIPermission, PikkuMiddleware } from '@pikku/core'
|
|
10
|
+
import { CoreAPIFunction, CoreAPIFunctionSessionless } from '@pikku/core/function'
|
|
11
|
+
import { CoreHTTPFunctionRoute, AssertRouteParams, addHTTPRoute as addCoreHTTPRoute } from '@pikku/core/http'
|
|
12
|
+
import { CoreScheduledTask, addScheduledTask as addCoreScheduledTask } from '@pikku/core/scheduler'
|
|
13
|
+
import { CoreAPIChannel, PikkuChannel, addChannel as addCoreChannel } from '@pikku/core/channel'
|
|
14
|
+
|
|
15
|
+
${userSessionTypeImport}
|
|
16
|
+
${singletonServicesTypeImport}
|
|
17
|
+
${sessionServicesTypeImport}
|
|
18
|
+
|
|
19
|
+
export type APIPermission<In = unknown, RequiredServices extends ${singletonServicesTypeName} = ${singletonServicesTypeName}> = CoreAPIPermission<In, RequiredServices, ${userSessionTypeName}>
|
|
20
|
+
export type APIMiddleware<RequiredServices extends ${singletonServicesTypeName} = ${singletonServicesTypeName}> = PikkuMiddleware<RequiredServices, ${userSessionTypeName}>
|
|
21
|
+
|
|
22
|
+
type APIFunctionSessionless<
|
|
23
|
+
In = unknown,
|
|
24
|
+
Out = never,
|
|
25
|
+
ChannelData = null, // null means optional channel
|
|
26
|
+
RequiredServices extends Services = Services & (
|
|
27
|
+
[ChannelData] extends [null]
|
|
28
|
+
? { channel?: PikkuChannel<unknown, Out> } // Optional channel
|
|
29
|
+
: { channel: PikkuChannel<ChannelData, Out> } // Required channel with any data type
|
|
30
|
+
)
|
|
31
|
+
> = CoreAPIFunctionSessionless<In, Out, ChannelData, RequiredServices, ${userSessionTypeName}>
|
|
32
|
+
|
|
33
|
+
type APIFunction<
|
|
34
|
+
In = unknown,
|
|
35
|
+
Out = never,
|
|
36
|
+
ChannelData = null, // null means optional channel
|
|
37
|
+
RequiredServices extends Services = Services & (
|
|
38
|
+
[ChannelData] extends [null]
|
|
39
|
+
? { channel?: PikkuChannel<unknown, Out> } // Optional channel
|
|
40
|
+
: { channel: PikkuChannel<ChannelData, Out> } // Required channel with any data type
|
|
41
|
+
)
|
|
42
|
+
> = CoreAPIFunction<In, Out, ChannelData, RequiredServices, ${userSessionTypeName}>
|
|
43
|
+
|
|
44
|
+
type APIRoute<In, Out, Route extends string> = CoreHTTPFunctionRoute<In, Out, Route, APIFunction<In, Out>, APIFunctionSessionless<In, Out>, APIPermission<In>, APIMiddleware>
|
|
45
|
+
type APIChannel<ChannelData, Channel extends string> = CoreAPIChannel<ChannelData, Channel, APIFunction<void, unknown> | APIFunction<void, unknown, ChannelData>, APIFunction<void, void> | APIFunction<void, void, ChannelData>, APIFunction<any, any> | APIFunction<any, any, ChannelData>, APIPermission>
|
|
46
|
+
type ScheduledTask = CoreScheduledTask<APIFunctionSessionless<void, void>, ${userSessionTypeName}>
|
|
47
|
+
|
|
48
|
+
export const pikkuFunc = <In, Out = unknown>(
|
|
49
|
+
func:
|
|
50
|
+
| APIFunction<In, Out>
|
|
51
|
+
| {
|
|
52
|
+
func: APIFunction<In, Out>
|
|
53
|
+
auth?: true
|
|
54
|
+
name?: string
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
func: APIFunctionSessionless<In, Out>
|
|
58
|
+
auth: false
|
|
59
|
+
name?: string
|
|
60
|
+
}
|
|
61
|
+
) => {
|
|
62
|
+
return typeof func === 'function' ? func : func.func
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const pikkuSessionlessFunc = <In, Out = unknown>(
|
|
66
|
+
func:
|
|
67
|
+
| APIFunctionSessionless<In, Out>
|
|
68
|
+
| {
|
|
69
|
+
func: APIFunctionSessionless<In, Out>
|
|
70
|
+
name?: string
|
|
71
|
+
}
|
|
72
|
+
) => {
|
|
73
|
+
return typeof func === 'function' ? func : func.func
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const pikkuChannelConnectionFunc = <Out = unknown, ChannelData = unknown>(
|
|
77
|
+
func:
|
|
78
|
+
| APIFunctionSessionless<void, Out, ChannelData>
|
|
79
|
+
| {
|
|
80
|
+
func: APIFunctionSessionless<void, Out, ChannelData>
|
|
81
|
+
name?: string
|
|
82
|
+
}
|
|
83
|
+
) => {
|
|
84
|
+
return typeof func === 'function' ? func : func.func
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const pikkuChannelDisconnectionFunc = <ChannelData = unknown>(
|
|
88
|
+
func:
|
|
89
|
+
| APIFunctionSessionless<void, void, ChannelData>
|
|
90
|
+
| {
|
|
91
|
+
func: APIFunction<void, void, ChannelData>
|
|
92
|
+
name?: string
|
|
93
|
+
}
|
|
94
|
+
) => {
|
|
95
|
+
return typeof func === 'function' ? func : func.func
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const pikkuChannelFunc = <In = unknown, Out = unknown, ChannelData = unknown>(
|
|
99
|
+
func:
|
|
100
|
+
| APIFunctionSessionless<In, Out, ChannelData>
|
|
101
|
+
| {
|
|
102
|
+
func: APIFunctionSessionless<In, Out, ChannelData>
|
|
103
|
+
name?: string
|
|
104
|
+
}
|
|
105
|
+
) => {
|
|
106
|
+
return typeof func === 'function' ? func : func.func
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const pikkuVoidFunc = (
|
|
110
|
+
func:
|
|
111
|
+
| APIFunctionSessionless<void, void>
|
|
112
|
+
| {
|
|
113
|
+
func: APIFunctionSessionless<void, void>
|
|
114
|
+
name?: string
|
|
115
|
+
}
|
|
116
|
+
) => {
|
|
117
|
+
return typeof func === 'function' ? func : func.func
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const addChannel = <ChannelData, Channel extends string>(
|
|
121
|
+
channel: APIChannel<ChannelData, Channel> & AssertRouteParams<ChannelData, Channel>
|
|
122
|
+
) => {
|
|
123
|
+
addCoreChannel(channel as any) // TODO
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const addHTTPRoute = <In, Out, Route extends string>(
|
|
127
|
+
route: APIRoute<In, Out, Route> & AssertRouteParams<In, Route>
|
|
128
|
+
) => {
|
|
129
|
+
addCoreHTTPRoute(route)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const addScheduledTask = (task: ScheduledTask) => {
|
|
133
|
+
addCoreScheduledTask(task as any) // TODO
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
};
|