@rvoh/psychic 3.0.0-alpha.2 → 3.0.0-alpha.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.
@@ -2,6 +2,7 @@ import { Dream, DreamApp } from '@rvoh/dream';
2
2
  import { GlobalNameNotSet } from '@rvoh/dream/errors';
3
3
  import { DreamSerializerBuilder, ObjectSerializerBuilder } from '@rvoh/dream/system';
4
4
  import fastJsonStringify from 'fast-json-stringify';
5
+ import { debuglog } from 'node:util';
5
6
  import ParamValidationError from '../error/controller/ParamValidationError.js';
6
7
  import HttpStatusBadGateway from '../error/http/BadGateway.js';
7
8
  import HttpStatusBadRequest from '../error/http/BadRequest.js';
@@ -646,15 +647,23 @@ export default class PsychicController {
646
647
  const schemaWithComponents = validator.getResponseSchemaWithComponents(statusCode);
647
648
  if (!schemaWithComponents)
648
649
  continue;
649
- // Generate cache key
650
650
  const cacheKey = `${controllerClass.globalName}#${this.action}|${openapiName}|${statusCode}`;
651
- // Check cache first
652
651
  const cachedStringify = getCachedStringify(cacheKey);
653
652
  if (cachedStringify)
654
653
  return cachedStringify;
655
- // Compile and cache the stringify function
656
- // If compilation fails, let the error propagate (dead programs tell no lies)
657
- const stringifyFn = fastJsonStringify(schemaWithComponents);
654
+ let stringifyFn;
655
+ if (debuglog('json').enabled) {
656
+ const result = fastJsonStringify(schemaWithComponents, {
657
+ mode: 'debug',
658
+ ajv: { validateFormats: false },
659
+ });
660
+ PsychicApp.log('fast-json-stringify code:', result.code);
661
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
662
+ stringifyFn = fastJsonStringify.restore(result);
663
+ }
664
+ else {
665
+ stringifyFn = fastJsonStringify(schemaWithComponents, { ajv: { validateFormats: false } });
666
+ }
658
667
  cacheStringify(cacheKey, stringifyFn);
659
668
  return stringifyFn;
660
669
  }
