@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.
Files changed (27) hide show
  1. package/dist/cjs/src/bin/helpers/enumsAndTheirValues.js +3 -2
  2. package/dist/cjs/src/bin/helpers/enumsFileStr.js +1 -1
  3. package/dist/cjs/src/devtools/helpers/launchDevServer.js +3 -0
  4. package/dist/cjs/src/error/UnexpectedUndefined.js +14 -0
  5. package/dist/cjs/src/generate/controller.js +12 -1
  6. package/dist/cjs/src/generate/helpers/addResourceToRoutes.js +3 -0
  7. package/dist/cjs/src/helpers/openapiJsonPath.js +5 -1
  8. package/dist/cjs/src/i18n/provider.js +4 -1
  9. package/dist/cjs/src/openapi-renderer/app.js +29 -17
  10. package/dist/cjs/src/openapi-renderer/body-segment.js +3 -2
  11. package/dist/cjs/src/openapi-renderer/endpoint.js +30 -15
  12. package/dist/cjs/src/openapi-renderer/serializer.js +25 -13
  13. package/dist/esm/src/bin/helpers/enumsAndTheirValues.js +3 -2
  14. package/dist/esm/src/bin/helpers/enumsFileStr.js +1 -1
  15. package/dist/esm/src/devtools/helpers/launchDevServer.js +3 -0
  16. package/dist/esm/src/error/UnexpectedUndefined.js +11 -0
  17. package/dist/esm/src/generate/controller.js +12 -1
  18. package/dist/esm/src/generate/helpers/addResourceToRoutes.js +3 -0
  19. package/dist/esm/src/helpers/openapiJsonPath.js +5 -1
  20. package/dist/esm/src/i18n/provider.js +4 -1
  21. package/dist/esm/src/openapi-renderer/app.js +29 -17
  22. package/dist/esm/src/openapi-renderer/body-segment.js +3 -2
  23. package/dist/esm/src/openapi-renderer/endpoint.js +30 -15
  24. package/dist/esm/src/openapi-renderer/serializer.js +25 -13
  25. package/dist/types/src/error/UnexpectedUndefined.d.ts +4 -0
  26. package/dist/types/src/generate/helpers/generateControllerContent.d.ts +3 -3
  27. 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
- rowData[row.enumType] ||= [];
14
- rowData[row.enumType].push(row.enumLabel);
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
- controllerNameParts.push(allControllerNameParts[index]);
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
- return path.join(psychicApp.apiRoot, psychicApp.openapi[openapiName].outputFilename);
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 translation = i18nHash[i18nPath[0]];
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
- const method = Object.keys(endpointPayload[path]).find(key => types_js_1.HttpMethods.includes(key));
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 pathObj = finalOutput.paths[path];
103
- const otherPathObj = endpointPayload[path];
104
- pathObj[method] = otherPathObj[method];
105
- pathObj.parameters = this.combineParameters([
106
- ...pathObj.parameters,
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
- const result = Object.keys(groupedParams).map(paramName => {
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 (compositeParam.allowEmptyValue !== undefined)
131
+ if (param.allowEmptyValue !== undefined)
120
132
  compositeParam.allowEmptyValue = param.allowEmptyValue;
121
- if (compositeParam.allowReserved !== undefined)
133
+ if (param.allowReserved !== undefined)
122
134
  compositeParam.allowReserved = param.allowReserved;
123
- if (compositeParam.required !== undefined)
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 sortedSchemas = Object.keys(schema.components.schemas).sort();
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 = sortedSchemas.reduce((agg, key) => {
155
+ sortedSchema.components.schemas = sortedSchemaNames.reduce((agg, key) => {
144
156
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
145
- agg[key] = schema.components.schemas[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
- if (bodySegment.description) {
178
- data.description = bodySegment.description;
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
- output[path][method].summary = this.summary;
110
+ outputPath[method].summary = this.summary;
108
111
  }
109
112
  if (this.description) {
110
- output[path][method].description = this.description;
113
+ outputPath[method].description = this.description;
111
114
  }
112
115
  if (this.security) {
113
- output[path][method].security = this.security;
116
+ outputPath[method].security = this.security;
114
117
  }
115
118
  if (requestBody) {
116
- output[path][method]['requestBody'] = requestBody;
119
+ outputPath[method]['requestBody'] = requestBody;
117
120
  }
118
- output[path][method].responses = responses;
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.description || queryName,
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.allowEmptyValue === 'boolean') {
307
+ if (typeof queryParam?.allowEmptyValue === 'boolean') {
303
308
  output.allowEmptyValue = queryParam.allowEmptyValue;
304
309
  }
305
- if (typeof queryParam.allowReserved === 'boolean') {
310
+ if (typeof queryParam?.allowReserved === 'boolean') {
306
311
  output.allowReserved = queryParam.allowReserved;
307
312
  }
308
- if (queryParam.schema) {
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
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
623
- responseData[key] = psychicAndConfigLevelDefaults[key];
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
- return dreamApp.serializers[serializerKey] || null;
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
- { ...finalOutput[serializerKey] },
125
+ { ...finalOutputForSerializerKey },
122
126
  {
123
127
  allOf: [
124
- { ...finalOutput[serializerKey] },
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
- { ...finalOutput[serializerKey] },
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-unsafe-member-access, @typescript-eslint/no-explicit-any
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
164
173
  ;
165
- finalOutput[serializerKey].properties[association.field] = {
174
+ finalOutputForSerializerKey.properties[association.field] = {
166
175
  type: 'array',
167
176
  items: {
168
177
  $ref: `#/components/schemas/${associatedSerializerKey}`,
169
178
  },
170
179
  };
171
- finalOutput[serializerKey].required = (0, dream_1.uniq)([
172
- ...(finalOutput[serializerKey].required || []),
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
- finalOutput[serializerKey].required = (0, dream_1.uniq)([
185
- ...(finalOutput[serializerKey].required || []),
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
- finalOutput[serializerKey].required = (0, dream_1.uniq)([
197
- ...(finalOutput[serializerKey].required || []),
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
- finalOutput[serializerKey].required = (0, dream_1.uniq)([
275
- ...(finalOutput[serializerKey].required || []),
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
- 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 ')}
@@ -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
- 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) {
@@ -0,0 +1,4 @@
1
+ export default class UnexpectedUndefined extends Error {
2
+ constructor();
3
+ get message(): string;
4
+ }
@@ -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.26.2",
5
+ "version": "0.27.0",
6
6
  "author": "RVOHealth",
7
- "repository": "https://github.com/rvohealth/psychic.git",
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:core": "rm -rf dist && echo \"building core app to esm...\" && npx tsc -p ./tsconfig.esm.build.core.json && echo \"building core app to cjs...\" && npx tsc -p ./tsconfig.cjs.build.core.json",
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.8",
92
+ "vitest": "^3.0.9",
90
93
  "winston": "^3.14.2"
91
94
  },
92
95
  "packageManager": "yarn@4.7.0"
93
- }
96
+ }