@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 +4 -0
- package/dist/cjs/src/error/openapi/OpenapiFileNotFound.js +27 -0
- package/dist/cjs/src/openapi-renderer/helpers/OpenapiPayloadValidator.js +3 -3
- package/dist/cjs/src/psychic-app/index.js +20 -1
- package/dist/cjs/src/psychic-app/openapi-cache.js +25 -5
- package/dist/esm/src/error/openapi/OpenapiFileNotFound.js +24 -0
- package/dist/esm/src/openapi-renderer/helpers/OpenapiPayloadValidator.js +3 -3
- package/dist/esm/src/psychic-app/index.js +21 -2
- package/dist/esm/src/psychic-app/openapi-cache.js +24 -5
- package/dist/types/src/error/openapi/OpenapiFileNotFound.d.ts +6 -0
- package/dist/types/src/psychic-app/index.d.ts +9 -1
- package/dist/types/src/psychic-app/openapi-cache.d.ts +10 -0
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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';
|
|
@@ -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
|
|
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;
|