@@ -166,8 +166,8 @@ export default class SerializerOpenapiRenderer {
166
166
  // rendersOnes //
167
167
  //////////////////
168
168
  case 'rendersOne': {
169
+ const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
169
170
  try {
170
- const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
171
171
  const { associationOpts, referencedSerializersAndOpenapiSchemaBodyShorthand } = associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers);
172
172
  const optional = attribute.options.optional ?? associationOpts.optional;
173
173
  newlyReferencedSerializers =
@@ -193,8 +193,14 @@ export default class SerializerOpenapiRenderer {
193
193
  return accumulator;
194
194
  }
195
195
  catch (error) {
196
- if (error instanceof CallingSerializersThrewError)
196
+ if (error instanceof CallingSerializersThrewError) {
197
+ accumulator[outputAttributeName] = {
198
+ type: 'object',
199
+ additionalProperties: true,
200
+ description: `Serializer ${this.serializer['globalName']} includes a rendersOne "${outputAttributeName}" with an OpenAPI shape that cannot be defined. This will break fast-json-stringify. Define the OpenAPI shape or disableFastJson on the endpoint.`,
201
+ };
197
202
  return accumulator;
203
+ }
198
204
  if (error instanceof AttemptedToDeriveDescendentSerializersFromNonSerializer)
199
205
  throw new ExpectedSerializerForRendersOneOrManyOption('rendersOne', this.globalName, attribute);
200
206
  throw error;
@@ -207,8 +213,8 @@ export default class SerializerOpenapiRenderer {
207
213
  // rendersManys //
208
214
  ///////////////////
209
215
  case 'rendersMany': {
216
+ const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
210
217
  try {
211
- const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
212
218
  const { referencedSerializersAndOpenapiSchemaBodyShorthand } = associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers);
213
219
  newlyReferencedSerializers =
214
220
  referencedSerializersAndOpenapiSchemaBodyShorthand.referencedSerializers;
@@ -219,8 +225,14 @@ export default class SerializerOpenapiRenderer {
219
225
  return accumulator;
220
226
  }
221
227
  catch (error) {
222
- if (error instanceof CallingSerializersThrewError)
228
+ if (error instanceof CallingSerializersThrewError) {
229
+ accumulator[outputAttributeName] = {
230
+ type: 'array',
231
+ items: { type: 'object', additionalProperties: true },
232
+ description: `Serializer ${this.serializer['globalName']} includes a rendersMany "${outputAttributeName}" with an OpenAPI shape that cannot be defined. This will break fast-json-stringify. Define the OpenAPI shape or disableFastJson on the endpoint.`,
233
+ };
223
234
  return accumulator;
235
+ }
224
236
  if (error instanceof AttemptedToDeriveDescendentSerializersFromNonSerializer)
225
237
  throw new ExpectedSerializerForRendersOneOrManyOption('rendersMany', this.globalName, attribute);
226
238
  throw error;
@@ -1,9 +1,9 @@
1
- import { closeAllDbConnections } from '@rvoh/dream/db';
2
1
  import cors from '@koa/cors';
2
+ import etag from '@koa/etag';
3
+ import { closeAllDbConnections } from '@rvoh/dream/db';
3
4
  import Koa from 'koa';
4
5
  import koaBodyparser from 'koa-bodyparser';
5
6
  import conditional from 'koa-conditional-get';
6
- import etag from 'koa-etag';
7
7
  import logIfDevelopment from '../controller/helpers/logIfDevelopment.js';
8
8
  import EnvInternal from '../helpers/EnvInternal.js';
9
9
  import PsychicApp from '../psychic-app/index.js';
@@ -2,6 +2,7 @@ import { Dream, DreamApp } from '@rvoh/dream';
2
2
  import { GlobalNameNotSet } from '@rvoh/dream/errors';
3
3
  import { DreamSerializerBuilder, ObjectSerializerBuilder } from '@rvoh/dream/system';
4
4
  import fastJsonStringify from 'fast-json-stringify';
5
+ import { debuglog } from 'node:util';
5
6
  import ParamValidationError from '../error/controller/ParamValidationError.js';
6
7
  import HttpStatusBadGateway from '../error/http/BadGateway.js';
7
8
  import HttpStatusBadRequest from '../error/http/BadRequest.js';
@@ -646,15 +647,23 @@ export default class PsychicController {
646
647
  const schemaWithComponents = validator.getResponseSchemaWithComponents(statusCode);
647
648
  if (!schemaWithComponents)
648
649
  continue;
649
- // Generate cache key
650
650
  const cacheKey = `${controllerClass.globalName}#${this.action}|${openapiName}|${statusCode}`;
651
- // Check cache first
652
651
  const cachedStringify = getCachedStringify(cacheKey);
653
652
  if (cachedStringify)
654
653
  return cachedStringify;
655
- // Compile and cache the stringify function
656
- // If compilation fails, let the error propagate (dead programs tell no lies)
657
- const stringifyFn = fastJsonStringify(schemaWithComponents);
654
+ let stringifyFn;
655
+ if (debuglog('json').enabled) {
656
+ const result = fastJsonStringify(schemaWithComponents, {
657
+ mode: 'debug',
658
+ ajv: { validateFormats: false },
659
+ });
660
+ PsychicApp.log('fast-json-stringify code:', result.code);
661
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
662
+ stringifyFn = fastJsonStringify.restore(result);
663
+ }
664
+ else {
665
+ stringifyFn = fastJsonStringify(schemaWithComponents, { ajv: { validateFormats: false } });
666
+ }
658
667
  cacheStringify(cacheKey, stringifyFn);
659
668
  return stringifyFn;
660
669
  }
@@ -166,8 +166,8 @@ export default class SerializerOpenapiRenderer {
166
166
  // rendersOnes //
167
167
  //////////////////
168
168
  case 'rendersOne': {
169
+ const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
169
170
  try {
170
- const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
171
171
  const { associationOpts, referencedSerializersAndOpenapiSchemaBodyShorthand } = associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers);
172
172
  const optional = attribute.options.optional ?? associationOpts.optional;
173
173
  newlyReferencedSerializers =
@@ -193,8 +193,14 @@ export default class SerializerOpenapiRenderer {
193
193
  return accumulator;
194
194
  }
195
195
  catch (error) {
196
- if (error instanceof CallingSerializersThrewError)
196
+ if (error instanceof CallingSerializersThrewError) {
197
+ accumulator[outputAttributeName] = {
198
+ type: 'object',
199
+ additionalProperties: true,
200
+ description: `Serializer ${this.serializer['globalName']} includes a rendersOne "${outputAttributeName}" with an OpenAPI shape that cannot be defined. This will break fast-json-stringify. Define the OpenAPI shape or disableFastJson on the endpoint.`,
201
+ };
197
202
  return accumulator;
203
+ }
198
204
  if (error instanceof AttemptedToDeriveDescendentSerializersFromNonSerializer)
199
205
  throw new ExpectedSerializerForRendersOneOrManyOption('rendersOne', this.globalName, attribute);
200
206
  throw error;
@@ -207,8 +213,8 @@ export default class SerializerOpenapiRenderer {
207
213
  // rendersManys //
208
214
  ///////////////////
209
215
  case 'rendersMany': {
216
+ const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
210
217
  try {
211
- const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
212
218
  const { referencedSerializersAndOpenapiSchemaBodyShorthand } = associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers);
213
219
  newlyReferencedSerializers =
214
220
  referencedSerializersAndOpenapiSchemaBodyShorthand.referencedSerializers;
@@ -219,8 +225,14 @@ export default class SerializerOpenapiRenderer {
219
225
  return accumulator;
220
226
  }
221
227
  catch (error) {
222
- if (error instanceof CallingSerializersThrewError)
228
+ if (error instanceof CallingSerializersThrewError) {
229
+ accumulator[outputAttributeName] = {
230
+ type: 'array',
231
+ items: { type: 'object', additionalProperties: true },
232
+ description: `Serializer ${this.serializer['globalName']} includes a rendersMany "${outputAttributeName}" with an OpenAPI shape that cannot be defined. This will break fast-json-stringify. Define the OpenAPI shape or disableFastJson on the endpoint.`,
233
+ };
223
234
  return accumulator;
235
+ }
224
236
  if (error instanceof AttemptedToDeriveDescendentSerializersFromNonSerializer)
225
237
  throw new ExpectedSerializerForRendersOneOrManyOption('rendersMany', this.globalName, attribute);
226
238
  throw error;
@@ -1,9 +1,9 @@
1
- import { closeAllDbConnections } from '@rvoh/dream/db';
2
1
  import cors from '@koa/cors';
2
+ import etag from '@koa/etag';
3
+ import { closeAllDbConnections } from '@rvoh/dream/db';
3
4
  import Koa from 'koa';
4
5
  import koaBodyparser from 'koa-bodyparser';
5
6
  import conditional from 'koa-conditional-get';
6
- import etag from 'koa-etag';
7
7
  import logIfDevelopment from '../controller/helpers/logIfDevelopment.js';
8
8
  import EnvInternal from '../helpers/EnvInternal.js';
9
9
  import PsychicApp from '../psychic-app/index.js';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "name": "@rvoh/psychic",
4
4
  "description": "Typescript web framework",
5
- "version": "3.0.0-alpha.2",
5
+ "version": "3.0.0-alpha.3",
6
6
  "author": "RVOHealth",
7
7
  "repository": {
8
8
  "type": "git",
@@ -66,6 +66,7 @@
66
66
  "prepack": "pnpm build"
67
67
  },
68
68
  "dependencies": {
69
+ "@koa/etag": "^5.0.2",
69
70
  "ajv": "^8.17.1",
70
71
  "ajv-formats": "^3.0.1",
71
72
  "commander": "^12.1.0",
@@ -84,7 +85,6 @@
84
85
  "koa": "^2.15.3",
85
86
  "koa-bodyparser": "^4.4.1",
86
87
  "koa-conditional-get": "^3.0.0",
87
- "koa-etag": "^5.0.0",
88
88
  "openapi-typescript": "^7.8.0"
89
89
  },
90
90
  "devDependencies": {
@@ -94,7 +94,7 @@
94
94
  "@rvoh/dream": "^2.3.1",
95
95
  "@rvoh/dream-spec-helpers": "^2.1.1",
96
96
  "@rvoh/psychic-spec-helpers": "^3.0.0-alpha.1",
97
- "@types/koa": "^2.15.0",
97
+ "@types/koa": "^3.0.1",
98
98
  "@types/koa-bodyparser": "^4.3.12",
99
99
  "@types/koa-conditional-get": "^2.0.3",
100
100
  "@types/koa-etag": "^3.0.3",
@@ -110,10 +110,9 @@
110
110
  "@typescript/analyze-trace": "^0.10.1",
111
111
  "eslint": "^9.39.1",
112
112
  "jsdom": "^26.1.0",
113
- "koa": "^2.15.3",
113
+ "koa": "^3.1.1",
114
114
  "koa-bodyparser": "^4.4.1",
115
115
  "koa-conditional-get": "^3.0.0",
116
- "koa-etag": "^4.0.0",
117
116
  "koa-passport": "^6.0.0",
118
117
  "koa-session": "^7.0.2",
119
118
  "kysely": "^0.28.5",