@rvoh/psychic 1.6.3 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 1.6.4
2
+
3
+ Raise an exception if attempting to import an openapi file during PsychicApp.init when in production. We will still swallow the exception in non-prod environments so that one can create a new openapi configuration and run sync without getting an error.
4
+
1
5
  ## 1.6.3
2
6
 
3
7
  - castParam accepts raw openapi shapes as type arguments, correctly casting the result to an interface representing the provided openapi shape.
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class OpenapiFileNotFound extends Error {
4
+ openapiName;
5
+ openapiPath;
6
+ constructor(openapiName, openapiPath) {
7
+ super();
8
+ this.openapiName = openapiName;
9
+ this.openapiPath = openapiPath;
10
+ }
11
+ get message() {
12
+ return `
13
+ Failed to read openapi file during psychic app initialization.
14
+
15
+ openapiName: ${this.openapiName}
16
+ openapi file path: ${this.openapiPath}
17
+
18
+ Typically, this occurs when you build your application to production,
19
+ but fail to copy over your openapi json files as part of the production
20
+ build. Be sure that your "build" script in your package.json correctly
21
+ copies the openapi dir over, e.g.
22
+
23
+ "build": "... && cp -R ./${this.openapiPath} ./dist/${this.openapiPath}"
24
+ `;
25
+ }
26
+ }
27
+ exports.default = OpenapiFileNotFound;
@@ -50,7 +50,7 @@ class OpenapiPayloadValidator {
50
50
  const routes = index_js_1.default.getOrFail().routesCache;
51
51
  if (openapiEndpointRenderer.shouldValidateRequestBody(openapiName)) {
52
52
  const openapiPathObject = openapiEndpointRenderer['computedRequestBody'](routes, {
53
- openapiName: 'default',
53
+ openapiName,
54
54
  renderOpts: this.renderOpts,
55
55
  });
56
56
  const requestBodySchema = openapiPathObject?.['content']?.['application/json']?.['schema'];
@@ -101,7 +101,7 @@ class OpenapiPayloadValidator {
101
101
  const openapiName = this.openapiName;
102
102
  if (openapiEndpointRenderer.shouldValidateQuery(openapiName)) {
103
103
  const openapiQueryParams = openapiEndpointRenderer['queryArray']({
104
- openapiName: 'default',
104
+ openapiName,
105
105
  renderOpts: this.renderOpts,
106
106
  });
107
107
  this.validateOrFail(query, this.openapiParameterResponsesToSchemaObject(openapiQueryParams), 'query');
@@ -126,7 +126,7 @@ class OpenapiPayloadValidator {
126
126
  const openapiName = this.openapiName;
127
127
  if (openapiEndpointRenderer.shouldValidateResponseBody(openapiName)) {
128
128
  const openapiResponseBody = openapiEndpointRenderer['parseResponses']({
129
- openapiName: this.openapiName,
129
+ openapiName,
130
130
  renderOpts: this.renderOpts,
131
131
  }).openapi?.[statusCode.toString()];
132
132
  const schema = openapiResponseBody?.['content']?.['application/json']?.['schema'];
@@ -129,9 +129,18 @@ class PsychicApp {
129
129
  * instead of having to build it from scratch.
130
130
  */
131
131
  async buildOpenapiCache() {
132
- await Promise.all(Object.keys(this.openapi).map(async (openapiName) => {
132
+ // build caches for all files that are registered for validation
133
+ await Promise.all(Object.keys(this.openapi)
134
+ .filter(key => this.openapi[key]?.validate)
135
+ .map(async (openapiName) => {
133
136
  await this.cacheOpenapiFile(openapiName);
134
137
  }));
138
+ // ignore all files that are not registered for validation
139
+ Object.keys(this.openapi)
140
+ .filter(key => !this.openapi[key]?.validate)
141
+ .forEach(openapiName => {
142
+ this.ignoreOpenapiFile(openapiName);
143
+ });
135
144
  }
136
145
  /**
137
146
  * Since javascript is inherently vulnerable to circular dependencies,
@@ -295,6 +304,16 @@ Try setting it to something valid, like:
295
304
  async cacheOpenapiFile(openapiName) {
296
305
  await (0, openapi_cache_js_1.readAndCacheOpenapiFile)(openapiName);
297
306
  }
307
+ /**
308
+ * indicates to the underlying cache that this openapi file is intentionally
309
+ * being ignored, so that future lookups for the file cache do not raise
310
+ * an exception
311
+ *
312
+ * @param openapiName - the openapiName to ignore
313
+ */
314
+ ignoreOpenapiFile(openapiName) {
315
+ (0, openapi_cache_js_1.ignoreOpenapiFile)(openapiName);
316
+ }
298
317
  /**
299
318
  * @internal
300
319
  *
@@ -28,9 +28,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.getOpenapiFileOrFail = getOpenapiFileOrFail;
30
30
  exports.readAndCacheOpenapiFile = readAndCacheOpenapiFile;
31
+ exports.ignoreOpenapiFile = ignoreOpenapiFile;
31
32
  const fs = __importStar(require("node:fs/promises"));
32
33
  const openapiJsonPath_js_1 = __importDefault(require("../helpers/openapiJsonPath.js"));
33
34
  const must_call_psychic_app_init_first_js_1 = __importDefault(require("../error/psychic-app/must-call-psychic-app-init-first.js"));
35
+ const EnvInternal_js_1 = __importDefault(require("../helpers/EnvInternal.js"));
36
+ const OpenapiFileNotFound_js_1 = __importDefault(require("../error/openapi/OpenapiFileNotFound.js"));
34
37
  const _openapiData = {};
35
38
  /**
36
39
  * Raises an exception if readAndCacheOpenapiFile was not called
@@ -43,7 +46,7 @@ function getOpenapiFileOrFail(openapiName) {
43
46
  const val = _openapiData[openapiName];
44
47
  if (!val)
45
48
  throw new must_call_psychic_app_init_first_js_1.default();
46
- if (val === FILE_DOES_NOT_EXIST)
49
+ if (val === FILE_DOES_NOT_EXIST || val === FILE_WAS_IGNORED)
47
50
  return undefined;
48
51
  return val;
49
52
  }
@@ -59,10 +62,9 @@ function getOpenapiFileOrFail(openapiName) {
59
62
  async function readAndCacheOpenapiFile(openapiName) {
60
63
  if (_openapiData[openapiName])
61
64
  return;
62
- // if the openapi file is not generated yet, we don't want to raise an
63
- // exception, so we will simply consider this file as undefined.
65
+ const openapiPath = (0, openapiJsonPath_js_1.default)(openapiName);
64
66
  try {
65
- const buffer = await fs.readFile((0, openapiJsonPath_js_1.default)(openapiName));
67
+ const buffer = await fs.readFile(openapiPath);
66
68
  const openapiDoc = JSON.parse(buffer.toString());
67
69
  // we only cache the components from the openapi files,
68
70
  // since that is all that we currently need for validation,
@@ -72,8 +74,26 @@ async function readAndCacheOpenapiFile(openapiName) {
72
74
  : FILE_DOES_NOT_EXIST;
73
75
  }
74
76
  catch {
77
+ if (EnvInternal_js_1.default.isProduction) {
78
+ throw new OpenapiFileNotFound_js_1.default(openapiName, openapiPath);
79
+ }
80
+ // if the openapi file is not generated yet and we are in our local
81
+ // environment, we don't want to raise an exception, since it could
82
+ // prevent someone from ever defining a new openapi configuration.
75
83
  _openapiData[openapiName] = FILE_DOES_NOT_EXIST;
76
- // noop
77
84
  }
78
85
  }
86
+ /**
87
+ * Reads the openapi file corresponding to the openapiName,
88
+ * and caches its contents, enabling them to be read back
89
+ * at a later time, such as during endpoint validation.
90
+ *
91
+ * This function is called during PsychicApp.init sequence automatically.
92
+ *
93
+ * @param openapiName - the openapiName you wish to look up
94
+ */
95
+ function ignoreOpenapiFile(openapiName) {
96
+ _openapiData[openapiName] = FILE_WAS_IGNORED;
97
+ }
79
98
  const FILE_DOES_NOT_EXIST = 'FILE_DOES_NOT_EXIST';
99
+ const FILE_WAS_IGNORED = 'FILE_WAS_IGNORED';
@@ -0,0 +1,24 @@
1
+ export default class OpenapiFileNotFound extends Error {
2
+ openapiName;
3
+ openapiPath;
4
+ constructor(openapiName, openapiPath) {
5
+ super();
6
+ this.openapiName = openapiName;
7
+ this.openapiPath = openapiPath;
8
+ }
9
+ get message() {
10
+ return `
11
+ Failed to read openapi file during psychic app initialization.
12
+
13
+ openapiName: ${this.openapiName}
14
+ openapi file path: ${this.openapiPath}
15
+
16
+ Typically, this occurs when you build your application to production,
17
+ but fail to copy over your openapi json files as part of the production
18
+ build. Be sure that your "build" script in your package.json correctly
19
+ copies the openapi dir over, e.g.
20
+
21
+ "build": "... && cp -R ./${this.openapiPath} ./dist/${this.openapiPath}"
22
+ `;
23
+ }
24
+ }
@@ -45,7 +45,7 @@ export default class OpenapiPayloadValidator {
45
45
  const routes = PsychicApp.getOrFail().routesCache;
46
46
  if (openapiEndpointRenderer.shouldValidateRequestBody(openapiName)) {
47
47
  const openapiPathObject = openapiEndpointRenderer['computedRequestBody'](routes, {
48
- openapiName: 'default',
48
+ openapiName,
49
49
  renderOpts: this.renderOpts,
50
50
  });
51
51
  const requestBodySchema = openapiPathObject?.['content']?.['application/json']?.['schema'];
@@ -96,7 +96,7 @@ export default class OpenapiPayloadValidator {
96
96
  const openapiName = this.openapiName;
97
97
  if (openapiEndpointRenderer.shouldValidateQuery(openapiName)) {
98
98
  const openapiQueryParams = openapiEndpointRenderer['queryArray']({
99
- openapiName: 'default',
99
+ openapiName,
100
100
  renderOpts: this.renderOpts,
101
101
  });
102
102
  this.validateOrFail(query, this.openapiParameterResponsesToSchemaObject(openapiQueryParams), 'query');
@@ -121,7 +121,7 @@ export default class OpenapiPayloadValidator {
121
121
  const openapiName = this.openapiName;
122
122
  if (openapiEndpointRenderer.shouldValidateResponseBody(openapiName)) {
123
123
  const openapiResponseBody = openapiEndpointRenderer['parseResponses']({
124
- openapiName: this.openapiName,
124
+ openapiName,
125
125
  renderOpts: this.renderOpts,
126
126
  }).openapi?.[statusCode.toString()];
127
127
  const schema = openapiResponseBody?.['content']?.['application/json']?.['schema'];
@@ -13,7 +13,7 @@ import importControllers, { getControllersOrFail } from './helpers/import/import
13
13
  import importInitializers, { getInitializersOrBlank } from './helpers/import/importInitializers.js';
14
14
  import importServices, { getServicesOrFail } from './helpers/import/importServices.js';
15
15
  import lookupClassByGlobalName from './helpers/lookupClassByGlobalName.js';
16
- import { getOpenapiFileOrFail, readAndCacheOpenapiFile } from './openapi-cache.js';
16
+ import { getOpenapiFileOrFail, ignoreOpenapiFile, readAndCacheOpenapiFile } from './openapi-cache.js';
17
17
  export default class PsychicApp {
18
18
  /**
19
19
  * Called by the initializePsychicApp function, which is built
@@ -100,9 +100,18 @@ export default class PsychicApp {
100
100
  * instead of having to build it from scratch.
101
101
  */
102
102
  async buildOpenapiCache() {
103
- await Promise.all(Object.keys(this.openapi).map(async (openapiName) => {
103
+ // build caches for all files that are registered for validation
104
+ await Promise.all(Object.keys(this.openapi)
105
+ .filter(key => this.openapi[key]?.validate)
106
+ .map(async (openapiName) => {
104
107
  await this.cacheOpenapiFile(openapiName);
105
108
  }));
109
+ // ignore all files that are not registered for validation
110
+ Object.keys(this.openapi)
111
+ .filter(key => !this.openapi[key]?.validate)
112
+ .forEach(openapiName => {
113
+ this.ignoreOpenapiFile(openapiName);
114
+ });
106
115
  }
107
116
  /**
108
117
  * Since javascript is inherently vulnerable to circular dependencies,
@@ -266,6 +275,16 @@ Try setting it to something valid, like:
266
275
  async cacheOpenapiFile(openapiName) {
267
276
  await readAndCacheOpenapiFile(openapiName);
268
277
  }
278
+ /**
279
+ * indicates to the underlying cache that this openapi file is intentionally
280
+ * being ignored, so that future lookups for the file cache do not raise
281
+ * an exception
282
+ *
283
+ * @param openapiName - the openapiName to ignore
284
+ */
285
+ ignoreOpenapiFile(openapiName) {
286
+ ignoreOpenapiFile(openapiName);
287
+ }
269
288
  /**
270
289
  * @internal
271
290
  *
@@ -1,6 +1,8 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import openapiJsonPath from '../helpers/openapiJsonPath.js';
3
3
  import MustCallPsychicAppInitFirst from '../error/psychic-app/must-call-psychic-app-init-first.js';
4
+ import EnvInternal from '../helpers/EnvInternal.js';
5
+ import OpenapiFileNotFound from '../error/openapi/OpenapiFileNotFound.js';
4
6
  const _openapiData = {};
5
7
  /**
6
8
  * Raises an exception if readAndCacheOpenapiFile was not called
@@ -13,7 +15,7 @@ export function getOpenapiFileOrFail(openapiName) {
13
15
  const val = _openapiData[openapiName];
14
16
  if (!val)
15
17
  throw new MustCallPsychicAppInitFirst();
16
- if (val === FILE_DOES_NOT_EXIST)
18
+ if (val === FILE_DOES_NOT_EXIST || val === FILE_WAS_IGNORED)
17
19
  return undefined;
18
20
  return val;
19
21
  }
@@ -29,10 +31,9 @@ export function getOpenapiFileOrFail(openapiName) {
29
31
  export async function readAndCacheOpenapiFile(openapiName) {
30
32
  if (_openapiData[openapiName])
31
33
  return;
32
- // if the openapi file is not generated yet, we don't want to raise an
33
- // exception, so we will simply consider this file as undefined.
34
+ const openapiPath = openapiJsonPath(openapiName);
34
35
  try {
35
- const buffer = await fs.readFile(openapiJsonPath(openapiName));
36
+ const buffer = await fs.readFile(openapiPath);
36
37
  const openapiDoc = JSON.parse(buffer.toString());
37
38
  // we only cache the components from the openapi files,
38
39
  // since that is all that we currently need for validation,
@@ -42,8 +43,26 @@ export async function readAndCacheOpenapiFile(openapiName) {
42
43
  : FILE_DOES_NOT_EXIST;
43
44
  }
44
45
  catch {
46
+ if (EnvInternal.isProduction) {
47
+ throw new OpenapiFileNotFound(openapiName, openapiPath);
48
+ }
49
+ // if the openapi file is not generated yet and we are in our local
50
+ // environment, we don't want to raise an exception, since it could
51
+ // prevent someone from ever defining a new openapi configuration.
45
52
  _openapiData[openapiName] = FILE_DOES_NOT_EXIST;
46
- // noop
47
53
  }
48
54
  }
55
+ /**
56
+ * Reads the openapi file corresponding to the openapiName,
57
+ * and caches its contents, enabling them to be read back
58
+ * at a later time, such as during endpoint validation.
59
+ *
60
+ * This function is called during PsychicApp.init sequence automatically.
61
+ *
62
+ * @param openapiName - the openapiName you wish to look up
63
+ */
64
+ export function ignoreOpenapiFile(openapiName) {
65
+ _openapiData[openapiName] = FILE_WAS_IGNORED;
66
+ }
49
67
  const FILE_DOES_NOT_EXIST = 'FILE_DOES_NOT_EXIST';
68
+ const FILE_WAS_IGNORED = 'FILE_WAS_IGNORED';
@@ -0,0 +1,6 @@
1
+ export default class OpenapiFileNotFound extends Error {
2
+ private openapiName;
3
+ private openapiPath;
4
+ constructor(openapiName: string, openapiPath: string);
5
+ get message(): string;
6
+ }
@@ -149,7 +149,15 @@ export default class PsychicApp {
149
149
  *
150
150
  * @param openapiName - the openapiName you want to look up the openapi cache for
151
151
  */
152
- cacheOpenapiFile(openapiName: string): Promise<void>;
152
+ private cacheOpenapiFile;
153
+ /**
154
+ * indicates to the underlying cache that this openapi file is intentionally
155
+ * being ignored, so that future lookups for the file cache do not raise
156
+ * an exception
157
+ *
158
+ * @param openapiName - the openapiName to ignore
159
+ */
160
+ private ignoreOpenapiFile;
153
161
  /**
154
162
  * @internal
155
163
  *
@@ -26,3 +26,13 @@ export declare function getOpenapiFileOrFail(openapiName: string): OpenapiShell
26
26
  * @param openapiName - the openapiName you wish to look up
27
27
  */
28
28
  export declare function readAndCacheOpenapiFile(openapiName: string): Promise<void>;
29
+ /**
30
+ * Reads the openapi file corresponding to the openapiName,
31
+ * and caches its contents, enabling them to be read back
32
+ * at a later time, such as during endpoint validation.
33
+ *
34
+ * This function is called during PsychicApp.init sequence automatically.
35
+ *
36
+ * @param openapiName - the openapiName you wish to look up
37
+ */
38
+ export declare function ignoreOpenapiFile(openapiName: string): void;
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": "1.6.3",
5
+ "version": "1.6.4",
6
6
  "author": "RVOHealth",
7
7
  "repository": {
8
8
  "type": "git",