@rvoh/psychic 0.26.3 → 0.27.2
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/README.md +6 -4
- package/dist/cjs/src/bin/helpers/enumsAndTheirValues.js +3 -2
- package/dist/cjs/src/bin/helpers/enumsFileStr.js +1 -1
- package/dist/cjs/src/bin/index.js +14 -0
- package/dist/cjs/src/cli/index.js +8 -0
- package/dist/cjs/src/devtools/helpers/launchDevServer.js +3 -0
- package/dist/cjs/src/error/UnexpectedUndefined.js +14 -0
- package/dist/cjs/src/error/psychic-application/init-missing-package-manager.js +20 -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/cjs/src/psychic-application/index.js +47 -2
- package/dist/esm/src/bin/helpers/enumsAndTheirValues.js +3 -2
- package/dist/esm/src/bin/helpers/enumsFileStr.js +1 -1
- package/dist/esm/src/bin/index.js +14 -0
- package/dist/esm/src/cli/index.js +8 -0
- package/dist/esm/src/devtools/helpers/launchDevServer.js +3 -0
- package/dist/esm/src/error/UnexpectedUndefined.js +11 -0
- package/dist/esm/src/error/psychic-application/init-missing-package-manager.js +17 -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/esm/src/psychic-application/index.js +46 -2
- package/dist/types/src/bin/index.d.ts +1 -0
- package/dist/types/src/error/UnexpectedUndefined.d.ts +4 -0
- package/dist/types/src/error/psychic-application/init-missing-package-manager.d.ts +4 -0
- package/dist/types/src/generate/helpers/generateControllerContent.d.ts +3 -3
- package/dist/types/src/psychic-application/index.d.ts +25 -4
- package/package.json +5 -6
- package/dist/cjs/src/helpers/sspawn.js +0 -26
- package/dist/esm/src/helpers/sspawn.js +0 -22
- package/dist/types/src/helpers/sspawn.d.ts +0 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsychicApplicationAllowedPackageManagersEnumValues = void 0;
|
|
3
4
|
const dream_1 = require("@rvoh/dream");
|
|
4
5
|
const init_missing_api_root_js_1 = require("../error/psychic-application/init-missing-api-root.js");
|
|
5
6
|
const init_missing_call_to_load_controllers_js_1 = require("../error/psychic-application/init-missing-call-to-load-controllers.js");
|
|
@@ -9,6 +10,7 @@ const EnvInternal_js_1 = require("../helpers/EnvInternal.js");
|
|
|
9
10
|
const cache_js_1 = require("./cache.js");
|
|
10
11
|
const importControllers_js_1 = require("./helpers/import/importControllers.js");
|
|
11
12
|
const lookupClassByGlobalName_js_1 = require("./helpers/lookupClassByGlobalName.js");
|
|
13
|
+
const init_missing_package_manager_js_1 = require("../error/psychic-application/init-missing-package-manager.js");
|
|
12
14
|
class PsychicApplication {
|
|
13
15
|
static async init(cb, dreamCb, opts = {}) {
|
|
14
16
|
let psychicApp;
|
|
@@ -21,11 +23,16 @@ class PsychicApplication {
|
|
|
21
23
|
throw new init_missing_api_root_js_1.default();
|
|
22
24
|
if (!psychicApp.routesCb)
|
|
23
25
|
throw new init_missing_routes_callback_js_1.default();
|
|
26
|
+
if (!exports.PsychicApplicationAllowedPackageManagersEnumValues.includes(psychicApp.packageManager))
|
|
27
|
+
throw new init_missing_package_manager_js_1.default();
|
|
24
28
|
if (psychicApp.encryption?.cookies?.current)
|
|
25
29
|
this.checkKey('cookies', psychicApp.encryption.cookies.current.key, psychicApp.encryption.cookies.current.algorithm);
|
|
26
30
|
await psychicApp.inflections?.();
|
|
27
31
|
dreamApp.set('projectRoot', psychicApp.apiRoot);
|
|
28
32
|
dreamApp.set('logger', psychicApp.logger);
|
|
33
|
+
for (const plugin of psychicApp.plugins) {
|
|
34
|
+
await plugin(psychicApp);
|
|
35
|
+
}
|
|
29
36
|
(0, cache_js_1.cachePsychicApplication)(psychicApp);
|
|
30
37
|
});
|
|
31
38
|
return psychicApp;
|
|
@@ -34,6 +41,29 @@ class PsychicApplication {
|
|
|
34
41
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
35
42
|
return (0, lookupClassByGlobalName_js_1.default)(name);
|
|
36
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* @internal
|
|
46
|
+
*
|
|
47
|
+
* used to provide the correct package manager syntax for running a script
|
|
48
|
+
* inside of the package.json "scripts" section.
|
|
49
|
+
*/
|
|
50
|
+
packageManagerRunCmd(cmd) {
|
|
51
|
+
switch (this.packageManager) {
|
|
52
|
+
case 'npm':
|
|
53
|
+
return `npm run ${cmd}`;
|
|
54
|
+
default:
|
|
55
|
+
return `${this.packageManager} ${cmd}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* @internal
|
|
60
|
+
*
|
|
61
|
+
* adds the necessary package manager prefix to the psy command provided
|
|
62
|
+
* i.e. `psyCmd('sync')`
|
|
63
|
+
*/
|
|
64
|
+
psyCmd(cmd) {
|
|
65
|
+
return this.packageManagerRunCmd(`psy ${cmd}`);
|
|
66
|
+
}
|
|
37
67
|
static checkKey(encryptionIdentifier, key, algorithm) {
|
|
38
68
|
if (!dream_1.Encrypt.validateKey(key, algorithm))
|
|
39
69
|
console.warn(`
|
|
@@ -108,14 +138,18 @@ Try setting it to something valid, like:
|
|
|
108
138
|
get logger() {
|
|
109
139
|
return this._logger;
|
|
110
140
|
}
|
|
111
|
-
_sslCredentials;
|
|
141
|
+
_sslCredentials = undefined;
|
|
112
142
|
get sslCredentials() {
|
|
113
143
|
return this._sslCredentials;
|
|
114
144
|
}
|
|
115
|
-
_saltRounds;
|
|
145
|
+
_saltRounds = undefined;
|
|
116
146
|
get saltRounds() {
|
|
117
147
|
return this._saltRounds;
|
|
118
148
|
}
|
|
149
|
+
_packageManager;
|
|
150
|
+
get packageManager() {
|
|
151
|
+
return this._packageManager;
|
|
152
|
+
}
|
|
119
153
|
_routesCb;
|
|
120
154
|
get routesCb() {
|
|
121
155
|
return this._routesCb;
|
|
@@ -196,6 +230,10 @@ Try setting it to something valid, like:
|
|
|
196
230
|
get controllers() {
|
|
197
231
|
return (0, importControllers_js_1.getControllersOrFail)();
|
|
198
232
|
}
|
|
233
|
+
_plugins = [];
|
|
234
|
+
get plugins() {
|
|
235
|
+
return this._plugins;
|
|
236
|
+
}
|
|
199
237
|
async load(resourceType, resourcePath,
|
|
200
238
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
201
239
|
importCb) {
|
|
@@ -226,6 +264,9 @@ Try setting it to something valid, like:
|
|
|
226
264
|
await this.inflections?.();
|
|
227
265
|
this.booted = true;
|
|
228
266
|
}
|
|
267
|
+
plugin(cb) {
|
|
268
|
+
this._plugins.push(cb);
|
|
269
|
+
}
|
|
229
270
|
on(hookEventType, cb) {
|
|
230
271
|
switch (hookEventType) {
|
|
231
272
|
case 'server:error':
|
|
@@ -306,6 +347,9 @@ Try setting it to something valid, like:
|
|
|
306
347
|
case 'port':
|
|
307
348
|
this._port = value;
|
|
308
349
|
break;
|
|
350
|
+
case 'packageManager':
|
|
351
|
+
this._packageManager = value;
|
|
352
|
+
break;
|
|
309
353
|
case 'saltRounds':
|
|
310
354
|
this._saltRounds = value;
|
|
311
355
|
break;
|
|
@@ -344,3 +388,4 @@ Try setting it to something valid, like:
|
|
|
344
388
|
}
|
|
345
389
|
}
|
|
346
390
|
exports.default = PsychicApplication;
|
|
391
|
+
exports.PsychicApplicationAllowedPackageManagersEnumValues = ['yarn', 'npm', 'pnpm'];
|
|
@@ -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 ')}
|
|
@@ -25,6 +25,20 @@ export default class PsychicBin {
|
|
|
25
25
|
if (!bypassDreamSync)
|
|
26
26
|
await DreamBin.sync(() => { });
|
|
27
27
|
await PsychicBin.syncTypes();
|
|
28
|
+
const psychicApp = PsychicApplication.getOrFail();
|
|
29
|
+
DreamCLI.logger.logStartProgress('running post-sync operations...');
|
|
30
|
+
// call post-sync command in a separate process, so that newly-generated
|
|
31
|
+
// types can be reloaded and brought into all classes.
|
|
32
|
+
await DreamCLI.spawn(psychicApp.psyCmd('post-sync'), {
|
|
33
|
+
onStdout: message => {
|
|
34
|
+
DreamCLI.logger.logContinueProgress(`[post-sync]` + ' ' + message, {
|
|
35
|
+
logPrefixColor: 'cyan',
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
DreamCLI.logger.logEndProgress();
|
|
40
|
+
}
|
|
41
|
+
static async postSync() {
|
|
28
42
|
const psychicApp = PsychicApplication.getOrFail();
|
|
29
43
|
await PsychicBin.syncOpenapiJson();
|
|
30
44
|
if (psychicApp.openapi?.syncEnumsToClient) {
|
|
@@ -48,6 +48,14 @@ export default class PsychicCLI {
|
|
|
48
48
|
await PsychicBin.sync();
|
|
49
49
|
process.exit();
|
|
50
50
|
});
|
|
51
|
+
program
|
|
52
|
+
.command('post-sync')
|
|
53
|
+
.description('an internal command that runs as the second stage of the `sync` command, since after types are rebuit, the application needs to be reloaded before autogenerating certain files, since those files will need to leverage the updated types')
|
|
54
|
+
.action(async () => {
|
|
55
|
+
await initializePsychicApplication();
|
|
56
|
+
await PsychicBin.postSync();
|
|
57
|
+
process.exit();
|
|
58
|
+
});
|
|
51
59
|
program
|
|
52
60
|
.command('sync:routes')
|
|
53
61
|
.description('reads the routes generated by your app and generates a cache file, which is then used to give autocomplete support to the route helper, amoongst other things.')
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default class PsychicApplicationInitMissingPackageManager extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
}
|
|
5
|
+
get message() {
|
|
6
|
+
return `
|
|
7
|
+
must set packageManager when initializing a new PsychicApplication.
|
|
8
|
+
|
|
9
|
+
within conf/app.ts, you must have a call to "#set('packageManager', '<YOUR_CHOSEN_PACKAGE_MANAGER>')", i.e.
|
|
10
|
+
|
|
11
|
+
// conf/app.ts
|
|
12
|
+
export default async (app: PsychicApplication) => {
|
|
13
|
+
await app.set('packageManager', 'yarn')
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -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) {
|