@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.
Files changed (42) hide show
  1. package/README.md +6 -4
  2. package/dist/cjs/src/bin/helpers/enumsAndTheirValues.js +3 -2
  3. package/dist/cjs/src/bin/helpers/enumsFileStr.js +1 -1
  4. package/dist/cjs/src/bin/index.js +14 -0
  5. package/dist/cjs/src/cli/index.js +8 -0
  6. package/dist/cjs/src/devtools/helpers/launchDevServer.js +3 -0
  7. package/dist/cjs/src/error/UnexpectedUndefined.js +14 -0
  8. package/dist/cjs/src/error/psychic-application/init-missing-package-manager.js +20 -0
  9. package/dist/cjs/src/generate/controller.js +12 -1
  10. package/dist/cjs/src/generate/helpers/addResourceToRoutes.js +3 -0
  11. package/dist/cjs/src/helpers/openapiJsonPath.js +5 -1
  12. package/dist/cjs/src/i18n/provider.js +4 -1
  13. package/dist/cjs/src/openapi-renderer/app.js +29 -17
  14. package/dist/cjs/src/openapi-renderer/body-segment.js +3 -2
  15. package/dist/cjs/src/openapi-renderer/endpoint.js +30 -15
  16. package/dist/cjs/src/openapi-renderer/serializer.js +25 -13
  17. package/dist/cjs/src/psychic-application/index.js +47 -2
  18. package/dist/esm/src/bin/helpers/enumsAndTheirValues.js +3 -2
  19. package/dist/esm/src/bin/helpers/enumsFileStr.js +1 -1
  20. package/dist/esm/src/bin/index.js +14 -0
  21. package/dist/esm/src/cli/index.js +8 -0
  22. package/dist/esm/src/devtools/helpers/launchDevServer.js +3 -0
  23. package/dist/esm/src/error/UnexpectedUndefined.js +11 -0
  24. package/dist/esm/src/error/psychic-application/init-missing-package-manager.js +17 -0
  25. package/dist/esm/src/generate/controller.js +12 -1
  26. package/dist/esm/src/generate/helpers/addResourceToRoutes.js +3 -0
  27. package/dist/esm/src/helpers/openapiJsonPath.js +5 -1
  28. package/dist/esm/src/i18n/provider.js +4 -1
  29. package/dist/esm/src/openapi-renderer/app.js +29 -17
  30. package/dist/esm/src/openapi-renderer/body-segment.js +3 -2
  31. package/dist/esm/src/openapi-renderer/endpoint.js +30 -15
  32. package/dist/esm/src/openapi-renderer/serializer.js +25 -13
  33. package/dist/esm/src/psychic-application/index.js +46 -2
  34. package/dist/types/src/bin/index.d.ts +1 -0
  35. package/dist/types/src/error/UnexpectedUndefined.d.ts +4 -0
  36. package/dist/types/src/error/psychic-application/init-missing-package-manager.d.ts +4 -0
  37. package/dist/types/src/generate/helpers/generateControllerContent.d.ts +3 -3
  38. package/dist/types/src/psychic-application/index.d.ts +25 -4
  39. package/package.json +5 -6
  40. package/dist/cjs/src/helpers/sspawn.js +0 -26
  41. package/dist/esm/src/helpers/sspawn.js +0 -22
  42. 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
- rowData[row.enumType] ||= [];
11
- rowData[row.enumType].push(row.enumLabel);
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
- controllerNameParts.push(allControllerNameParts[index]);
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
- return path.join(psychicApp.apiRoot, psychicApp.openapi[openapiName].outputFilename);
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 translation = i18nHash[i18nPath[0]];
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
- const method = Object.keys(endpointPayload[path]).find(key => HttpMethods.includes(key));
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 pathObj = finalOutput.paths[path];
101
- const otherPathObj = endpointPayload[path];
102
- pathObj[method] = otherPathObj[method];
103
- pathObj.parameters = this.combineParameters([
104
- ...pathObj.parameters,
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
- const result = Object.keys(groupedParams).map(paramName => {
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 (compositeParam.allowEmptyValue !== undefined)
129
+ if (param.allowEmptyValue !== undefined)
118
130
  compositeParam.allowEmptyValue = param.allowEmptyValue;
119
- if (compositeParam.allowReserved !== undefined)
131
+ if (param.allowReserved !== undefined)
120
132
  compositeParam.allowReserved = param.allowReserved;
121
- if (compositeParam.required !== undefined)
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 sortedSchemas = Object.keys(schema.components.schemas).sort();
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 = sortedSchemas.reduce((agg, key) => {
153
+ sortedSchema.components.schemas = sortedSchemaNames.reduce((agg, key) => {
142
154
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
143
- agg[key] = schema.components.schemas[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
- if (bodySegment.description) {
176
- data.description = bodySegment.description;
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
- output[path][method].summary = this.summary;
107
+ outputPath[method].summary = this.summary;
105
108
  }
106
109
  if (this.description) {
107
- output[path][method].description = this.description;
110
+ outputPath[method].description = this.description;
108
111
  }
109
112
  if (this.security) {
110
- output[path][method].security = this.security;
113
+ outputPath[method].security = this.security;
111
114
  }
112
115
  if (requestBody) {
113
- output[path][method]['requestBody'] = requestBody;
116
+ outputPath[method]['requestBody'] = requestBody;
114
117
  }
115
- output[path][method].responses = responses;
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.description || queryName,
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.allowEmptyValue === 'boolean') {
304
+ if (typeof queryParam?.allowEmptyValue === 'boolean') {
300
305
  output.allowEmptyValue = queryParam.allowEmptyValue;
301
306
  }
302
- if (typeof queryParam.allowReserved === 'boolean') {
307
+ if (typeof queryParam?.allowReserved === 'boolean') {
303
308
  output.allowReserved = queryParam.allowReserved;
304
309
  }
305
- if (queryParam.schema) {
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
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
620
- responseData[key] = psychicAndConfigLevelDefaults[key];
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
- return dreamApp.serializers[serializerKey] || null;
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
- { ...finalOutput[serializerKey] },
123
+ { ...finalOutputForSerializerKey },
120
124
  {
121
125
  allOf: [
122
- { ...finalOutput[serializerKey] },
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
- { ...finalOutput[serializerKey] },
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-unsafe-member-access, @typescript-eslint/no-explicit-any
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
162
171
  ;
163
- finalOutput[serializerKey].properties[association.field] = {
172
+ finalOutputForSerializerKey.properties[association.field] = {
164
173
  type: 'array',
165
174
  items: {
166
175
  $ref: `#/components/schemas/${associatedSerializerKey}`,
167
176
  },
168
177
  };
169
- finalOutput[serializerKey].required = uniq([
170
- ...(finalOutput[serializerKey].required || []),
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
- finalOutput[serializerKey].required = uniq([
183
- ...(finalOutput[serializerKey].required || []),
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
- finalOutput[serializerKey].required = uniq([
195
- ...(finalOutput[serializerKey].required || []),
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
- finalOutput[serializerKey].required = uniq([
273
- ...(finalOutput[serializerKey].required || []),
284
+ finalOutputForSerializerKey.required = uniq([
285
+ ...(finalOutputForSerializerKey.required || []),
274
286
  association.field,
275
287
  ]);
276
288
  switch (association.type) {