@rvoh/psychic 0.36.0-beta.1 → 0.37.0-beta.3

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 (24) hide show
  1. package/dist/cjs/src/controller/index.js +18 -12
  2. package/dist/cjs/src/openapi-renderer/app.js +16 -15
  3. package/dist/cjs/src/openapi-renderer/body-segment.js +3 -6
  4. package/dist/cjs/src/openapi-renderer/endpoint.js +56 -103
  5. package/dist/cjs/src/openapi-renderer/helpers/{suppressResponseEnums.js → suppressResponseEnumsConfig.js} +2 -2
  6. package/dist/cjs/src/psychic-app/index.js +0 -1
  7. package/dist/cjs/src/server/index.js +1 -35
  8. package/dist/esm/src/controller/index.js +19 -13
  9. package/dist/esm/src/openapi-renderer/app.js +17 -16
  10. package/dist/esm/src/openapi-renderer/body-segment.js +3 -6
  11. package/dist/esm/src/openapi-renderer/endpoint.js +57 -104
  12. package/dist/esm/src/openapi-renderer/helpers/{suppressResponseEnums.js → suppressResponseEnumsConfig.js} +1 -1
  13. package/dist/esm/src/psychic-app/index.js +0 -1
  14. package/dist/esm/src/server/index.js +1 -35
  15. package/dist/types/src/controller/index.d.ts +3 -1
  16. package/dist/types/src/openapi-renderer/body-segment.d.ts +4 -7
  17. package/dist/types/src/openapi-renderer/endpoint.d.ts +17 -21
  18. package/dist/types/src/openapi-renderer/helpers/{schemaDelimiter.d.ts → suppressResponseEnumsConfig.d.ts} +1 -1
  19. package/dist/types/src/psychic-app/index.d.ts +0 -53
  20. package/dist/types/src/server/index.d.ts +0 -1
  21. package/package.json +2 -5
  22. package/dist/cjs/src/openapi-renderer/helpers/schemaDelimiter.js +0 -13
  23. package/dist/esm/src/openapi-renderer/helpers/schemaDelimiter.js +0 -7
  24. package/dist/types/src/openapi-renderer/helpers/suppressResponseEnums.d.ts +0 -4
@@ -1,4 +1,4 @@
1
- import { DreamApp, GlobalNameNotSet, isDreamSerializer, SerializerRenderer, } from '@rvoh/dream';
1
+ import { DreamApp, DreamSerializerBuilder, GlobalNameNotSet, isDreamSerializer, ObjectSerializerBuilder, } from '@rvoh/dream';
2
2
  import ParamValidationError from '../error/controller/ParamValidationError.js';
3
3
  import HttpStatusBadGateway from '../error/http/BadGateway.js';
4
4
  import HttpStatusBadRequest from '../error/http/BadRequest.js';
