@rvoh/psychic 0.26.2 → 0.27.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/dist/cjs/src/bin/helpers/enumsAndTheirValues.js +3 -2
- package/dist/cjs/src/bin/helpers/enumsFileStr.js +1 -1
- package/dist/cjs/src/devtools/helpers/launchDevServer.js +3 -0
- package/dist/cjs/src/error/UnexpectedUndefined.js +14 -0
- package/dist/cjs/src/generate/controller.js +12 -1
- package/dist/cjs/src/generate/helpers/addResourceToRoutes.js +3 -0
- package/dist/cjs/src/helpers/openapiJsonPath.js +5 -1
- package/dist/cjs/src/i18n/provider.js +4 -1
- package/dist/cjs/src/openapi-renderer/app.js +29 -17
- package/dist/cjs/src/openapi-renderer/body-segment.js +3 -2
- package/dist/cjs/src/openapi-renderer/endpoint.js +30 -15
- package/dist/cjs/src/openapi-renderer/serializer.js +25 -13
- package/dist/esm/src/bin/helpers/enumsAndTheirValues.js +3 -2
- package/dist/esm/src/bin/helpers/enumsFileStr.js +1 -1
- package/dist/esm/src/devtools/helpers/launchDevServer.js +3 -0
- package/dist/esm/src/error/UnexpectedUndefined.js +11 -0
- package/dist/esm/src/generate/controller.js +12 -1
- package/dist/esm/src/generate/helpers/addResourceToRoutes.js +3 -0
- package/dist/esm/src/helpers/openapiJsonPath.js +5 -1
- package/dist/esm/src/i18n/provider.js +4 -1
- package/dist/esm/src/openapi-renderer/app.js +29 -17
- package/dist/esm/src/openapi-renderer/body-segment.js +3 -2
- package/dist/esm/src/openapi-renderer/endpoint.js +30 -15
- package/dist/esm/src/openapi-renderer/serializer.js +25 -13
- package/dist/types/src/error/UnexpectedUndefined.d.ts +4 -0
- package/dist/types/src/generate/helpers/generateControllerContent.d.ts +3 -3
- package/package.json +8 -5
|
@@ -10,8 +10,9 @@ SELECT pg_type.typname AS enum_type, pg_enum.enumlabel AS enum_label FROM pg_typ
|
|
|
10
10
|
`.execute((0, dream_1.db)('primary'));
|
|
11
11
|
const rowData = {};
|
|
12
12
|
rows.forEach(row => {
|
|
13
|
-
|
|
14
|
-
rowData[
|
|
13
|
+
const enumType = row.enumType;
|
|
14
|
+
rowData[enumType] ||= [];
|
|
15
|
+
rowData[enumType].push(row.enumLabel);
|
|
15
16
|
});
|
|
16
17
|
return rowData;
|
|
17
18
|
}
|
|
@@ -9,7 +9,7 @@ async function enumsFileStr() {
|
|
|
9
9
|
let enumsFileStr = (0, autogeneratedFileDisclaimer_js_1.default)();
|
|
10
10
|
Object.keys(enums).forEach(enumName => {
|
|
11
11
|
const exportedTypeName = (0, pascalizeFileName_js_1.default)(enumName) + 'Values';
|
|
12
|
-
const values = enums[enumName];
|
|
12
|
+
const values = enums[enumName] || [];
|
|
13
13
|
enumsFileStr += `\
|
|
14
14
|
export const ${exportedTypeName} = [
|
|
15
15
|
${values.map(val => `'${val}'`).join(',\n ')}
|
|
@@ -6,6 +6,7 @@ exports.stopDevServers = stopDevServers;
|
|
|
6
6
|
const child_process_1 = require("child_process");
|
|
7
7
|
const net_1 = require("net");
|
|
8
8
|
const sleep_js_1 = require("../../../spec/helpers/sleep.js");
|
|
9
|
+
const UnexpectedUndefined_js_1 = require("../../error/UnexpectedUndefined.js");
|
|
9
10
|
const devServerProcesses = {};
|
|
10
11
|
async function launchDevServer(key, { port = 3000, cmd = 'yarn client', timeout = 5000 } = {}) {
|
|
11
12
|
if (devServerProcesses[key])
|
|
@@ -13,6 +14,8 @@ async function launchDevServer(key, { port = 3000, cmd = 'yarn client', timeout
|
|
|
13
14
|
if (process.env.DEBUG === '1')
|
|
14
15
|
console.log('Starting server...');
|
|
15
16
|
const [_cmd, ...args] = cmd.split(' ');
|
|
17
|
+
if (_cmd === undefined)
|
|
18
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
16
19
|
const proc = (0, child_process_1.spawn)(_cmd, args, {
|
|
17
20
|
detached: true,
|
|
18
21
|
env: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class UnexpectedUndefined extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
}
|
|
7
|
+
get message() {
|
|
8
|
+
return `Undefined detected where it should never happen since we are iterating
|
|
9
|
+
over keys of an internal object that should not have undefined values.
|
|
10
|
+
|
|
11
|
+
This was added as part of activating noUncheckedIndexedAccess in tsconfig.`;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.default = UnexpectedUndefined;
|
|
@@ -4,6 +4,7 @@ exports.default = generateController;
|
|
|
4
4
|
const dream_1 = require("@rvoh/dream");
|
|
5
5
|
const fs = require("fs/promises");
|
|
6
6
|
const node_fs_1 = require("node:fs");
|
|
7
|
+
const UnexpectedUndefined_js_1 = require("../error/UnexpectedUndefined.js");
|
|
7
8
|
const EnvInternal_js_1 = require("../helpers/EnvInternal.js");
|
|
8
9
|
const psychicFileAndDirPaths_js_1 = require("../helpers/path/psychicFileAndDirPaths.js");
|
|
9
10
|
const psychicPath_js_1 = require("../helpers/path/psychicPath.js");
|
|
@@ -23,6 +24,10 @@ async function generateController({ fullyQualifiedControllerName, fullyQualified
|
|
|
23
24
|
if (controllerNameParts.length > (isAdmin ? 1 : 0)) {
|
|
24
25
|
// Write the ancestor controller
|
|
25
26
|
const [baseAncestorName, baseAncestorImportStatement] = baseAncestorNameAndImport(controllerNameParts, isAdmin, { forBaseController: true });
|
|
27
|
+
if (baseAncestorName === undefined)
|
|
28
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
29
|
+
if (baseAncestorImportStatement === undefined)
|
|
30
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
26
31
|
const baseControllerName = [...controllerNameParts, 'BaseController'].join('/');
|
|
27
32
|
const { absDirPath, absFilePath } = (0, psychicFileAndDirPaths_js_1.default)((0, psychicPath_js_1.default)('controllers'), baseControllerName + `.ts`);
|
|
28
33
|
await fs.mkdir(absDirPath, { recursive: true });
|
|
@@ -35,12 +40,18 @@ async function generateController({ fullyQualifiedControllerName, fullyQualified
|
|
|
35
40
|
}));
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
|
-
|
|
43
|
+
const namedPart = allControllerNameParts[index];
|
|
44
|
+
if (namedPart)
|
|
45
|
+
controllerNameParts.push(namedPart);
|
|
39
46
|
}
|
|
40
47
|
// Write the controller
|
|
41
48
|
const [ancestorName, ancestorImportStatement] = baseAncestorNameAndImport(controllerNameParts, isAdmin, {
|
|
42
49
|
forBaseController: false,
|
|
43
50
|
});
|
|
51
|
+
if (ancestorName === undefined)
|
|
52
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
53
|
+
if (ancestorImportStatement === undefined)
|
|
54
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
44
55
|
const { relFilePath, absDirPath, absFilePath } = (0, psychicFileAndDirPaths_js_1.default)((0, psychicPath_js_1.default)('controllers'), fullyQualifiedControllerName + `.ts`);
|
|
45
56
|
await fs.mkdir(absDirPath, { recursive: true });
|
|
46
57
|
try {
|
|
@@ -4,6 +4,7 @@ exports.default = addResourceToRoutes;
|
|
|
4
4
|
exports.addResourceToRoutes_routeToRegexAndReplacements = addResourceToRoutes_routeToRegexAndReplacements;
|
|
5
5
|
const fs = require("fs/promises");
|
|
6
6
|
const path = require("path");
|
|
7
|
+
const UnexpectedUndefined_js_1 = require("../../error/UnexpectedUndefined.js");
|
|
7
8
|
const psychicPath_js_1 = require("../../helpers/path/psychicPath.js");
|
|
8
9
|
const index_js_1 = require("../../psychic-application/index.js");
|
|
9
10
|
async function addResourceToRoutes(route) {
|
|
@@ -13,6 +14,8 @@ async function addResourceToRoutes(route) {
|
|
|
13
14
|
const matchesAndReplacements = addResourceToRoutes_routeToRegexAndReplacements(route);
|
|
14
15
|
for (let index = 0; index < matchesAndReplacements.length; index++) {
|
|
15
16
|
const matchAndReplacement = matchesAndReplacements[index];
|
|
17
|
+
if (matchAndReplacement === undefined)
|
|
18
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
16
19
|
if (matchAndReplacement.regex.test(routes)) {
|
|
17
20
|
routes = routes.replace(matchAndReplacement.regex, matchAndReplacement.replacement +
|
|
18
21
|
closeBrackets(index, indent(matchesAndReplacements.length - index - 1)) +
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = openapiJsonPath;
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const UnexpectedUndefined_js_1 = require("../error/UnexpectedUndefined.js");
|
|
5
6
|
const index_js_1 = require("../psychic-application/index.js");
|
|
6
7
|
function openapiJsonPath(openapiName = 'default') {
|
|
7
8
|
const psychicApp = index_js_1.default.getOrFail();
|
|
8
|
-
|
|
9
|
+
const namedOpenapi = psychicApp.openapi[openapiName];
|
|
10
|
+
if (namedOpenapi === undefined)
|
|
11
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
12
|
+
return path.join(psychicApp.apiRoot, namedOpenapi.outputFilename);
|
|
9
13
|
}
|
|
@@ -51,7 +51,10 @@ function applyInterpolations(i18nPathString, str, interpolations) {
|
|
|
51
51
|
return str;
|
|
52
52
|
}
|
|
53
53
|
function _i18n(i18nHash, i18nPath) {
|
|
54
|
-
const
|
|
54
|
+
const index = i18nPath[0];
|
|
55
|
+
if (index === undefined)
|
|
56
|
+
throw new TranslationMissing();
|
|
57
|
+
const translation = i18nHash[index];
|
|
55
58
|
if (translation === undefined)
|
|
56
59
|
throw new TranslationMissing();
|
|
57
60
|
if (typeof translation === 'string')
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const dream_1 = require("@rvoh/dream");
|
|
3
4
|
const fs = require("fs/promises");
|
|
4
5
|
const lodash_es_1 = require("lodash-es");
|
|
6
|
+
const UnexpectedUndefined_js_1 = require("../error/UnexpectedUndefined.js");
|
|
5
7
|
const EnvInternal_js_1 = require("../helpers/EnvInternal.js");
|
|
6
8
|
const openapiJsonPath_js_1 = require("../helpers/openapiJsonPath.js");
|
|
7
9
|
const index_js_1 = require("../psychic-application/index.js");
|
|
@@ -89,22 +91,30 @@ class OpenapiAppRenderer {
|
|
|
89
91
|
if (EnvInternal_js_1.default.isDebug)
|
|
90
92
|
console.log(`Processing OpenAPI key ${key} for controller ${controllerName}`);
|
|
91
93
|
const renderer = controller.openapi[key];
|
|
94
|
+
if (renderer === undefined)
|
|
95
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
92
96
|
finalOutput.components.schemas = {
|
|
93
97
|
...finalOutput.components.schemas,
|
|
94
98
|
...renderer.toSchemaObject(openapiName, processedSchemas),
|
|
95
99
|
};
|
|
96
100
|
const endpointPayload = renderer.toPathObject(openapiName, processedSchemas, routes);
|
|
101
|
+
if (endpointPayload === undefined)
|
|
102
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
97
103
|
const path = Object.keys(endpointPayload)[0];
|
|
98
|
-
|
|
104
|
+
if (path === undefined)
|
|
105
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
106
|
+
const endpointPayloadPath = endpointPayload[path];
|
|
107
|
+
if (endpointPayloadPath === undefined)
|
|
108
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
109
|
+
const method = Object.keys(endpointPayloadPath).find(key => types_js_1.HttpMethods.includes(key));
|
|
99
110
|
if (!finalOutput.paths[path]) {
|
|
100
111
|
finalOutput.paths[path] = { parameters: [] };
|
|
101
112
|
}
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
...
|
|
107
|
-
...endpointPayload[path].parameters,
|
|
113
|
+
const finalPathObject = finalOutput.paths[path];
|
|
114
|
+
finalPathObject[method] = endpointPayloadPath[method];
|
|
115
|
+
finalPathObject.parameters = this.combineParameters([
|
|
116
|
+
...finalPathObject.parameters,
|
|
117
|
+
...endpointPayloadPath.parameters,
|
|
108
118
|
]);
|
|
109
119
|
}
|
|
110
120
|
}
|
|
@@ -112,24 +122,26 @@ class OpenapiAppRenderer {
|
|
|
112
122
|
}
|
|
113
123
|
static combineParameters(parameters) {
|
|
114
124
|
const groupedParams = (0, lodash_es_1.groupBy)(parameters, 'name');
|
|
115
|
-
|
|
116
|
-
const identicalParams = groupedParams[paramName];
|
|
125
|
+
return (0, dream_1.compact)(Object.keys(groupedParams).map(paramName => {
|
|
126
|
+
const identicalParams = groupedParams[paramName] || [];
|
|
117
127
|
return identicalParams.reduce((compositeParam, param) => {
|
|
128
|
+
if (compositeParam === undefined)
|
|
129
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
118
130
|
compositeParam.description ||= param.description;
|
|
119
|
-
if (
|
|
131
|
+
if (param.allowEmptyValue !== undefined)
|
|
120
132
|
compositeParam.allowEmptyValue = param.allowEmptyValue;
|
|
121
|
-
if (
|
|
133
|
+
if (param.allowReserved !== undefined)
|
|
122
134
|
compositeParam.allowReserved = param.allowReserved;
|
|
123
|
-
if (
|
|
135
|
+
if (param.required !== undefined)
|
|
124
136
|
compositeParam.required = param.required;
|
|
125
137
|
return compositeParam;
|
|
126
138
|
}, identicalParams[0]);
|
|
127
|
-
});
|
|
128
|
-
return result;
|
|
139
|
+
}));
|
|
129
140
|
}
|
|
130
141
|
static sortedSchemaPayload(schema) {
|
|
131
142
|
const sortedPaths = Object.keys(schema.paths).sort();
|
|
132
|
-
const
|
|
143
|
+
const schemas = schema.components.schemas || {};
|
|
144
|
+
const sortedSchemaNames = Object.keys(schemas).sort();
|
|
133
145
|
const sortedSchema = { ...schema };
|
|
134
146
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
135
147
|
sortedSchema.paths = sortedPaths.reduce((agg, path) => {
|
|
@@ -140,9 +152,9 @@ class OpenapiAppRenderer {
|
|
|
140
152
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
141
153
|
}, {});
|
|
142
154
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
143
|
-
sortedSchema.components.schemas =
|
|
155
|
+
sortedSchema.components.schemas = sortedSchemaNames.reduce((agg, key) => {
|
|
144
156
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
145
|
-
agg[key] =
|
|
157
|
+
agg[key] = schemas[key];
|
|
146
158
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
147
159
|
return agg;
|
|
148
160
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -174,8 +174,9 @@ class OpenapiBodySegmentRenderer {
|
|
|
174
174
|
if (bodySegment.nullable) {
|
|
175
175
|
data.nullable = true;
|
|
176
176
|
}
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
const description = bodySegment.description;
|
|
178
|
+
if (description) {
|
|
179
|
+
data.description = description;
|
|
179
180
|
}
|
|
180
181
|
return data;
|
|
181
182
|
}
|
|
@@ -103,19 +103,22 @@ class OpenapiEndpointRenderer {
|
|
|
103
103
|
},
|
|
104
104
|
},
|
|
105
105
|
};
|
|
106
|
+
const outputPath = output[path];
|
|
107
|
+
if (outputPath === undefined)
|
|
108
|
+
throw new Error(`no output for path ${path}`);
|
|
106
109
|
if (this.summary) {
|
|
107
|
-
|
|
110
|
+
outputPath[method].summary = this.summary;
|
|
108
111
|
}
|
|
109
112
|
if (this.description) {
|
|
110
|
-
|
|
113
|
+
outputPath[method].description = this.description;
|
|
111
114
|
}
|
|
112
115
|
if (this.security) {
|
|
113
|
-
|
|
116
|
+
outputPath[method].security = this.security;
|
|
114
117
|
}
|
|
115
118
|
if (requestBody) {
|
|
116
|
-
|
|
119
|
+
outputPath[method]['requestBody'] = requestBody;
|
|
117
120
|
}
|
|
118
|
-
|
|
121
|
+
outputPath[method].responses = responses;
|
|
119
122
|
return output;
|
|
120
123
|
}
|
|
121
124
|
/**
|
|
@@ -263,8 +266,10 @@ class OpenapiEndpointRenderer {
|
|
|
263
266
|
? {}
|
|
264
267
|
: this.openapiOpts(openapiName)?.defaults?.headers || {};
|
|
265
268
|
const headers = { ...defaultHeaders, ...(this.headers || []) };
|
|
266
|
-
return (Object.keys(headers).map((headerName) => {
|
|
269
|
+
return ((0, dream_1.compact)(Object.keys(headers).map((headerName) => {
|
|
267
270
|
const header = headers[headerName];
|
|
271
|
+
if (header === undefined)
|
|
272
|
+
return null;
|
|
268
273
|
const data = {
|
|
269
274
|
in: 'header',
|
|
270
275
|
name: headerName,
|
|
@@ -278,7 +283,7 @@ class OpenapiEndpointRenderer {
|
|
|
278
283
|
data.schema.format = header.format;
|
|
279
284
|
}
|
|
280
285
|
return data;
|
|
281
|
-
}) || []);
|
|
286
|
+
})) || []);
|
|
282
287
|
}
|
|
283
288
|
/**
|
|
284
289
|
* @internal
|
|
@@ -292,20 +297,20 @@ class OpenapiEndpointRenderer {
|
|
|
292
297
|
let output = {
|
|
293
298
|
in: 'query',
|
|
294
299
|
name: queryName,
|
|
295
|
-
description: queryParam
|
|
300
|
+
description: queryParam?.description || queryName,
|
|
296
301
|
allowReserved: true,
|
|
297
302
|
...queryParam,
|
|
298
303
|
schema: {
|
|
299
304
|
type: 'string',
|
|
300
305
|
},
|
|
301
306
|
};
|
|
302
|
-
if (typeof queryParam
|
|
307
|
+
if (typeof queryParam?.allowEmptyValue === 'boolean') {
|
|
303
308
|
output.allowEmptyValue = queryParam.allowEmptyValue;
|
|
304
309
|
}
|
|
305
|
-
if (typeof queryParam
|
|
310
|
+
if (typeof queryParam?.allowReserved === 'boolean') {
|
|
306
311
|
output.allowReserved = queryParam.allowReserved;
|
|
307
312
|
}
|
|
308
|
-
if (queryParam
|
|
313
|
+
if (queryParam?.schema) {
|
|
309
314
|
output = {
|
|
310
315
|
...output,
|
|
311
316
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
@@ -617,10 +622,16 @@ class OpenapiEndpointRenderer {
|
|
|
617
622
|
...defaultResponses,
|
|
618
623
|
});
|
|
619
624
|
Object.keys(psychicAndConfigLevelDefaults).forEach(key => {
|
|
620
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
|
621
625
|
if (!responseData[key]) {
|
|
622
|
-
|
|
623
|
-
|
|
626
|
+
const data = psychicAndConfigLevelDefaults[key];
|
|
627
|
+
switch (key) {
|
|
628
|
+
case 'summary':
|
|
629
|
+
case 'description':
|
|
630
|
+
responseData[key] = data;
|
|
631
|
+
break;
|
|
632
|
+
default:
|
|
633
|
+
responseData[key] = data;
|
|
634
|
+
}
|
|
624
635
|
}
|
|
625
636
|
});
|
|
626
637
|
return responseData;
|
|
@@ -677,6 +688,8 @@ class OpenapiEndpointRenderer {
|
|
|
677
688
|
*/
|
|
678
689
|
parseSingleEntitySerializerResponseShape() {
|
|
679
690
|
const serializerClass = this.getSerializerClasses()[0];
|
|
691
|
+
if (serializerClass === undefined)
|
|
692
|
+
throw new Error('getSerializerClasses returned no serializer classes');
|
|
680
693
|
const serializerKey = serializerClass.openapiName;
|
|
681
694
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
682
695
|
const serializerObject = this.accountForNullableOption({
|
|
@@ -818,7 +831,9 @@ class OpenapiEndpointRenderer {
|
|
|
818
831
|
const modelClass = dreamOrSerializerOrViewModel;
|
|
819
832
|
const modelPrototype = modelClass.prototype;
|
|
820
833
|
const serializerKey = modelPrototype.serializers[this.serializerKey || 'default'];
|
|
821
|
-
|
|
834
|
+
if (serializerKey === undefined)
|
|
835
|
+
throw new Error(`no serializerKey for ${this.serializerKey || 'default'}`);
|
|
836
|
+
return dreamApp.serializers[serializerKey] ?? null;
|
|
822
837
|
}
|
|
823
838
|
}
|
|
824
839
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const dream_1 = require("@rvoh/dream");
|
|
4
4
|
const CannotFlattenMultiplePolymorphicRendersOneAssociations_js_1 = require("../error/openapi/CannotFlattenMultiplePolymorphicRendersOneAssociations.js");
|
|
5
|
+
const UnexpectedUndefined_js_1 = require("../error/UnexpectedUndefined.js");
|
|
5
6
|
const EnvInternal_js_1 = require("../helpers/EnvInternal.js");
|
|
6
7
|
const index_js_1 = require("../psychic-application/index.js");
|
|
7
8
|
const body_segment_js_1 = require("./body-segment.js");
|
|
@@ -80,6 +81,9 @@ class OpenapiSerializerRenderer {
|
|
|
80
81
|
let finalOutput = { ...serializerPayload };
|
|
81
82
|
let flattenedPolymorphicSchemas = [];
|
|
82
83
|
let flattenedPolymorphicAssociation;
|
|
84
|
+
const finalOutputForSerializerKey = finalOutput[serializerKey];
|
|
85
|
+
if (finalOutputForSerializerKey === undefined)
|
|
86
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
83
87
|
associations.forEach(association => {
|
|
84
88
|
const associatedSerializers = dream_1.DreamSerializer.getAssociatedSerializersForOpenapi(association);
|
|
85
89
|
if (!associatedSerializers)
|
|
@@ -118,10 +122,10 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
118
122
|
...finalOutput,
|
|
119
123
|
[serializerKey]: {
|
|
120
124
|
anyOf: [
|
|
121
|
-
{ ...
|
|
125
|
+
{ ...finalOutputForSerializerKey },
|
|
122
126
|
{
|
|
123
127
|
allOf: [
|
|
124
|
-
{ ...
|
|
128
|
+
{ ...finalOutputForSerializerKey },
|
|
125
129
|
{ anyOf: flattenedPolymorphicSchemas.map(schema => ({ $schema: schema })) },
|
|
126
130
|
],
|
|
127
131
|
},
|
|
@@ -134,7 +138,7 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
134
138
|
...finalOutput,
|
|
135
139
|
[serializerKey]: {
|
|
136
140
|
allOf: [
|
|
137
|
-
{ ...
|
|
141
|
+
{ ...finalOutputForSerializerKey },
|
|
138
142
|
{ anyOf: flattenedPolymorphicSchemas.map(schema => ({ $schema: schema })) },
|
|
139
143
|
],
|
|
140
144
|
},
|
|
@@ -154,22 +158,27 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
154
158
|
*/
|
|
155
159
|
addSingleSerializerAssociationToOutput({ associatedSerializers, finalOutput, serializerKey, association, }) {
|
|
156
160
|
const associatedSerializer = associatedSerializers[0];
|
|
161
|
+
if (associatedSerializer === undefined)
|
|
162
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
157
163
|
const associatedSerializerKey = associatedSerializer.openapiName;
|
|
158
164
|
if (EnvInternal_js_1.default.isDebug)
|
|
159
165
|
index_js_1.default.log(`Processing serializer ${associatedSerializerKey}`);
|
|
160
166
|
let flattenedData;
|
|
167
|
+
const finalOutputForSerializerKey = finalOutput[serializerKey];
|
|
168
|
+
if (finalOutputForSerializerKey === undefined)
|
|
169
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
161
170
|
switch (association.type) {
|
|
162
171
|
case 'RendersMany':
|
|
163
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
164
173
|
;
|
|
165
|
-
|
|
174
|
+
finalOutputForSerializerKey.properties[association.field] = {
|
|
166
175
|
type: 'array',
|
|
167
176
|
items: {
|
|
168
177
|
$ref: `#/components/schemas/${associatedSerializerKey}`,
|
|
169
178
|
},
|
|
170
179
|
};
|
|
171
|
-
|
|
172
|
-
...(
|
|
180
|
+
finalOutputForSerializerKey.required = (0, dream_1.uniq)([
|
|
181
|
+
...(finalOutputForSerializerKey.required || []),
|
|
173
182
|
association.field,
|
|
174
183
|
]);
|
|
175
184
|
break;
|
|
@@ -181,8 +190,8 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
181
190
|
});
|
|
182
191
|
finalOutput[serializerKey].properties = flattenedData;
|
|
183
192
|
if (!association.optional) {
|
|
184
|
-
|
|
185
|
-
...(
|
|
193
|
+
finalOutputForSerializerKey.required = (0, dream_1.uniq)([
|
|
194
|
+
...(finalOutputForSerializerKey.required || []),
|
|
186
195
|
...Object.keys(flattenedData),
|
|
187
196
|
]);
|
|
188
197
|
}
|
|
@@ -193,8 +202,8 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
193
202
|
finalOutput[serializerKey].properties[association.field] = this.accountForNullableOption({
|
|
194
203
|
$ref: `#/components/schemas/${associatedSerializerKey}`,
|
|
195
204
|
}, association.optional);
|
|
196
|
-
|
|
197
|
-
...(
|
|
205
|
+
finalOutputForSerializerKey.required = (0, dream_1.uniq)([
|
|
206
|
+
...(finalOutputForSerializerKey.required || []),
|
|
198
207
|
association.field,
|
|
199
208
|
]);
|
|
200
209
|
}
|
|
@@ -244,6 +253,9 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
244
253
|
* each pointing to its respective target serializer.
|
|
245
254
|
*/
|
|
246
255
|
addMultiSerializerAssociationToOutput({ associatedSerializers, finalOutput, serializerKey, association, flattenedPolymorphicSchemas, flattenedPolymorphicAssociation, }) {
|
|
256
|
+
const finalOutputForSerializerKey = finalOutput[serializerKey];
|
|
257
|
+
if (finalOutputForSerializerKey === undefined)
|
|
258
|
+
throw new UnexpectedUndefined_js_1.default();
|
|
247
259
|
if (association.flatten) {
|
|
248
260
|
if (flattenedPolymorphicSchemas.length)
|
|
249
261
|
throw new CannotFlattenMultiplePolymorphicRendersOneAssociations_js_1.default(this.serializerClass, association.field);
|
|
@@ -271,8 +283,8 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
271
283
|
const associatedSerializerKey = associatedSerializer.openapiName;
|
|
272
284
|
if (EnvInternal_js_1.default.isDebug)
|
|
273
285
|
index_js_1.default.log(`Processing serializer ${associatedSerializerKey}`);
|
|
274
|
-
|
|
275
|
-
...(
|
|
286
|
+
finalOutputForSerializerKey.required = (0, dream_1.uniq)([
|
|
287
|
+
...(finalOutputForSerializerKey.required || []),
|
|
276
288
|
association.field,
|
|
277
289
|
]);
|
|
278
290
|
switch (association.type) {
|
|
@@ -7,8 +7,9 @@ SELECT pg_type.typname AS enum_type, pg_enum.enumlabel AS enum_label FROM pg_typ
|
|
|
7
7
|
`.execute(db('primary'));
|
|
8
8
|
const rowData = {};
|
|
9
9
|
rows.forEach(row => {
|
|
10
|
-
|
|
11
|
-
rowData[
|
|
10
|
+
const enumType = row.enumType;
|
|
11
|
+
rowData[enumType] ||= [];
|
|
12
|
+
rowData[enumType].push(row.enumLabel);
|
|
12
13
|
});
|
|
13
14
|
return rowData;
|
|
14
15
|
}
|
|
@@ -6,7 +6,7 @@ export default async function enumsFileStr() {
|
|
|
6
6
|
let enumsFileStr = autogeneratedFileDisclaimer();
|
|
7
7
|
Object.keys(enums).forEach(enumName => {
|
|
8
8
|
const exportedTypeName = pascalizeFileName(enumName) + 'Values';
|
|
9
|
-
const values = enums[enumName];
|
|
9
|
+
const values = enums[enumName] || [];
|
|
10
10
|
enumsFileStr += `\
|
|
11
11
|
export const ${exportedTypeName} = [
|
|
12
12
|
${values.map(val => `'${val}'`).join(',\n ')}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { createServer } from 'net';
|
|
3
3
|
import sleep from '../../../spec/helpers/sleep.js';
|
|
4
|
+
import UnexpectedUndefined from '../../error/UnexpectedUndefined.js';
|
|
4
5
|
const devServerProcesses = {};
|
|
5
6
|
export async function launchDevServer(key, { port = 3000, cmd = 'yarn client', timeout = 5000 } = {}) {
|
|
6
7
|
if (devServerProcesses[key])
|
|
@@ -8,6 +9,8 @@ export async function launchDevServer(key, { port = 3000, cmd = 'yarn client', t
|
|
|
8
9
|
if (process.env.DEBUG === '1')
|
|
9
10
|
console.log('Starting server...');
|
|
10
11
|
const [_cmd, ...args] = cmd.split(' ');
|
|
12
|
+
if (_cmd === undefined)
|
|
13
|
+
throw new UnexpectedUndefined();
|
|
11
14
|
const proc = spawn(_cmd, args, {
|
|
12
15
|
detached: true,
|
|
13
16
|
env: {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default class UnexpectedUndefined extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
}
|
|
5
|
+
get message() {
|
|
6
|
+
return `Undefined detected where it should never happen since we are iterating
|
|
7
|
+
over keys of an internal object that should not have undefined values.
|
|
8
|
+
|
|
9
|
+
This was added as part of activating noUncheckedIndexedAccess in tsconfig.`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { hyphenize, standardizeFullyQualifiedModelName } from '@rvoh/dream';
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
+
import UnexpectedUndefined from '../error/UnexpectedUndefined.js';
|
|
4
5
|
import EnvInternal from '../helpers/EnvInternal.js';
|
|
5
6
|
import psychicFileAndDirPaths from '../helpers/path/psychicFileAndDirPaths.js';
|
|
6
7
|
import psychicPath from '../helpers/path/psychicPath.js';
|
|
@@ -20,6 +21,10 @@ export default async function generateController({ fullyQualifiedControllerName,
|
|
|
20
21
|
if (controllerNameParts.length > (isAdmin ? 1 : 0)) {
|
|
21
22
|
// Write the ancestor controller
|
|
22
23
|
const [baseAncestorName, baseAncestorImportStatement] = baseAncestorNameAndImport(controllerNameParts, isAdmin, { forBaseController: true });
|
|
24
|
+
if (baseAncestorName === undefined)
|
|
25
|
+
throw new UnexpectedUndefined();
|
|
26
|
+
if (baseAncestorImportStatement === undefined)
|
|
27
|
+
throw new UnexpectedUndefined();
|
|
23
28
|
const baseControllerName = [...controllerNameParts, 'BaseController'].join('/');
|
|
24
29
|
const { absDirPath, absFilePath } = psychicFileAndDirPaths(psychicPath('controllers'), baseControllerName + `.ts`);
|
|
25
30
|
await fs.mkdir(absDirPath, { recursive: true });
|
|
@@ -32,12 +37,18 @@ export default async function generateController({ fullyQualifiedControllerName,
|
|
|
32
37
|
}));
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
|
-
|
|
40
|
+
const namedPart = allControllerNameParts[index];
|
|
41
|
+
if (namedPart)
|
|
42
|
+
controllerNameParts.push(namedPart);
|
|
36
43
|
}
|
|
37
44
|
// Write the controller
|
|
38
45
|
const [ancestorName, ancestorImportStatement] = baseAncestorNameAndImport(controllerNameParts, isAdmin, {
|
|
39
46
|
forBaseController: false,
|
|
40
47
|
});
|
|
48
|
+
if (ancestorName === undefined)
|
|
49
|
+
throw new UnexpectedUndefined();
|
|
50
|
+
if (ancestorImportStatement === undefined)
|
|
51
|
+
throw new UnexpectedUndefined();
|
|
41
52
|
const { relFilePath, absDirPath, absFilePath } = psychicFileAndDirPaths(psychicPath('controllers'), fullyQualifiedControllerName + `.ts`);
|
|
42
53
|
await fs.mkdir(absDirPath, { recursive: true });
|
|
43
54
|
try {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import UnexpectedUndefined from '../../error/UnexpectedUndefined.js';
|
|
3
4
|
import psychicPath from '../../helpers/path/psychicPath.js';
|
|
4
5
|
import PsychicApplication from '../../psychic-application/index.js';
|
|
5
6
|
export default async function addResourceToRoutes(route) {
|
|
@@ -9,6 +10,8 @@ export default async function addResourceToRoutes(route) {
|
|
|
9
10
|
const matchesAndReplacements = addResourceToRoutes_routeToRegexAndReplacements(route);
|
|
10
11
|
for (let index = 0; index < matchesAndReplacements.length; index++) {
|
|
11
12
|
const matchAndReplacement = matchesAndReplacements[index];
|
|
13
|
+
if (matchAndReplacement === undefined)
|
|
14
|
+
throw new UnexpectedUndefined();
|
|
12
15
|
if (matchAndReplacement.regex.test(routes)) {
|
|
13
16
|
routes = routes.replace(matchAndReplacement.regex, matchAndReplacement.replacement +
|
|
14
17
|
closeBrackets(index, indent(matchesAndReplacements.length - index - 1)) +
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
+
import UnexpectedUndefined from '../error/UnexpectedUndefined.js';
|
|
2
3
|
import PsychicApplication from '../psychic-application/index.js';
|
|
3
4
|
export default function openapiJsonPath(openapiName = 'default') {
|
|
4
5
|
const psychicApp = PsychicApplication.getOrFail();
|
|
5
|
-
|
|
6
|
+
const namedOpenapi = psychicApp.openapi[openapiName];
|
|
7
|
+
if (namedOpenapi === undefined)
|
|
8
|
+
throw new UnexpectedUndefined();
|
|
9
|
+
return path.join(psychicApp.apiRoot, namedOpenapi.outputFilename);
|
|
6
10
|
}
|
|
@@ -46,7 +46,10 @@ function applyInterpolations(i18nPathString, str, interpolations) {
|
|
|
46
46
|
return str;
|
|
47
47
|
}
|
|
48
48
|
function _i18n(i18nHash, i18nPath) {
|
|
49
|
-
const
|
|
49
|
+
const index = i18nPath[0];
|
|
50
|
+
if (index === undefined)
|
|
51
|
+
throw new TranslationMissing();
|
|
52
|
+
const translation = i18nHash[index];
|
|
50
53
|
if (translation === undefined)
|
|
51
54
|
throw new TranslationMissing();
|
|
52
55
|
if (typeof translation === 'string')
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { compact } from '@rvoh/dream';
|
|
1
2
|
import * as fs from 'fs/promises';
|
|
2
3
|
import { groupBy } from 'lodash-es';
|
|
4
|
+
import UnexpectedUndefined from '../error/UnexpectedUndefined.js';
|
|
3
5
|
import EnvInternal from '../helpers/EnvInternal.js';
|
|
4
6
|
import openapiJsonPath from '../helpers/openapiJsonPath.js';
|
|
5
7
|
import PsychicApplication from '../psychic-application/index.js';
|
|
@@ -87,22 +89,30 @@ export default class OpenapiAppRenderer {
|
|
|
87
89
|
if (EnvInternal.isDebug)
|
|
88
90
|
console.log(`Processing OpenAPI key ${key} for controller ${controllerName}`);
|
|
89
91
|
const renderer = controller.openapi[key];
|
|
92
|
+
if (renderer === undefined)
|
|
93
|
+
throw new UnexpectedUndefined();
|
|
90
94
|
finalOutput.components.schemas = {
|
|
91
95
|
...finalOutput.components.schemas,
|
|
92
96
|
...renderer.toSchemaObject(openapiName, processedSchemas),
|
|
93
97
|
};
|
|
94
98
|
const endpointPayload = renderer.toPathObject(openapiName, processedSchemas, routes);
|
|
99
|
+
if (endpointPayload === undefined)
|
|
100
|
+
throw new UnexpectedUndefined();
|
|
95
101
|
const path = Object.keys(endpointPayload)[0];
|
|
96
|
-
|
|
102
|
+
if (path === undefined)
|
|
103
|
+
throw new UnexpectedUndefined();
|
|
104
|
+
const endpointPayloadPath = endpointPayload[path];
|
|
105
|
+
if (endpointPayloadPath === undefined)
|
|
106
|
+
throw new UnexpectedUndefined();
|
|
107
|
+
const method = Object.keys(endpointPayloadPath).find(key => HttpMethods.includes(key));
|
|
97
108
|
if (!finalOutput.paths[path]) {
|
|
98
109
|
finalOutput.paths[path] = { parameters: [] };
|
|
99
110
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
...
|
|
105
|
-
...endpointPayload[path].parameters,
|
|
111
|
+
const finalPathObject = finalOutput.paths[path];
|
|
112
|
+
finalPathObject[method] = endpointPayloadPath[method];
|
|
113
|
+
finalPathObject.parameters = this.combineParameters([
|
|
114
|
+
...finalPathObject.parameters,
|
|
115
|
+
...endpointPayloadPath.parameters,
|
|
106
116
|
]);
|
|
107
117
|
}
|
|
108
118
|
}
|
|
@@ -110,24 +120,26 @@ export default class OpenapiAppRenderer {
|
|
|
110
120
|
}
|
|
111
121
|
static combineParameters(parameters) {
|
|
112
122
|
const groupedParams = groupBy(parameters, 'name');
|
|
113
|
-
|
|
114
|
-
const identicalParams = groupedParams[paramName];
|
|
123
|
+
return compact(Object.keys(groupedParams).map(paramName => {
|
|
124
|
+
const identicalParams = groupedParams[paramName] || [];
|
|
115
125
|
return identicalParams.reduce((compositeParam, param) => {
|
|
126
|
+
if (compositeParam === undefined)
|
|
127
|
+
throw new UnexpectedUndefined();
|
|
116
128
|
compositeParam.description ||= param.description;
|
|
117
|
-
if (
|
|
129
|
+
if (param.allowEmptyValue !== undefined)
|
|
118
130
|
compositeParam.allowEmptyValue = param.allowEmptyValue;
|
|
119
|
-
if (
|
|
131
|
+
if (param.allowReserved !== undefined)
|
|
120
132
|
compositeParam.allowReserved = param.allowReserved;
|
|
121
|
-
if (
|
|
133
|
+
if (param.required !== undefined)
|
|
122
134
|
compositeParam.required = param.required;
|
|
123
135
|
return compositeParam;
|
|
124
136
|
}, identicalParams[0]);
|
|
125
|
-
});
|
|
126
|
-
return result;
|
|
137
|
+
}));
|
|
127
138
|
}
|
|
128
139
|
static sortedSchemaPayload(schema) {
|
|
129
140
|
const sortedPaths = Object.keys(schema.paths).sort();
|
|
130
|
-
const
|
|
141
|
+
const schemas = schema.components.schemas || {};
|
|
142
|
+
const sortedSchemaNames = Object.keys(schemas).sort();
|
|
131
143
|
const sortedSchema = { ...schema };
|
|
132
144
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
133
145
|
sortedSchema.paths = sortedPaths.reduce((agg, path) => {
|
|
@@ -138,9 +150,9 @@ export default class OpenapiAppRenderer {
|
|
|
138
150
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
151
|
}, {});
|
|
140
152
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
141
|
-
sortedSchema.components.schemas =
|
|
153
|
+
sortedSchema.components.schemas = sortedSchemaNames.reduce((agg, key) => {
|
|
142
154
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
143
|
-
agg[key] =
|
|
155
|
+
agg[key] = schemas[key];
|
|
144
156
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
145
157
|
return agg;
|
|
146
158
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -172,8 +172,9 @@ export default class OpenapiBodySegmentRenderer {
|
|
|
172
172
|
if (bodySegment.nullable) {
|
|
173
173
|
data.nullable = true;
|
|
174
174
|
}
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
const description = bodySegment.description;
|
|
176
|
+
if (description) {
|
|
177
|
+
data.description = description;
|
|
177
178
|
}
|
|
178
179
|
return data;
|
|
179
180
|
}
|
|
@@ -100,19 +100,22 @@ export default class OpenapiEndpointRenderer {
|
|
|
100
100
|
},
|
|
101
101
|
},
|
|
102
102
|
};
|
|
103
|
+
const outputPath = output[path];
|
|
104
|
+
if (outputPath === undefined)
|
|
105
|
+
throw new Error(`no output for path ${path}`);
|
|
103
106
|
if (this.summary) {
|
|
104
|
-
|
|
107
|
+
outputPath[method].summary = this.summary;
|
|
105
108
|
}
|
|
106
109
|
if (this.description) {
|
|
107
|
-
|
|
110
|
+
outputPath[method].description = this.description;
|
|
108
111
|
}
|
|
109
112
|
if (this.security) {
|
|
110
|
-
|
|
113
|
+
outputPath[method].security = this.security;
|
|
111
114
|
}
|
|
112
115
|
if (requestBody) {
|
|
113
|
-
|
|
116
|
+
outputPath[method]['requestBody'] = requestBody;
|
|
114
117
|
}
|
|
115
|
-
|
|
118
|
+
outputPath[method].responses = responses;
|
|
116
119
|
return output;
|
|
117
120
|
}
|
|
118
121
|
/**
|
|
@@ -260,8 +263,10 @@ export default class OpenapiEndpointRenderer {
|
|
|
260
263
|
? {}
|
|
261
264
|
: this.openapiOpts(openapiName)?.defaults?.headers || {};
|
|
262
265
|
const headers = { ...defaultHeaders, ...(this.headers || []) };
|
|
263
|
-
return (Object.keys(headers).map((headerName) => {
|
|
266
|
+
return (compact(Object.keys(headers).map((headerName) => {
|
|
264
267
|
const header = headers[headerName];
|
|
268
|
+
if (header === undefined)
|
|
269
|
+
return null;
|
|
265
270
|
const data = {
|
|
266
271
|
in: 'header',
|
|
267
272
|
name: headerName,
|
|
@@ -275,7 +280,7 @@ export default class OpenapiEndpointRenderer {
|
|
|
275
280
|
data.schema.format = header.format;
|
|
276
281
|
}
|
|
277
282
|
return data;
|
|
278
|
-
}) || []);
|
|
283
|
+
})) || []);
|
|
279
284
|
}
|
|
280
285
|
/**
|
|
281
286
|
* @internal
|
|
@@ -289,20 +294,20 @@ export default class OpenapiEndpointRenderer {
|
|
|
289
294
|
let output = {
|
|
290
295
|
in: 'query',
|
|
291
296
|
name: queryName,
|
|
292
|
-
description: queryParam
|
|
297
|
+
description: queryParam?.description || queryName,
|
|
293
298
|
allowReserved: true,
|
|
294
299
|
...queryParam,
|
|
295
300
|
schema: {
|
|
296
301
|
type: 'string',
|
|
297
302
|
},
|
|
298
303
|
};
|
|
299
|
-
if (typeof queryParam
|
|
304
|
+
if (typeof queryParam?.allowEmptyValue === 'boolean') {
|
|
300
305
|
output.allowEmptyValue = queryParam.allowEmptyValue;
|
|
301
306
|
}
|
|
302
|
-
if (typeof queryParam
|
|
307
|
+
if (typeof queryParam?.allowReserved === 'boolean') {
|
|
303
308
|
output.allowReserved = queryParam.allowReserved;
|
|
304
309
|
}
|
|
305
|
-
if (queryParam
|
|
310
|
+
if (queryParam?.schema) {
|
|
306
311
|
output = {
|
|
307
312
|
...output,
|
|
308
313
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
@@ -614,10 +619,16 @@ export default class OpenapiEndpointRenderer {
|
|
|
614
619
|
...defaultResponses,
|
|
615
620
|
});
|
|
616
621
|
Object.keys(psychicAndConfigLevelDefaults).forEach(key => {
|
|
617
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
|
618
622
|
if (!responseData[key]) {
|
|
619
|
-
|
|
620
|
-
|
|
623
|
+
const data = psychicAndConfigLevelDefaults[key];
|
|
624
|
+
switch (key) {
|
|
625
|
+
case 'summary':
|
|
626
|
+
case 'description':
|
|
627
|
+
responseData[key] = data;
|
|
628
|
+
break;
|
|
629
|
+
default:
|
|
630
|
+
responseData[key] = data;
|
|
631
|
+
}
|
|
621
632
|
}
|
|
622
633
|
});
|
|
623
634
|
return responseData;
|
|
@@ -674,6 +685,8 @@ export default class OpenapiEndpointRenderer {
|
|
|
674
685
|
*/
|
|
675
686
|
parseSingleEntitySerializerResponseShape() {
|
|
676
687
|
const serializerClass = this.getSerializerClasses()[0];
|
|
688
|
+
if (serializerClass === undefined)
|
|
689
|
+
throw new Error('getSerializerClasses returned no serializer classes');
|
|
677
690
|
const serializerKey = serializerClass.openapiName;
|
|
678
691
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
679
692
|
const serializerObject = this.accountForNullableOption({
|
|
@@ -815,7 +828,9 @@ export default class OpenapiEndpointRenderer {
|
|
|
815
828
|
const modelClass = dreamOrSerializerOrViewModel;
|
|
816
829
|
const modelPrototype = modelClass.prototype;
|
|
817
830
|
const serializerKey = modelPrototype.serializers[this.serializerKey || 'default'];
|
|
818
|
-
|
|
831
|
+
if (serializerKey === undefined)
|
|
832
|
+
throw new Error(`no serializerKey for ${this.serializerKey || 'default'}`);
|
|
833
|
+
return dreamApp.serializers[serializerKey] ?? null;
|
|
819
834
|
}
|
|
820
835
|
}
|
|
821
836
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DreamSerializer, uniq, } from '@rvoh/dream';
|
|
2
2
|
import CannotFlattenMultiplePolymorphicRendersOneAssociations from '../error/openapi/CannotFlattenMultiplePolymorphicRendersOneAssociations.js';
|
|
3
|
+
import UnexpectedUndefined from '../error/UnexpectedUndefined.js';
|
|
3
4
|
import EnvInternal from '../helpers/EnvInternal.js';
|
|
4
5
|
import PsychicApplication from '../psychic-application/index.js';
|
|
5
6
|
import OpenapiBodySegmentRenderer from './body-segment.js';
|
|
@@ -78,6 +79,9 @@ export default class OpenapiSerializerRenderer {
|
|
|
78
79
|
let finalOutput = { ...serializerPayload };
|
|
79
80
|
let flattenedPolymorphicSchemas = [];
|
|
80
81
|
let flattenedPolymorphicAssociation;
|
|
82
|
+
const finalOutputForSerializerKey = finalOutput[serializerKey];
|
|
83
|
+
if (finalOutputForSerializerKey === undefined)
|
|
84
|
+
throw new UnexpectedUndefined();
|
|
81
85
|
associations.forEach(association => {
|
|
82
86
|
const associatedSerializers = DreamSerializer.getAssociatedSerializersForOpenapi(association);
|
|
83
87
|
if (!associatedSerializers)
|
|
@@ -116,10 +120,10 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
116
120
|
...finalOutput,
|
|
117
121
|
[serializerKey]: {
|
|
118
122
|
anyOf: [
|
|
119
|
-
{ ...
|
|
123
|
+
{ ...finalOutputForSerializerKey },
|
|
120
124
|
{
|
|
121
125
|
allOf: [
|
|
122
|
-
{ ...
|
|
126
|
+
{ ...finalOutputForSerializerKey },
|
|
123
127
|
{ anyOf: flattenedPolymorphicSchemas.map(schema => ({ $schema: schema })) },
|
|
124
128
|
],
|
|
125
129
|
},
|
|
@@ -132,7 +136,7 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
132
136
|
...finalOutput,
|
|
133
137
|
[serializerKey]: {
|
|
134
138
|
allOf: [
|
|
135
|
-
{ ...
|
|
139
|
+
{ ...finalOutputForSerializerKey },
|
|
136
140
|
{ anyOf: flattenedPolymorphicSchemas.map(schema => ({ $schema: schema })) },
|
|
137
141
|
],
|
|
138
142
|
},
|
|
@@ -152,22 +156,27 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
152
156
|
*/
|
|
153
157
|
addSingleSerializerAssociationToOutput({ associatedSerializers, finalOutput, serializerKey, association, }) {
|
|
154
158
|
const associatedSerializer = associatedSerializers[0];
|
|
159
|
+
if (associatedSerializer === undefined)
|
|
160
|
+
throw new UnexpectedUndefined();
|
|
155
161
|
const associatedSerializerKey = associatedSerializer.openapiName;
|
|
156
162
|
if (EnvInternal.isDebug)
|
|
157
163
|
PsychicApplication.log(`Processing serializer ${associatedSerializerKey}`);
|
|
158
164
|
let flattenedData;
|
|
165
|
+
const finalOutputForSerializerKey = finalOutput[serializerKey];
|
|
166
|
+
if (finalOutputForSerializerKey === undefined)
|
|
167
|
+
throw new UnexpectedUndefined();
|
|
159
168
|
switch (association.type) {
|
|
160
169
|
case 'RendersMany':
|
|
161
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
162
171
|
;
|
|
163
|
-
|
|
172
|
+
finalOutputForSerializerKey.properties[association.field] = {
|
|
164
173
|
type: 'array',
|
|
165
174
|
items: {
|
|
166
175
|
$ref: `#/components/schemas/${associatedSerializerKey}`,
|
|
167
176
|
},
|
|
168
177
|
};
|
|
169
|
-
|
|
170
|
-
...(
|
|
178
|
+
finalOutputForSerializerKey.required = uniq([
|
|
179
|
+
...(finalOutputForSerializerKey.required || []),
|
|
171
180
|
association.field,
|
|
172
181
|
]);
|
|
173
182
|
break;
|
|
@@ -179,8 +188,8 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
179
188
|
});
|
|
180
189
|
finalOutput[serializerKey].properties = flattenedData;
|
|
181
190
|
if (!association.optional) {
|
|
182
|
-
|
|
183
|
-
...(
|
|
191
|
+
finalOutputForSerializerKey.required = uniq([
|
|
192
|
+
...(finalOutputForSerializerKey.required || []),
|
|
184
193
|
...Object.keys(flattenedData),
|
|
185
194
|
]);
|
|
186
195
|
}
|
|
@@ -191,8 +200,8 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
191
200
|
finalOutput[serializerKey].properties[association.field] = this.accountForNullableOption({
|
|
192
201
|
$ref: `#/components/schemas/${associatedSerializerKey}`,
|
|
193
202
|
}, association.optional);
|
|
194
|
-
|
|
195
|
-
...(
|
|
203
|
+
finalOutputForSerializerKey.required = uniq([
|
|
204
|
+
...(finalOutputForSerializerKey.required || []),
|
|
196
205
|
association.field,
|
|
197
206
|
]);
|
|
198
207
|
}
|
|
@@ -242,6 +251,9 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
242
251
|
* each pointing to its respective target serializer.
|
|
243
252
|
*/
|
|
244
253
|
addMultiSerializerAssociationToOutput({ associatedSerializers, finalOutput, serializerKey, association, flattenedPolymorphicSchemas, flattenedPolymorphicAssociation, }) {
|
|
254
|
+
const finalOutputForSerializerKey = finalOutput[serializerKey];
|
|
255
|
+
if (finalOutputForSerializerKey === undefined)
|
|
256
|
+
throw new UnexpectedUndefined();
|
|
245
257
|
if (association.flatten) {
|
|
246
258
|
if (flattenedPolymorphicSchemas.length)
|
|
247
259
|
throw new CannotFlattenMultiplePolymorphicRendersOneAssociations(this.serializerClass, association.field);
|
|
@@ -269,8 +281,8 @@ Error: ${this.serializerClass.name} missing explicit serializer definition for $
|
|
|
269
281
|
const associatedSerializerKey = associatedSerializer.openapiName;
|
|
270
282
|
if (EnvInternal.isDebug)
|
|
271
283
|
PsychicApplication.log(`Processing serializer ${associatedSerializerKey}`);
|
|
272
|
-
|
|
273
|
-
...(
|
|
284
|
+
finalOutputForSerializerKey.required = uniq([
|
|
285
|
+
...(finalOutputForSerializerKey.required || []),
|
|
274
286
|
association.field,
|
|
275
287
|
]);
|
|
276
288
|
switch (association.type) {
|
|
@@ -2,7 +2,7 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
2
2
|
ancestorName: string;
|
|
3
3
|
ancestorImportStatement: string;
|
|
4
4
|
fullyQualifiedControllerName: string;
|
|
5
|
-
fullyQualifiedModelName?: string;
|
|
6
|
-
actions?: string[];
|
|
7
|
-
omitOpenApi?: boolean;
|
|
5
|
+
fullyQualifiedModelName?: string | undefined;
|
|
6
|
+
actions?: string[] | undefined;
|
|
7
|
+
omitOpenApi?: boolean | undefined;
|
|
8
8
|
}): string;
|
package/package.json
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@rvoh/psychic",
|
|
4
4
|
"description": "Typescript web framework",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.27.0",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
|
-
"repository":
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/rvohealth/psychic.git"
|
|
10
|
+
},
|
|
8
11
|
"license": "MIT",
|
|
9
12
|
"main": "./dist/cjs/src/index.js",
|
|
10
13
|
"module": "./dist/esm/src/index.js",
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
"psycore": "PSYCHIC_CORE_DEVELOPMENT=1 yarn psyts",
|
|
28
31
|
"gpsycore": "PSYCHIC_CORE_DEVELOPMENT=1 tsx ./global-cli/main.ts",
|
|
29
32
|
"build": "echo \"building cjs...\" && rm -rf dist && npx tsc -p ./tsconfig.cjs.build.json && echo \"building esm...\" && npx tsc -p ./tsconfig.esm.build.json",
|
|
30
|
-
"build:
|
|
33
|
+
"build:test-app": "rm -rf dist && echo \"building test app to esm...\" && npx tsc -p ./tsconfig.esm.build.test-app.json && echo \"building test app to cjs...\" && npx tsc -p ./tsconfig.cjs.build.test-app.json",
|
|
31
34
|
"dev": "PSYCHIC_CORE_DEVELOPMENT=1 NODE_ENV=development tsx ./test-app/main.ts",
|
|
32
35
|
"console": "PSYCHIC_CORE_DEVELOPMENT=1 tsx ./test-app/src/conf/repl.ts",
|
|
33
36
|
"uspec": "PSYCHIC_CORE_DEVELOPMENT=1 vitest --config ./spec/unit/vite.config.ts",
|
|
@@ -86,8 +89,8 @@
|
|
|
86
89
|
"typedoc": "^0.26.6",
|
|
87
90
|
"typescript": "^5.5.4",
|
|
88
91
|
"typescript-eslint": "=7.18.0",
|
|
89
|
-
"vitest": "^3.0.
|
|
92
|
+
"vitest": "^3.0.9",
|
|
90
93
|
"winston": "^3.14.2"
|
|
91
94
|
},
|
|
92
95
|
"packageManager": "yarn@4.7.0"
|
|
93
|
-
}
|
|
96
|
+
}
|