@@ -187,12 +187,20 @@ export default class PsychicController {
187
187
  session;
188
188
  config;
189
189
  action;
190
+ renderOpts;
190
191
  constructor(req, res, { config, action, }) {
191
192
  this.req = req;
192
193
  this.res = res;
193
194
  this.config = config;
194
195
  this.session = new Session(req, res);
195
196
  this.action = action;
197
+ // TODO: read casing from Dream app config
198
+ this.renderOpts = {
199
+ casing: 'camel',
200
+ };
201
+ }
202
+ get headers() {
203
+ return this.req.headers;
196
204
  }
197
205
  get params() {
198
206
  const params = {
@@ -256,24 +264,25 @@ export default class PsychicController {
256
264
  return data;
257
265
  const dreamApp = DreamApp.getOrFail();
258
266
  const psychicControllerClass = this.constructor;
267
+ // if we already have a serializer, let's just render it
268
+ if (data instanceof DreamSerializerBuilder || data instanceof ObjectSerializerBuilder) {
269
+ return data.render(this.defaultSerializerPassthrough, this.renderOpts);
270
+ }
259
271
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
260
272
  const lookup = controllerSerializerIndex.lookupModel(this.constructor, data.constructor);
261
273
  if (lookup?.length) {
262
274
  const serializer = lookup?.[1];
263
275
  if (isDreamSerializer(serializer)) {
264
- return new SerializerRenderer(
265
276
  // passthrough data going into the serializer is the argument that gets
266
277
  // used in the custom attribute callback function
267
- serializer(data, this.defaultSerializerPassthrough),
268
- // passthrough data must be passed both into the serializer and the SerializerRenderer
278
+ return serializer(data, this.defaultSerializerPassthrough).render(
279
+ // passthrough data must be passed both into the serializer and render
269
280
  // because, if the serializer does accept passthrough data, then passing it in is how
270
281
  // it gets into the serializer, but if it does not accept passthrough data, and therefore
271
282
  // does not pass it into the call to DreamSerializer/ObjectSerializer,
272
283
  // then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
273
284
  // handles passing its passthrough data into those
274
- this.defaultSerializerPassthrough, {
275
- casing: 'camel',
276
- }).render();
285
+ this.defaultSerializerPassthrough, this.renderOpts);
277
286
  }
278
287
  }
279
288
  else {
@@ -285,19 +294,16 @@ export default class PsychicController {
285
294
  if (serializerKey && Object.prototype.hasOwnProperty.call(dreamApp.serializers, serializerKey)) {
286
295
  const serializer = dreamApp.serializers[serializerKey];
287
296
  if (serializer && isDreamSerializer(serializer)) {
288
- return new SerializerRenderer(
289
297
  // passthrough data going into the serializer is the argument that gets
290
298
  // used in the custom attribute callback function
291
- serializer(data, this.defaultSerializerPassthrough),
292
- // passthrough data must be passed both into the serializer and the SerializerRenderer
299
+ return serializer(data, this.defaultSerializerPassthrough).render(
300
+ // passthrough data must be passed both into the serializer and render
293
301
  // because, if the serializer does accept passthrough data, then passing it in is how
294
302
  // it gets into the serializer, but if it does not accept passthrough data, and therefore
295
303
  // does not pass it into the call to DreamSerializer/ObjectSerializer,
296
304
  // then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
297
305
  // handles passing its passthrough data into those
298
- this.defaultSerializerPassthrough, {
299
- casing: 'camel',
300
- }).render();
306
+ this.defaultSerializerPassthrough, this.renderOpts);
301
307
  }
302
308
  else {
303
309
  throw new Error(`
@@ -1,5 +1,4 @@
1
- import { compact, sortObjectByKey } from '@rvoh/dream';
2
- import { groupBy } from 'lodash-es';
1
+ import { compact, groupBy, sortObjectByKey } from '@rvoh/dream';
3
2
  import * as fs from 'node:fs/promises';
4
3
  import { debuglog } from 'node:util';
5
4
  import UnexpectedUndefined from '../error/UnexpectedUndefined.js';
@@ -8,8 +7,7 @@ import PsychicApp from '../psychic-app/index.js';
8
7
  import { HttpMethods } from '../router/types.js';
9
8
  import PsychicServer from '../server/index.js';
10
9
  import { DEFAULT_OPENAPI_COMPONENT_RESPONSES, DEFAULT_OPENAPI_COMPONENT_SCHEMAS } from './defaults.js';
11
- import schemaDelimiter from './helpers/schemaDelimiter.js';
12
- import suppressResponseEnums from './helpers/suppressResponseEnums.js';
10
+ import suppressResponseEnumsConfig from './helpers/suppressResponseEnumsConfig.js';
13
11
  const debugEnabled = debuglog('psychic').enabled;
14
12
  export default class OpenapiAppRenderer {
15
13
  /**
@@ -48,13 +46,12 @@ export default class OpenapiAppRenderer {
48
46
  return output;
49
47
  }
50
48
  static _toObject(routes, openapiName) {
51
- const opts = {
52
- openapiName,
49
+ const renderOpts = {
53
50
  casing: 'camel',
54
- schemaDelimiter: schemaDelimiter(openapiName),
55
- suppressResponseEnums: suppressResponseEnums(openapiName),
51
+ suppressResponseEnums: suppressResponseEnumsConfig(openapiName),
56
52
  };
57
- let processedSchemas = {};
53
+ const alreadyExtractedDescendantSerializers = {};
54
+ const renderedSchemasOpenapi = {};
58
55
  const psychicApp = PsychicApp.getOrFail();
59
56
  const controllers = psychicApp.controllers;
60
57
  const openapiConfig = psychicApp.openapi?.[openapiName];
@@ -99,7 +96,10 @@ export default class OpenapiAppRenderer {
99
96
  const renderer = controller.openapi[key];
100
97
  if (renderer === undefined)
101
98
  throw new UnexpectedUndefined();
102
- const endpointPayloadAndReferencedSerializers = renderer.toPathObject(routes, opts);
99
+ const endpointPayloadAndReferencedSerializers = renderer.toPathObject(routes, {
100
+ openapiName,
101
+ renderOpts,
102
+ });
103
103
  const serializersAppearingInHandWrittenOpenapi = endpointPayloadAndReferencedSerializers.referencedSerializers;
104
104
  const endpointPayload = endpointPayloadAndReferencedSerializers.openapi;
105
105
  if (endpointPayload === undefined)
@@ -120,15 +120,16 @@ export default class OpenapiAppRenderer {
120
120
  ...finalPathObject.parameters,
121
121
  ...endpointPayloadPath.parameters,
122
122
  ]);
123
- const schemaRenderingResults = renderer.toSchemaObject({
124
- ...opts,
125
- processedSchemas,
123
+ renderer.toSchemaObject({
124
+ openapiName,
125
+ renderOpts,
126
+ renderedSchemasOpenapi,
127
+ alreadyExtractedDescendantSerializers,
126
128
  serializersAppearingInHandWrittenOpenapi,
127
129
  });
128
- processedSchemas = { ...processedSchemas, ...schemaRenderingResults.processedSchemas };
129
130
  finalOutput.components.schemas = {
130
131
  ...finalOutput.components.schemas,
131
- ...schemaRenderingResults.renderedSchemas,
132
+ ...renderedSchemasOpenapi,
132
133
  };
133
134
  }
134
135
  }
@@ -140,7 +141,7 @@ export default class OpenapiAppRenderer {
140
141
  return finalOutput;
141
142
  }
142
143
  static combineParameters(parameters) {
143
- const groupedParams = groupBy(parameters, 'name');
144
+ const groupedParams = groupBy(parameters, obj => obj.name);
144
145
  return compact(Object.keys(groupedParams).map(paramName => {
145
146
  const identicalParams = groupedParams[paramName] || [];
146
147
  return identicalParams.reduce((compositeParam, param) => {
@@ -7,7 +7,6 @@ import primitiveOpenapiStatementToOpenapi from './helpers/primitiveOpenapiStatem
7
7
  import schemaToRef from './helpers/schemaToRef.js';
8
8
  export default class OpenapiBodySegmentRenderer {
9
9
  bodySegment;
10
- schemaDelimiter;
11
10
  casing;
12
11
  suppressResponseEnums;
13
12
  target;
@@ -18,12 +17,11 @@ export default class OpenapiBodySegmentRenderer {
18
17
  * Used to recursively parse nested object structures
19
18
  * within nested openapi objects
20
19
  */
21
- constructor(bodySegment, { openapiName, schemaDelimiter, casing, suppressResponseEnums, target }) {
20
+ constructor(bodySegment, { openapiName, renderOpts, target }) {
22
21
  this.openapiName = openapiName;
23
22
  this.bodySegment = bodySegment;
24
- this.schemaDelimiter = schemaDelimiter;
25
- this.casing = casing;
26
- this.suppressResponseEnums = suppressResponseEnums;
23
+ this.casing = renderOpts.casing;
24
+ this.suppressResponseEnums = renderOpts.suppressResponseEnums;
27
25
  this.target = target;
28
26
  }
29
27
  /**
@@ -385,7 +383,6 @@ The following values will be allowed:
385
383
  throw new NonSerializerSuppliedToSerializerBodySegment(this.bodySegment, serializer);
386
384
  const serializerRef = new SerializerOpenapiRenderer(serializer, {
387
385
  casing: this.casing,
388
- schemaDelimiter: this.schemaDelimiter,
389
386
  suppressResponseEnums: this.suppressResponseEnums,
390
387
  }).serializerRef;
391
388
  if (serializerRefBodySegment.many) {
@@ -1,5 +1,4 @@
1
- import { SerializerOpenapiRenderer, compact, inferSerializersFromDreamClassOrViewModelClass, isDreamSerializer, sortBy, } from '@rvoh/dream';
2
- import { cloneDeep } from 'lodash-es';
1
+ import { SerializerOpenapiRenderer, cloneDeepSafe, compact, inferSerializersFromDreamClassOrViewModelClass, isDreamSerializer, sortBy, } from '@rvoh/dream';
3
2
  import OpenApiFailedToLookupSerializerForEndpoint from '../error/openapi/FailedToLookupSerializerForEndpoint.js';
4
3
  import NonSerializerDerivedInOpenapiEndpointRenderer from '../error/openapi/NonSerializerDerivedInOpenapiEndpointRenderer.js';
5
4
  import NonSerializerDerivedInToSchemaObjects from '../error/openapi/NonSerializerDerivedInToSchemaObjects.js';
@@ -15,10 +14,6 @@ export default class OpenapiEndpointRenderer {
15
14
  dreamsOrSerializers;
16
15
  controllerClass;
17
16
  action;
18
- openapiName;
19
- casing;
20
- schemaDelimiter;
21
- suppressResponseEnums;
22
17
  many;
23
18
  paginate;
24
19
  responses;
@@ -90,18 +85,18 @@ export default class OpenapiEndpointRenderer {
90
85
  * `#toPathObject` specifically builds the `paths` portion of the
91
86
  * final openapi.json output
92
87
  */
93
- toPathObject(routes, { openapiName, casing, schemaDelimiter, suppressResponseEnums, }) {
94
- this.openapiName = openapiName;
95
- this.casing = casing;
96
- this.schemaDelimiter = schemaDelimiter;
97
- this.suppressResponseEnums = suppressResponseEnums;
88
+ toPathObject(routes, { openapiName, renderOpts }) {
98
89
  const path = this.computedPath(routes);
99
90
  const method = this.computedMethod(routes);
100
- const requestBody = this.computedRequestBody(routes);
101
- const responsesAndReferencedSerializers = this.parseResponses();
91
+ const requestBody = this.computedRequestBody(routes, { openapiName, renderOpts });
92
+ const responsesAndReferencedSerializers = this.parseResponses({ openapiName, renderOpts });
102
93
  const output = {
103
94
  [path]: {
104
- parameters: [...this.headersArray(), ...this.pathParamsArray(routes), ...this.queryArray()],
95
+ parameters: [
96
+ ...this.headersArray({ openapiName }),
97
+ ...this.pathParamsArray(routes),
98
+ ...this.queryArray({ openapiName, renderOpts }),
99
+ ],
105
100
  [method]: {
106
101
  tags: this.tags || [],
107
102
  },
@@ -143,18 +138,13 @@ export default class OpenapiEndpointRenderer {
143
138
  * final openapi.json output, adding any relevant entries that were uncovered
144
139
  * while parsing the responses and provided callback function.
145
140
  */
146
- toSchemaObject({ openapiName, casing, schemaDelimiter, suppressResponseEnums, processedSchemas, serializersAppearingInHandWrittenOpenapi, }) {
147
- this.openapiName = openapiName;
148
- this.casing = casing;
149
- this.schemaDelimiter = schemaDelimiter;
150
- this.suppressResponseEnums = suppressResponseEnums;
141
+ toSchemaObject({ openapiName, renderOpts, alreadyExtractedDescendantSerializers, renderedSchemasOpenapi, serializersAppearingInHandWrittenOpenapi, }) {
151
142
  const serializers = this.getSerializerClasses() ?? [];
152
- return serializersToSchemaObjects(this.controllerClass, this.action, [...serializers, ...serializersAppearingInHandWrittenOpenapi], {
153
- openapiName: this.openapiName,
154
- casing: this.casing,
155
- schemaDelimiter: this.schemaDelimiter,
156
- suppressResponseEnums: this.suppressResponseEnums,
157
- processedSchemas,
143
+ serializersToSchemaObjects(this.controllerClass, this.action, [...serializers, ...serializersAppearingInHandWrittenOpenapi], {
144
+ openapiName,
145
+ renderOpts,
146
+ alreadyExtractedDescendantSerializers,
147
+ renderedSchemasOpenapi,
158
148
  });
159
149
  }
160
150
  /**
@@ -258,10 +248,8 @@ export default class OpenapiEndpointRenderer {
258
248
  * Generates the header portion of the openapi payload's
259
249
  * "parameters" field for a single endpoint.
260
250
  */
261
- headersArray() {
262
- const defaultHeaders = this.omitDefaultHeaders
263
- ? {}
264
- : openapiOpts(this.openapiName)?.defaults?.headers || {};
251
+ headersArray({ openapiName }) {
252
+ const defaultHeaders = this.omitDefaultHeaders ? {} : openapiOpts(openapiName)?.defaults?.headers || {};
265
253
  const headers = { ...defaultHeaders, ...(this.headers || []) };
266
254
  return (compact(Object.keys(headers).map((headerName) => {
267
255
  const header = headers[headerName];
@@ -288,7 +276,7 @@ export default class OpenapiEndpointRenderer {
288
276
  * Generates the header portion of the openapi payload's
289
277
  * "parameters" field for a single endpoint.
290
278
  */
291
- queryArray() {
279
+ queryArray({ openapiName, renderOpts, }) {
292
280
  const queryParams = Object.keys(this.query || {}).map((queryName) => {
293
281
  const queryParam = this.query[queryName];
294
282
  let output = {
@@ -312,10 +300,8 @@ export default class OpenapiEndpointRenderer {
312
300
  ...output,
313
301
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
314
302
  schema: new OpenapiBodySegmentRenderer(queryParam.schema, {
315
- openapiName: this.openapiName,
316
- schemaDelimiter: this.schemaDelimiter,
317
- casing: this.casing,
318
- suppressResponseEnums: this.suppressResponseEnums,
303
+ openapiName,
304
+ renderOpts,
319
305
  target: 'request',
320
306
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
321
307
  }).render().openapi,
@@ -343,7 +329,7 @@ export default class OpenapiEndpointRenderer {
343
329
  *
344
330
  * Generates the requestBody portion of the endpoint
345
331
  */
346
- computedRequestBody(routes) {
332
+ computedRequestBody(routes, { openapiName, renderOpts, }) {
347
333
  const method = this.computedMethod(routes);
348
334
  if (this.requestBody === null)
349
335
  return this.defaultRequestBody();
@@ -351,13 +337,11 @@ export default class OpenapiEndpointRenderer {
351
337
  if (!httpMethodsThatAllowBody.includes(method))
352
338
  return this.defaultRequestBody();
353
339
  if (this.shouldAutogenerateBody()) {
354
- return this.generateRequestBodyForModel();
340
+ return this.generateRequestBodyForModel({ openapiName, renderOpts });
355
341
  }
356
342
  let schema = new OpenapiBodySegmentRenderer(this.requestBody, {
357
- openapiName: this.openapiName,
358
- schemaDelimiter: this.schemaDelimiter,
359
- casing: this.casing,
360
- suppressResponseEnums: this.suppressResponseEnums,
343
+ openapiName,
344
+ renderOpts,
361
345
  target: 'request',
362
346
  }).render().openapi;
363
347
  const bodyPageParam = this.paginate?.body;
@@ -421,7 +405,7 @@ export default class OpenapiEndpointRenderer {
421
405
  * that model that are safe to ingest will be automatically added to
422
406
  * the request body.
423
407
  */
424
- generateRequestBodyForModel() {
408
+ generateRequestBodyForModel({ openapiName, renderOpts, }) {
425
409
  const forDreamClass = this.requestBody?.for;
426
410
  const dreamClass = forDreamClass || this.getSingleDreamModelClass();
427
411
  if (!dreamClass)
@@ -534,10 +518,8 @@ export default class OpenapiEndpointRenderer {
534
518
  paramsShape.properties = {
535
519
  ...paramsShape.properties,
536
520
  [columnName]: new OpenapiBodySegmentRenderer(metadata.type, {
537
- openapiName: this.openapiName,
538
- schemaDelimiter: this.schemaDelimiter,
539
- casing: this.casing,
540
- suppressResponseEnums: this.suppressResponseEnums,
521
+ openapiName,
522
+ renderOpts,
541
523
  target: 'request',
542
524
  }).render().openapi,
543
525
  };
@@ -581,10 +563,8 @@ export default class OpenapiEndpointRenderer {
581
563
  }
582
564
  }
583
565
  let processedSchema = new OpenapiBodySegmentRenderer(paramsShape, {
584
- openapiName: this.openapiName,
585
- schemaDelimiter: this.schemaDelimiter,
586
- casing: this.casing,
587
- suppressResponseEnums: this.suppressResponseEnums,
566
+ openapiName,
567
+ renderOpts,
588
568
  target: 'request',
589
569
  }).render().openapi;
590
570
  const bodyPageParam = this.paginate?.body;
@@ -604,13 +584,11 @@ export default class OpenapiEndpointRenderer {
604
584
  *
605
585
  * Generates the responses portion of the endpoint
606
586
  */
607
- parseResponses() {
587
+ parseResponses({ openapiName, renderOpts, }) {
608
588
  let responseData = {};
609
589
  const rendererOpts = {
610
- openapiName: this.openapiName,
611
- schemaDelimiter: this.schemaDelimiter,
612
- casing: this.casing,
613
- suppressResponseEnums: this.suppressResponseEnums,
590
+ openapiName,
591
+ renderOpts,
614
592
  target: 'response',
615
593
  };
616
594
  const computedStatus = this.status || this.defaultStatus;
@@ -626,7 +604,7 @@ export default class OpenapiEndpointRenderer {
626
604
  };
627
605
  }
628
606
  else {
629
- const parsingResults = this.parseSerializerResponseShape();
607
+ const parsingResults = this.parseSerializerResponseShape({ renderOpts });
630
608
  serializersAppearingInHandWrittenOpenapi = [
631
609
  ...serializersAppearingInHandWrittenOpenapi,
632
610
  ...parsingResults.referencedSerializers,
@@ -641,7 +619,7 @@ export default class OpenapiEndpointRenderer {
641
619
  }
642
620
  Object.keys(this.responses || {}).forEach(statusCode => {
643
621
  const statusCodeInt = parseInt(statusCode);
644
- const response = cloneDeep(this.responses[statusCodeInt]);
622
+ const response = cloneDeepSafe(this.responses[statusCodeInt], obj => obj);
645
623
  responseData[statusCodeInt] ||= { description: statusDescription(statusCodeInt) };
646
624
  const statusResponse = responseData[statusCodeInt];
647
625
  const results = new OpenapiBodySegmentRenderer(response, rendererOpts).render();
@@ -657,13 +635,13 @@ export default class OpenapiEndpointRenderer {
657
635
  });
658
636
  const defaultResponses = this.omitDefaultResponses
659
637
  ? {}
660
- : openapiOpts(this.openapiName)?.defaults?.responses || {};
638
+ : openapiOpts(openapiName)?.defaults?.responses || {};
661
639
  const psychicAndConfigLevelDefaults = this.omitDefaultResponses
662
640
  ? {}
663
- : cloneDeep({
641
+ : cloneDeepSafe({
664
642
  ...DEFAULT_OPENAPI_RESPONSES,
665
643
  ...defaultResponses,
666
- });
644
+ }, obj => obj);
667
645
  Object.keys(psychicAndConfigLevelDefaults).forEach(key => {
668
646
  if (!responseData[key]) {
669
647
  const data = psychicAndConfigLevelDefaults[key];
@@ -702,7 +680,7 @@ export default class OpenapiEndpointRenderer {
702
680
  * returns a ref object for the callback passed to the
703
681
  * Openapi decorator.
704
682
  */
705
- parseSerializerResponseShape() {
683
+ parseSerializerResponseShape({ renderOpts, }) {
706
684
  const serializerClasses = this.getSerializerClasses();
707
685
  if (!serializerClasses)
708
686
  return {
@@ -710,9 +688,9 @@ export default class OpenapiEndpointRenderer {
710
688
  openapi: { description: 'no content' },
711
689
  };
712
690
  if (serializerClasses.length > 1) {
713
- return this.parseMultiEntitySerializerResponseShape(serializerClasses);
691
+ return this.parseMultiEntitySerializerResponseShape(serializerClasses, { renderOpts });
714
692
  }
715
- return this.parseSingleEntitySerializerResponseShape(serializerClasses[0]);
693
+ return this.parseSingleEntitySerializerResponseShape(serializerClasses[0], { renderOpts });
716
694
  }
717
695
  /**
718
696
  * @internal
@@ -725,16 +703,14 @@ export default class OpenapiEndpointRenderer {
725
703
  * public show() {...}
726
704
  * ```
727
705
  */
728
- parseSingleEntitySerializerResponseShape(serializer) {
706
+ parseSingleEntitySerializerResponseShape(serializer, { renderOpts, }) {
729
707
  if (serializer === undefined) {
730
708
  throw new OpenApiFailedToLookupSerializerForEndpoint(this.controllerClass, this.action);
731
709
  }
732
710
  if (!isDreamSerializer(serializer)) {
733
711
  throw new OpenApiSerializerForEndpointNotAFunction(this.controllerClass, this.action, serializer);
734
712
  }
735
- const serializerOpenapiRenderer = new SerializerOpenapiRenderer(serializer, {
736
- schemaDelimiter: this.schemaDelimiter,
737
- });
713
+ const serializerOpenapiRenderer = new SerializerOpenapiRenderer(serializer, renderOpts);
738
714
  const finalOutput = {
739
715
  content: {
740
716
  'application/json': {
@@ -801,18 +777,16 @@ export default class OpenapiEndpointRenderer {
801
777
  * public responses() {...}
802
778
  * ```
803
779
  */
804
- parseMultiEntitySerializerResponseShape(serializers) {
780
+ parseMultiEntitySerializerResponseShape(serializers, { renderOpts, }) {
805
781
  const anyOf = { anyOf: [] };
806
782
  serializers.forEach(serializer => {
807
783
  if (!isDreamSerializer(serializer))
808
784
  throw new NonSerializerDerivedInOpenapiEndpointRenderer(this.controllerClass, this.action, serializer);
809
785
  });
810
- const sortedSerializerClasses = sortBy(serializers, serializer => new SerializerOpenapiRenderer(serializer, { schemaDelimiter: this.schemaDelimiter }).openapiName);
786
+ const sortedSerializerClasses = sortBy(serializers, serializer => new SerializerOpenapiRenderer(serializer, renderOpts).openapiName);
811
787
  let referencedSerializers = [];
812
788
  sortedSerializerClasses.forEach(serializer => {
813
- const serializerOpenapiRenderer = new SerializerOpenapiRenderer(serializer, {
814
- schemaDelimiter: this.schemaDelimiter,
815
- });
789
+ const serializerOpenapiRenderer = new SerializerOpenapiRenderer(serializer, renderOpts);
816
790
  anyOf.anyOf.push(serializerOpenapiRenderer.serializerRef);
817
791
  referencedSerializers = [
818
792
  ...referencedSerializers,
@@ -990,55 +964,34 @@ function statusDescription(status) {
990
964
  return `Status ${status}`;
991
965
  }
992
966
  }
993
- function serializersToSchemaObjects(controllerClass, actionName, serializers, { casing, schemaDelimiter, suppressResponseEnums, openapiName, processedSchemas, }) {
967
+ function serializersToSchemaObjects(controllerClass, actionName, serializers, { renderOpts, openapiName, alreadyExtractedDescendantSerializers, renderedSchemasOpenapi, }) {
994
968
  serializers.forEach(serializer => {
995
969
  if (!isDreamSerializer(serializer))
996
970
  throw new NonSerializerDerivedInToSchemaObjects(controllerClass, actionName, serializer);
997
971
  });
998
- serializers = serializers.filter(serializer => {
999
- const serializerOpenapiRenderer = new SerializerOpenapiRenderer(serializer, {
1000
- casing,
1001
- schemaDelimiter,
1002
- suppressResponseEnums,
1003
- });
1004
- return !processedSchemas[serializerOpenapiRenderer.globalName];
1005
- });
972
+ serializers = serializers.filter(serializer => !renderedSchemasOpenapi[new SerializerOpenapiRenderer(serializer, renderOpts).openapiName]);
1006
973
  if (!serializers.length)
1007
- return { processedSchemas, renderedSchemas: {} };
1008
- const renderedSchemas = {};
974
+ return;
1009
975
  let dependentOnSerializers = [];
1010
976
  serializers.forEach(serializer => {
1011
- const renderer = new SerializerOpenapiRenderer(serializer, {
1012
- casing,
1013
- schemaDelimiter,
1014
- suppressResponseEnums,
1015
- });
1016
- const globalName = renderer.globalName;
1017
- processedSchemas = { ...processedSchemas, [globalName]: true };
1018
- const results = renderer.renderedOpenapi(processedSchemas);
977
+ const renderer = new SerializerOpenapiRenderer(serializer, renderOpts);
978
+ const results = renderer.renderedOpenapi(alreadyExtractedDescendantSerializers);
1019
979
  const segmentRendererResults = new OpenapiBodySegmentRenderer(results.openapi, {
1020
980
  openapiName,
1021
- casing,
1022
- schemaDelimiter,
1023
- suppressResponseEnums,
981
+ renderOpts,
1024
982
  target: 'response',
1025
983
  }).render();
1026
- renderedSchemas[renderer.openapiName] = segmentRendererResults.openapi;
984
+ renderedSchemasOpenapi[renderer.openapiName] = segmentRendererResults.openapi;
1027
985
  dependentOnSerializers = [
1028
986
  ...dependentOnSerializers,
1029
987
  ...results.referencedSerializers,
1030
- ...segmentRendererResults.referencedSerializers,
988
+ ...segmentRendererResults.referencedSerializers, // should always be empty
1031
989
  ];
1032
990
  });
1033
- const recursiveResults = serializersToSchemaObjects(controllerClass, actionName, dependentOnSerializers, {
1034
- casing,
1035
- suppressResponseEnums,
1036
- schemaDelimiter,
991
+ serializersToSchemaObjects(controllerClass, actionName, dependentOnSerializers, {
992
+ renderOpts,
1037
993
  openapiName,
1038
- processedSchemas,
994
+ alreadyExtractedDescendantSerializers,
995
+ renderedSchemasOpenapi,
1039
996
  });
1040
- return {
1041
- processedSchemas: { ...processedSchemas, ...recursiveResults.processedSchemas },
1042
- renderedSchemas: { ...renderedSchemas, ...recursiveResults.renderedSchemas },
1043
- };
1044
997
  }
@@ -2,6 +2,6 @@ import openapiOpts from './openapiOpts.js';
2
2
  /**
3
3
  * returns either the delimiter set in the app config, or else a blank string
4
4
  */
5
- export default function suppressResponseEnums(openapiName) {
5
+ export default function suppressResponseEnumsConfig(openapiName) {
6
6
  return !!openapiOpts(openapiName)?.suppressResponseEnums;
7
7
  }
@@ -161,7 +161,6 @@ Try setting it to something valid, like:
161
161
  _openapi = {
162
162
  default: {
163
163
  outputFilename: 'openapi.json',
164
- schemaDelimiter: '',
165
164
  info: {
166
165
  title: 'untitled openapi spec',
167
166
  version: 'unknown version',
@@ -2,14 +2,10 @@ import { closeAllDbConnections, DreamLogos } from '@rvoh/dream';
2
2
  import * as cookieParser from 'cookie-parser';
3
3
  import * as cors from 'cors';
4
4
  import * as express from 'express';
5
- import * as OpenApiValidator from 'express-openapi-validator';
6
- import * as path from 'node:path';
7
- import { debuglog, inspect } from 'node:util';
8
- import isOpenapiError from '../helpers/isOpenapiError.js';
9
5
  import PsychicApp from '../psychic-app/index.js';
10
6
  import PsychicRouter from '../router/index.js';
11
7
  import startPsychicServer, { createPsychicHttpInstance, } from './helpers/startPsychicServer.js';
12
- const debugEnabled = debuglog('psychic').enabled;
8
+ // const debugEnabled = debuglog('psychic').enabled
13
9
  export default class PsychicServer {
14
10
  static async startPsychicServer(opts) {
15
11
  return await startPsychicServer(opts);
@@ -64,7 +60,6 @@ export default class PsychicServer {
64
60
  for (const serverInitAfterMiddlewareHook of this.config.specialHooks.serverInitAfterMiddleware) {
65
61
  await serverInitAfterMiddlewareHook(this);
66
62
  }
67
- this.initializeOpenapiValidation();
68
63
  await this.buildRoutes();
69
64
  for (const afterRoutesHook of this.config.specialHooks.serverInitAfterRoutes) {
70
65
  await afterRoutesHook(this);
@@ -143,35 +138,6 @@ export default class PsychicServer {
143
138
  initializeJSON() {
144
139
  this.expressApp.use(express.json(this.config.jsonOptions));
145
140
  }
146
- initializeOpenapiValidation() {
147
- const psychicApp = PsychicApp.getOrFail();
148
- for (const openapiName in psychicApp.openapi) {
149
- const openapiOpts = psychicApp.openapi[openapiName];
150
- if (openapiOpts?.validation) {
151
- const opts = openapiOpts.validation;
152
- opts.apiSpec ||= path.join(psychicApp.apiRoot, 'openapi.json');
153
- this.expressApp.use(OpenApiValidator.middleware(opts));
154
- this.expressApp.use((err, req, res, next) => {
155
- if (isOpenapiError(err)) {
156
- if (debugEnabled) {
157
- PsychicApp.log(inspect(err));
158
- console.trace();
159
- }
160
- res.status(err.status).json({
161
- message: err.message,
162
- errors: err.errors,
163
- });
164
- }
165
- else {
166
- if (debugEnabled) {
167
- PsychicApp.logWithLevel('error', err);
168
- }
169
- next();
170
- }
171
- });
172
- }
173
- }
174
- }
175
141
  async buildRoutes() {
176
142
  const r = new PsychicRouter(this.expressApp, this.config);
177
143
  const psychicApp = PsychicApp.getOrFail();
@@ -1,4 +1,4 @@
1
- import { Dream, DreamModelSerializerType, DreamParamSafeAttributes, SimpleObjectSerializerType } from '@rvoh/dream';
1
+ import { Dream, DreamModelSerializerType, DreamParamSafeAttributes, SerializerRendererOpts, SimpleObjectSerializerType } from '@rvoh/dream';
2
2
  import { Request, Response } from 'express';
3
3
  import { ControllerHook } from '../controller/hooks.js';
4
4
  import { HttpStatusCodeInt, HttpStatusSymbol } from '../error/http/status-codes.js';
@@ -119,10 +119,12 @@ export default class PsychicController {
119
119
  session: Session;
120
120
  config: PsychicApp;
121
121
  action: string;
122
+ renderOpts: SerializerRendererOpts;
122
123
  constructor(req: Request, res: Response, { config, action, }: {
123
124
  config: PsychicApp;
124
125
  action: string;
125
126
  });
127
+ get headers(): import("http").IncomingHttpHeaders;
126
128
  get params(): PsychicParamsDictionary;
127
129
  param<ReturnType = string>(key: string): ReturnType;
128
130
  castParam<const EnumType extends readonly string[], OptsType extends ParamsCastOptions<EnumType>, ExpectedType extends (typeof PsychicParamsPrimitiveLiterals)[number] | RegExp>(key: string, expectedType: ExpectedType, opts?: OptsType): ValidatedAllowsNull<ExpectedType, OptsType> extends infer T ? T extends ValidatedAllowsNull<ExpectedType, OptsType> ? T extends true ? ValidatedReturnType<ExpectedType, OptsType> | null | undefined : ValidatedReturnType<ExpectedType, OptsType> : never : never;
@@ -1,15 +1,12 @@
1
- import { DreamModelSerializerType, OpenapiSchemaBody, OpenapiSchemaBodyShorthand, OpenapiShorthandPrimitiveTypes, SerializerCasing, SimpleObjectSerializerType } from '@rvoh/dream';
2
- import { OpenapiEndpointResponse, OpenapiResponses } from './endpoint.js';
1
+ import { DreamModelSerializerType, OpenapiSchemaBody, OpenapiSchemaBodyShorthand, OpenapiShorthandPrimitiveTypes, SimpleObjectSerializerType } from '@rvoh/dream';
2
+ import { OpenapiEndpointResponse, OpenapiRenderOpts, OpenapiResponses } from './endpoint.js';
3
3
  export interface OpenapiBodySegmentRendererOpts {
4
4
  openapiName: string;
5
- schemaDelimiter: string;
6
- casing: SerializerCasing;
7
- suppressResponseEnums: boolean;
5
+ renderOpts: OpenapiRenderOpts;
8
6
  target: OpenapiBodyTarget;
9
7
  }
10
8
  export default class OpenapiBodySegmentRenderer {
11
9
  private bodySegment;
12
- private schemaDelimiter;
13
10
  private casing;
14
11
  private suppressResponseEnums;
15
12
  private target;
@@ -20,7 +17,7 @@ export default class OpenapiBodySegmentRenderer {
20
17
  * Used to recursively parse nested object structures
21
18
  * within nested openapi objects
22
19
  */
23
- constructor(bodySegment: OpenapiBodySegment, { openapiName, schemaDelimiter, casing, suppressResponseEnums, target }: OpenapiBodySegmentRendererOpts);
20
+ constructor(bodySegment: OpenapiBodySegment, { openapiName, renderOpts, target }: OpenapiBodySegmentRendererOpts);
24
21
  /**
25
22
  * returns the shorthanded body segment, rendered
26
23
  * to the appropriate openapi shape