@rvoh/psychic 1.6.4 → 1.7.1
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 +10 -0
- package/dist/cjs/src/controller/helpers/renderDreamOrViewModel.js +22 -0
- package/dist/cjs/src/controller/index.js +33 -75
- package/dist/cjs/src/helpers/sanitizeString.js +28 -0
- package/dist/cjs/src/helpers/toJson.js +21 -0
- package/dist/cjs/src/index.js +8 -6
- package/dist/cjs/src/psychic-app/helpers/import/importControllers.js +1 -4
- package/dist/cjs/src/psychic-app/index.js +45 -44
- package/dist/cjs/src/psychic-app/openapi-cache.js +11 -53
- package/dist/cjs/src/router/index.js +9 -15
- package/dist/cjs/src/server/index.js +17 -21
- package/dist/esm/src/controller/helpers/renderDreamOrViewModel.js +19 -0
- package/dist/esm/src/controller/index.js +31 -72
- package/dist/esm/src/helpers/sanitizeString.js +25 -0
- package/dist/esm/src/helpers/toJson.js +15 -0
- package/dist/esm/src/index.js +3 -2
- package/dist/esm/src/psychic-app/helpers/import/importControllers.js +1 -4
- package/dist/esm/src/psychic-app/index.js +46 -45
- package/dist/esm/src/psychic-app/openapi-cache.js +8 -27
- package/dist/esm/src/router/index.js +9 -15
- package/dist/esm/src/server/index.js +17 -21
- package/dist/types/src/controller/helpers/renderDreamOrViewModel.d.ts +2 -0
- package/dist/types/src/controller/index.d.ts +3 -30
- package/dist/types/src/helpers/sanitizeString.d.ts +1 -0
- package/dist/types/src/helpers/toJson.d.ts +1 -0
- package/dist/types/src/index.d.ts +3 -2
- package/dist/types/src/psychic-app/index.d.ts +24 -27
- package/dist/types/src/psychic-app/openapi-cache.d.ts +4 -3
- package/dist/types/src/router/index.d.ts +2 -5
- package/dist/types/src/server/index.d.ts +1 -2
- package/package.json +2 -2
- package/dist/cjs/src/error/openapi/OpenapiFileNotFound.js +0 -27
- package/dist/esm/src/error/openapi/OpenapiFileNotFound.js +0 -24
- package/dist/types/src/error/openapi/OpenapiFileNotFound.d.ts +0 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 1.7.1
|
|
2
|
+
|
|
3
|
+
- compute openapi doc during intiialization, rather than problematically reading from a file cache
|
|
4
|
+
|
|
5
|
+
## 1.7.0
|
|
6
|
+
|
|
7
|
+
- `sanitizeResponseJson` config to automatically escape `<`, `>`, `&`, `/`, `\`, `'`, and `"` unicode representations when rendering json to satisfy security reviews (e.g., a pentest report recently called this out on one of our applications). For all practical purposes, this doesn't protect against anything (now that we have the `nosniff` header) since `JSON.parse` on the other end restores the original, dangerous string. Modern front end web frameworks already handle safely displaying arbitrary content, so further sanitization generally isn't needed. This version does provide the `sanitizeString` function that could be used to sanitize individual strings, replacing the above characters with string representations of the unicode characters that will survive Psychic converting to json and then parsing that json (i.e.: `<` will end up as the string "\u003c")
|
|
8
|
+
|
|
9
|
+
- Fix openapi serializer fallback issue introduced in 1.6.3, where we mistakenly double render data that has already been serialized.
|
|
10
|
+
|
|
1
11
|
## 1.6.4
|
|
2
12
|
|
|
3
13
|
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.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = renderDreamOrVewModel;
|
|
4
|
+
const dream_1 = require("@rvoh/dream");
|
|
5
|
+
function renderDreamOrVewModel(data, serializerKey,
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
passthrough, renderOpts) {
|
|
8
|
+
const serializer = (0, dream_1.inferSerializerFromDreamOrViewModel)(data, serializerKey);
|
|
9
|
+
if (serializer && (0, dream_1.isDreamSerializer)(serializer)) {
|
|
10
|
+
// passthrough data going into the serializer is the argument that gets
|
|
11
|
+
// used in the custom attribute callback function
|
|
12
|
+
return serializer(data, passthrough).render(
|
|
13
|
+
// passthrough data must be passed both into the serializer and render
|
|
14
|
+
// because, if the serializer does accept passthrough data, then passing it in is how
|
|
15
|
+
// it gets into the serializer, but if it does not accept passthrough data, and therefore
|
|
16
|
+
// does not pass it into the call to DreamSerializer/ObjectSerializer,
|
|
17
|
+
// then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
|
|
18
|
+
// handles passing its passthrough data into those
|
|
19
|
+
passthrough, renderOpts);
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`${serializer?.constructor?.name} is not a Dream serializer`);
|
|
22
|
+
}
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.PsychicParamsPrimitiveLiterals = void 0;
|
|
7
7
|
const dream_1 = require("@rvoh/dream");
|
|
8
8
|
const ParamValidationError_js_1 = __importDefault(require("../error/controller/ParamValidationError.js"));
|
|
9
9
|
const BadGateway_js_1 = __importDefault(require("../error/http/BadGateway.js"));
|
|
@@ -37,10 +37,13 @@ const Unauthorized_js_1 = __importDefault(require("../error/http/Unauthorized.js
|
|
|
37
37
|
const UnavailableForLegalReasons_js_1 = __importDefault(require("../error/http/UnavailableForLegalReasons.js"));
|
|
38
38
|
const UnprocessableContent_js_1 = __importDefault(require("../error/http/UnprocessableContent.js"));
|
|
39
39
|
const UnsupportedMediaType_js_1 = __importDefault(require("../error/http/UnsupportedMediaType.js"));
|
|
40
|
+
const toJson_js_1 = __importDefault(require("../helpers/toJson.js"));
|
|
40
41
|
const OpenapiPayloadValidator_js_1 = __importDefault(require("../openapi-renderer/helpers/OpenapiPayloadValidator.js"));
|
|
42
|
+
const index_js_1 = __importDefault(require("../psychic-app/index.js"));
|
|
41
43
|
const params_js_1 = __importDefault(require("../server/params.js"));
|
|
42
|
-
const
|
|
44
|
+
const index_js_2 = __importDefault(require("../session/index.js"));
|
|
43
45
|
const isPaginatedResult_js_1 = __importDefault(require("./helpers/isPaginatedResult.js"));
|
|
46
|
+
const renderDreamOrViewModel_js_1 = __importDefault(require("./helpers/renderDreamOrViewModel.js"));
|
|
44
47
|
exports.PsychicParamsPrimitiveLiterals = [
|
|
45
48
|
'bigint',
|
|
46
49
|
'bigint[]',
|
|
@@ -108,26 +111,6 @@ class PsychicController {
|
|
|
108
111
|
*/
|
|
109
112
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
113
|
static openapi;
|
|
111
|
-
/**
|
|
112
|
-
* Enables you to specify specific serializers to use
|
|
113
|
-
* when encountering specific models, i.e.
|
|
114
|
-
*
|
|
115
|
-
* ```ts
|
|
116
|
-
* class MyController extends AuthedController {
|
|
117
|
-
* static {
|
|
118
|
-
* this.serializes(User).with(UserCustomSerializer)
|
|
119
|
-
* }
|
|
120
|
-
* }
|
|
121
|
-
* ````
|
|
122
|
-
*/
|
|
123
|
-
static serializes(ModelClass) {
|
|
124
|
-
return {
|
|
125
|
-
with: (SerializerClass) => {
|
|
126
|
-
exports.controllerSerializerIndex.add(this, SerializerClass, ModelClass);
|
|
127
|
-
return this;
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
114
|
/**
|
|
132
115
|
* @internal
|
|
133
116
|
*
|
|
@@ -192,14 +175,12 @@ class PsychicController {
|
|
|
192
175
|
req;
|
|
193
176
|
res;
|
|
194
177
|
session;
|
|
195
|
-
config;
|
|
196
178
|
action;
|
|
197
179
|
renderOpts;
|
|
198
|
-
constructor(req, res, {
|
|
180
|
+
constructor(req, res, { action, }) {
|
|
199
181
|
this.req = req;
|
|
200
182
|
this.res = res;
|
|
201
|
-
this.
|
|
202
|
-
this.session = new index_js_1.default(req, res);
|
|
183
|
+
this.session = new index_js_2.default(req, res);
|
|
203
184
|
this.action = action;
|
|
204
185
|
// TODO: read casing from Dream app config
|
|
205
186
|
this.renderOpts = {
|
|
@@ -357,13 +338,13 @@ class PsychicController {
|
|
|
357
338
|
return this.session.setCookie(name, data, opts);
|
|
358
339
|
}
|
|
359
340
|
startSession(user) {
|
|
360
|
-
return this.setCookie(
|
|
341
|
+
return this.setCookie(index_js_1.default.getOrFail().sessionCookieName, JSON.stringify({
|
|
361
342
|
id: user.primaryKeyValue().toString(),
|
|
362
343
|
modelKey: user.constructor.globalName,
|
|
363
344
|
}));
|
|
364
345
|
}
|
|
365
346
|
endSession() {
|
|
366
|
-
return this.session.clearCookie(
|
|
347
|
+
return this.session.clearCookie(index_js_1.default.getOrFail().sessionCookieName);
|
|
367
348
|
}
|
|
368
349
|
singleObjectJson(data, opts) {
|
|
369
350
|
if (!data)
|
|
@@ -373,8 +354,6 @@ class PsychicController {
|
|
|
373
354
|
if (data instanceof dream_1.DreamSerializerBuilder || data instanceof dream_1.ObjectSerializerBuilder) {
|
|
374
355
|
return data.render(this.defaultSerializerPassthrough, this.renderOpts);
|
|
375
356
|
}
|
|
376
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
377
|
-
const lookup = exports.controllerSerializerIndex.lookupModel(this.constructor, data.constructor);
|
|
378
357
|
const openapiDef = this.constructor?.openapi?.[this.action];
|
|
379
358
|
// passthrough data must be passed both into the serializer and render
|
|
380
359
|
// because, if the serializer does accept passthrough data, then passing it in is how
|
|
@@ -383,48 +362,38 @@ class PsychicController {
|
|
|
383
362
|
// then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
|
|
384
363
|
// handles passing its passthrough data into those
|
|
385
364
|
const passthrough = this.defaultSerializerPassthrough;
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
// passthrough data going into the serializer is the argument that gets
|
|
390
|
-
// used in the custom attribute callback function
|
|
365
|
+
if (data instanceof dream_1.Dream || data.serializers) {
|
|
366
|
+
if (!opts.serializerKey && (0, dream_1.isDreamSerializer)(openapiDef?.dreamsOrSerializers)) {
|
|
367
|
+
const serializer = openapiDef.dreamsOrSerializers;
|
|
391
368
|
return serializer(data, passthrough).render(passthrough, this.renderOpts);
|
|
392
369
|
}
|
|
393
|
-
|
|
394
|
-
else if ((0, dream_1.isDreamSerializer)(openapiDef?.dreamsOrSerializers)) {
|
|
395
|
-
const serializer = openapiDef.dreamsOrSerializers;
|
|
396
|
-
return serializer(data, passthrough).render(passthrough, this.renderOpts);
|
|
397
|
-
}
|
|
398
|
-
else if (data instanceof dream_1.Dream || data.serializers) {
|
|
399
|
-
const serializer = (0, dream_1.inferSerializerFromDreamOrViewModel)(data, opts.serializerKey ||
|
|
370
|
+
return (0, renderDreamOrViewModel_js_1.default)(data, opts.serializerKey ||
|
|
400
371
|
psychicControllerClass['controllerActionMetadata'][this.action]?.['serializerKey'] ||
|
|
401
|
-
'default');
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
return serializer(data, passthrough).render(
|
|
406
|
-
// passthrough data must be passed both into the serializer and render
|
|
407
|
-
// because, if the serializer does accept passthrough data, then passing it in is how
|
|
408
|
-
// it gets into the serializer, but if it does not accept passthrough data, and therefore
|
|
409
|
-
// does not pass it into the call to DreamSerializer/ObjectSerializer,
|
|
410
|
-
// then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
|
|
411
|
-
// handles passing its passthrough data into those
|
|
412
|
-
passthrough, this.renderOpts);
|
|
413
|
-
}
|
|
372
|
+
'default', passthrough, this.renderOpts);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
return data;
|
|
414
376
|
}
|
|
415
|
-
return data;
|
|
416
377
|
}
|
|
417
378
|
json(data, opts = {}) {
|
|
418
|
-
|
|
419
|
-
|
|
379
|
+
return this.validateAndRenderJsonResponse(this._json(data, opts));
|
|
380
|
+
}
|
|
381
|
+
_json(data, opts = {}) {
|
|
382
|
+
if (Array.isArray(data)) {
|
|
420
383
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
421
|
-
this.singleObjectJson(d, opts))
|
|
422
|
-
|
|
423
|
-
|
|
384
|
+
return data.map(d => this.singleObjectJson(d, opts));
|
|
385
|
+
//
|
|
386
|
+
}
|
|
387
|
+
else if ((0, isPaginatedResult_js_1.default)(data)) {
|
|
388
|
+
return {
|
|
424
389
|
...data,
|
|
425
390
|
results: data.results.map(result => this.singleObjectJson(result, opts)),
|
|
426
|
-
}
|
|
427
|
-
|
|
391
|
+
};
|
|
392
|
+
//
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
return this.singleObjectJson(data, opts);
|
|
396
|
+
}
|
|
428
397
|
}
|
|
429
398
|
/**
|
|
430
399
|
* Runs the data through openapi response validation, and then renders
|
|
@@ -436,7 +405,7 @@ class PsychicController {
|
|
|
436
405
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
437
406
|
data) {
|
|
438
407
|
this.validateOpenapiResponseBody(data);
|
|
439
|
-
this.res.json(data);
|
|
408
|
+
this.res.type('json').send((0, toJson_js_1.default)(data, index_js_1.default.getOrFail().sanitizeResponseJson));
|
|
440
409
|
}
|
|
441
410
|
defaultSerializerPassthrough = {};
|
|
442
411
|
serializerPassthrough(passthrough) {
|
|
@@ -795,14 +764,3 @@ class PsychicController {
|
|
|
795
764
|
}
|
|
796
765
|
}
|
|
797
766
|
exports.default = PsychicController;
|
|
798
|
-
class ControllerSerializerIndex {
|
|
799
|
-
associations = [];
|
|
800
|
-
add(ControllerClass, SerializerClass, ModelClass) {
|
|
801
|
-
this.associations.push([ControllerClass, SerializerClass, ModelClass]);
|
|
802
|
-
}
|
|
803
|
-
lookupModel(ControllerClass, ModelClass) {
|
|
804
|
-
return this.associations.find(association => association[0] === ControllerClass && association[2] === ModelClass);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
exports.ControllerSerializerIndex = ControllerSerializerIndex;
|
|
808
|
-
exports.controllerSerializerIndex = new ControllerSerializerIndex();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = sanitizeString;
|
|
4
|
+
const CHARACTERS_TO_SANITIZE_REGEXP = /[\\&<>/'"]/g;
|
|
5
|
+
function sanitizeString(str) {
|
|
6
|
+
if (str === null || str === undefined)
|
|
7
|
+
return str;
|
|
8
|
+
return str.replace(CHARACTERS_TO_SANITIZE_REGEXP, function (char) {
|
|
9
|
+
switch (char) {
|
|
10
|
+
case '\\':
|
|
11
|
+
return '\\u005c';
|
|
12
|
+
case '/':
|
|
13
|
+
return '\\u002f';
|
|
14
|
+
case '<':
|
|
15
|
+
return '\\u003c';
|
|
16
|
+
case '>':
|
|
17
|
+
return '\\u003e';
|
|
18
|
+
case '&':
|
|
19
|
+
return '\\u0026';
|
|
20
|
+
case "'":
|
|
21
|
+
return '\\u0027';
|
|
22
|
+
case '"':
|
|
23
|
+
return '\\u0022';
|
|
24
|
+
default:
|
|
25
|
+
return char;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = toJson;
|
|
7
|
+
const sanitizeString_js_1 = __importDefault(require("./sanitizeString.js"));
|
|
8
|
+
function toJson(data, sanitize) {
|
|
9
|
+
/**
|
|
10
|
+
* 'undefined' is invalid json, and `JSON.stringify(undefined)` returns `undefined`
|
|
11
|
+
* so follow the pattern established by Express and return '{}' for `undefined`
|
|
12
|
+
*/
|
|
13
|
+
if (data === undefined)
|
|
14
|
+
return '{}';
|
|
15
|
+
if (sanitize) {
|
|
16
|
+
return JSON.stringify(data, (_, x) => (typeof x !== 'string' ? x : (0, sanitizeString_js_1.default)(x))).replace(/\\\\/g, '\\');
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
return JSON.stringify(data);
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/cjs/src/index.js
CHANGED
|
@@ -3,8 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
exports.PsychicSession = exports.
|
|
6
|
+
exports.PsychicRouter = exports.PsychicApp = exports.PsychicImporter = exports.MissingControllerActionPairingInRoutes = exports.ParamValidationErrors = exports.ParamValidationError = exports.sanitizeString = exports.pathifyNestedObject = exports.cookieMaxAgeFromCookieOpts = exports.generateResource = exports.generateController = exports.HttpStatusUnsupportedMediaType = exports.HttpStatusUnprocessableContent = exports.HttpStatusUnavailableForLegalReasons = exports.HttpStatusUnauthorized = exports.HttpStatusTooManyRequests = exports.HttpStatusServiceUnavailable = exports.HttpStatusRequestHeaderFieldsTooLarge = exports.HttpStatusProxyAuthenticationRequired = exports.HttpStatusPreconditionRequired = exports.HttpStatusPreconditionFailed = exports.HttpStatusPaymentRequired = exports.HttpStatusNotImplemented = exports.HttpStatusNotFound = exports.HttpStatusNotExtended = exports.HttpStatusNotAcceptable = exports.HttpStatusMisdirectedRequest = exports.HttpStatusMethodNotAllowed = exports.HttpStatusLocked = exports.HttpStatusInternalServerError = exports.HttpStatusInsufficientStorage = exports.HttpStatusImATeapot = exports.HttpStatusGone = exports.HttpStatusGatewayTimeout = exports.HttpStatusForbidden = exports.HttpStatusFailedDependency = exports.HttpStatusExpectationFailed = exports.HttpStatusContentTooLarge = exports.HttpStatusConflict = exports.HttpStatusBadRequest = exports.HttpStatusBadGateway = exports.I18nProvider = exports.envLoader = exports.PsychicDevtools = exports.PsychicController = exports.OpenAPI = exports.BeforeAction = exports.PsychicCLI = exports.PsychicBin = exports.pluralize = void 0;
|
|
7
|
+
exports.PsychicSession = exports.Params = exports.PsychicServer = exports.getPsychicHttpInstance = void 0;
|
|
8
8
|
const pluralize_esm_1 = __importDefault(require("pluralize-esm"));
|
|
9
9
|
exports.pluralize = pluralize_esm_1.default;
|
|
10
10
|
var index_js_1 = require("./bin/index.js");
|
|
@@ -90,6 +90,12 @@ var cookieMaxAgeFromCookieOpts_js_1 = require("./helpers/cookieMaxAgeFromCookieO
|
|
|
90
90
|
Object.defineProperty(exports, "cookieMaxAgeFromCookieOpts", { enumerable: true, get: function () { return __importDefault(cookieMaxAgeFromCookieOpts_js_1).default; } });
|
|
91
91
|
var pathifyNestedObject_js_1 = require("./helpers/pathifyNestedObject.js");
|
|
92
92
|
Object.defineProperty(exports, "pathifyNestedObject", { enumerable: true, get: function () { return __importDefault(pathifyNestedObject_js_1).default; } });
|
|
93
|
+
var sanitizeString_js_1 = require("./helpers/sanitizeString.js");
|
|
94
|
+
Object.defineProperty(exports, "sanitizeString", { enumerable: true, get: function () { return __importDefault(sanitizeString_js_1).default; } });
|
|
95
|
+
var ParamValidationError_js_1 = require("./error/controller/ParamValidationError.js");
|
|
96
|
+
Object.defineProperty(exports, "ParamValidationError", { enumerable: true, get: function () { return __importDefault(ParamValidationError_js_1).default; } });
|
|
97
|
+
var ParamValidationErrors_js_1 = require("./error/controller/ParamValidationErrors.js");
|
|
98
|
+
Object.defineProperty(exports, "ParamValidationErrors", { enumerable: true, get: function () { return __importDefault(ParamValidationErrors_js_1).default; } });
|
|
93
99
|
var endpoint_js_1 = require("./openapi-renderer/endpoint.js");
|
|
94
100
|
Object.defineProperty(exports, "MissingControllerActionPairingInRoutes", { enumerable: true, get: function () { return endpoint_js_1.MissingControllerActionPairingInRoutes; } });
|
|
95
101
|
var PsychicImporter_js_1 = require("./psychic-app/helpers/PsychicImporter.js");
|
|
@@ -104,9 +110,5 @@ var index_js_6 = require("./server/index.js");
|
|
|
104
110
|
Object.defineProperty(exports, "PsychicServer", { enumerable: true, get: function () { return __importDefault(index_js_6).default; } });
|
|
105
111
|
var params_js_1 = require("./server/params.js");
|
|
106
112
|
Object.defineProperty(exports, "Params", { enumerable: true, get: function () { return __importDefault(params_js_1).default; } });
|
|
107
|
-
var ParamValidationError_js_1 = require("./error/controller/ParamValidationError.js");
|
|
108
|
-
Object.defineProperty(exports, "ParamValidationError", { enumerable: true, get: function () { return __importDefault(ParamValidationError_js_1).default; } });
|
|
109
|
-
var ParamValidationErrors_js_1 = require("./error/controller/ParamValidationErrors.js");
|
|
110
|
-
Object.defineProperty(exports, "ParamValidationErrors", { enumerable: true, get: function () { return __importDefault(ParamValidationErrors_js_1).default; } });
|
|
111
113
|
var index_js_7 = require("./session/index.js");
|
|
112
114
|
Object.defineProperty(exports, "PsychicSession", { enumerable: true, get: function () { return __importDefault(index_js_7).default; } });
|
|
@@ -40,10 +40,7 @@ importCb) {
|
|
|
40
40
|
* at decoration time such that the class of a property being decorated is only avilable during instance instantiation. In order
|
|
41
41
|
* to only apply static values once, on boot, `globallyInitializingDecorators` is set to true on Dream, and all Dream models are instantiated.
|
|
42
42
|
*/
|
|
43
|
-
new controllerClass({}, {}, {
|
|
44
|
-
action: 'a',
|
|
45
|
-
config: psychicApp,
|
|
46
|
-
});
|
|
43
|
+
new controllerClass({}, {}, { action: 'a' });
|
|
47
44
|
}
|
|
48
45
|
}
|
|
49
46
|
index_js_1.default['globallyInitializingDecorators'] = false;
|
|
@@ -97,7 +97,10 @@ class PsychicApp {
|
|
|
97
97
|
dreamApp.set('logger', psychicApp.logger);
|
|
98
98
|
dreamApp.set('packageManager', psychicApp.packageManager);
|
|
99
99
|
(0, cache_js_1.cachePsychicApp)(psychicApp);
|
|
100
|
-
|
|
100
|
+
// routes _must_ be built before openapi
|
|
101
|
+
// cache can be processed
|
|
102
|
+
await psychicApp.buildRoutesCache();
|
|
103
|
+
psychicApp.buildOpenapiCache();
|
|
101
104
|
});
|
|
102
105
|
return psychicApp;
|
|
103
106
|
}
|
|
@@ -111,7 +114,7 @@ class PsychicApp {
|
|
|
111
114
|
async buildRoutesCache() {
|
|
112
115
|
if (this._routesCache)
|
|
113
116
|
return;
|
|
114
|
-
const r = new index_js_1.default(null
|
|
117
|
+
const r = new index_js_1.default(null);
|
|
115
118
|
await this.routesCb(r);
|
|
116
119
|
this._routesCache = r.routes;
|
|
117
120
|
}
|
|
@@ -128,20 +131,40 @@ class PsychicApp {
|
|
|
128
131
|
* request validation can look to the route cache
|
|
129
132
|
* instead of having to build it from scratch.
|
|
130
133
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Object.keys(this.openapi)
|
|
140
|
-
.filter(key => !this.openapi[key]?.validate)
|
|
141
|
-
.forEach(openapiName => {
|
|
142
|
-
this.ignoreOpenapiFile(openapiName);
|
|
134
|
+
buildOpenapiCache() {
|
|
135
|
+
Object.keys(this.openapi).forEach(openapiName => {
|
|
136
|
+
if (this.openapi[openapiName]?.validate) {
|
|
137
|
+
this.cacheOpenapiDoc(openapiName);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.ignoreOpenapiDoc(openapiName);
|
|
141
|
+
}
|
|
143
142
|
});
|
|
144
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* @internal
|
|
146
|
+
*
|
|
147
|
+
* When PsychicApp.init is called, a cache is built for each openapiName
|
|
148
|
+
* registered within your app config. For each openapiName, Psychic will
|
|
149
|
+
* read compute and cache the openapi document contents for that openapiName.
|
|
150
|
+
* It does this to enable the validation engine to read component
|
|
151
|
+
* schemas accross the entire document to perform individual endpoint validation.
|
|
152
|
+
*
|
|
153
|
+
* @param openapiName - the openapiName you want to look up the openapi cache for
|
|
154
|
+
*/
|
|
155
|
+
cacheOpenapiDoc(openapiName) {
|
|
156
|
+
(0, openapi_cache_js_1.cacheOpenapiDoc)(openapiName, this.routesCache);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* indicates to the underlying cache that this openapi file is intentionally
|
|
160
|
+
* being ignored, so that future lookups for the file cache do not raise
|
|
161
|
+
* an exception
|
|
162
|
+
*
|
|
163
|
+
* @param openapiName - the openapiName to ignore
|
|
164
|
+
*/
|
|
165
|
+
ignoreOpenapiDoc(openapiName) {
|
|
166
|
+
(0, openapi_cache_js_1.ignoreOpenapiDoc)(openapiName);
|
|
167
|
+
}
|
|
145
168
|
/**
|
|
146
169
|
* Since javascript is inherently vulnerable to circular dependencies,
|
|
147
170
|
* this function provides a workaround by enabling you to dynamically
|
|
@@ -273,6 +296,10 @@ Try setting it to something valid, like:
|
|
|
273
296
|
get saltRounds() {
|
|
274
297
|
return this._saltRounds;
|
|
275
298
|
}
|
|
299
|
+
_sanitizeResponseJson = false;
|
|
300
|
+
get sanitizeResponseJson() {
|
|
301
|
+
return this._sanitizeResponseJson;
|
|
302
|
+
}
|
|
276
303
|
_packageManager;
|
|
277
304
|
get packageManager() {
|
|
278
305
|
return this._packageManager;
|
|
@@ -285,35 +312,6 @@ Try setting it to something valid, like:
|
|
|
285
312
|
get routesCb() {
|
|
286
313
|
return this._routesCb;
|
|
287
314
|
}
|
|
288
|
-
/**
|
|
289
|
-
* @internal
|
|
290
|
-
*
|
|
291
|
-
* When PsychicApp.init is called, a cache is built for each openapiName
|
|
292
|
-
* registered within your app config. For each openapiName, Psychic will
|
|
293
|
-
* read the openapi file (unless it has not been written yet) and cache the
|
|
294
|
-
* contents. It does this to enable the validation engine to read component
|
|
295
|
-
* schemas accross the entire document to perform individual endpoint validation.
|
|
296
|
-
*
|
|
297
|
-
* This function is used to cache the contents of this file during initialization,
|
|
298
|
-
* so that the validation engine can call down to the complementing method,
|
|
299
|
-
* `getOpenapiFileOrFail`, which will return the contents of the document, or undefined
|
|
300
|
-
* if it was not found.
|
|
301
|
-
*
|
|
302
|
-
* @param openapiName - the openapiName you want to look up the openapi cache for
|
|
303
|
-
*/
|
|
304
|
-
async cacheOpenapiFile(openapiName) {
|
|
305
|
-
await (0, openapi_cache_js_1.readAndCacheOpenapiFile)(openapiName);
|
|
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
|
-
}
|
|
317
315
|
/**
|
|
318
316
|
* @internal
|
|
319
317
|
*
|
|
@@ -332,7 +330,7 @@ Try setting it to something valid, like:
|
|
|
332
330
|
* @returns the scanned openapi document, or undefined if it could not be found.
|
|
333
331
|
*/
|
|
334
332
|
getOpenapiFileOrFail(openapiName) {
|
|
335
|
-
return (0, openapi_cache_js_1.
|
|
333
|
+
return (0, openapi_cache_js_1.getCachedOpenapiDocOrFail)(openapiName);
|
|
336
334
|
}
|
|
337
335
|
/**
|
|
338
336
|
* @returns the entire openapi config, which includes configurations for each endpoint, indexed by their openapiNames
|
|
@@ -564,6 +562,9 @@ Try setting it to something valid, like:
|
|
|
564
562
|
case 'saltRounds':
|
|
565
563
|
this._saltRounds = value;
|
|
566
564
|
break;
|
|
565
|
+
case 'sanitizeResponseJson':
|
|
566
|
+
this._sanitizeResponseJson = value;
|
|
567
|
+
break;
|
|
567
568
|
case 'openapi':
|
|
568
569
|
this._openapi = {
|
|
569
570
|
...this.openapi,
|
|
@@ -1,39 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
4
|
};
|
|
28
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.
|
|
30
|
-
exports.
|
|
31
|
-
exports.
|
|
32
|
-
const fs = __importStar(require("node:fs/promises"));
|
|
33
|
-
const openapiJsonPath_js_1 = __importDefault(require("../helpers/openapiJsonPath.js"));
|
|
6
|
+
exports.getCachedOpenapiDocOrFail = getCachedOpenapiDocOrFail;
|
|
7
|
+
exports.cacheOpenapiDoc = cacheOpenapiDoc;
|
|
8
|
+
exports.ignoreOpenapiDoc = ignoreOpenapiDoc;
|
|
34
9
|
const must_call_psychic_app_init_first_js_1 = __importDefault(require("../error/psychic-app/must-call-psychic-app-init-first.js"));
|
|
35
|
-
const
|
|
36
|
-
const OpenapiFileNotFound_js_1 = __importDefault(require("../error/openapi/OpenapiFileNotFound.js"));
|
|
10
|
+
const app_js_1 = __importDefault(require("../openapi-renderer/app.js"));
|
|
37
11
|
const _openapiData = {};
|
|
38
12
|
/**
|
|
39
13
|
* Raises an exception if readAndCacheOpenapiFile was not called
|
|
@@ -42,7 +16,7 @@ const _openapiData = {};
|
|
|
42
16
|
* @param openapiName - the openapiName you wish to look up
|
|
43
17
|
* @returns the cached openapi file, or undefined if it was not found
|
|
44
18
|
*/
|
|
45
|
-
function
|
|
19
|
+
function getCachedOpenapiDocOrFail(openapiName) {
|
|
46
20
|
const val = _openapiData[openapiName];
|
|
47
21
|
if (!val)
|
|
48
22
|
throw new must_call_psychic_app_init_first_js_1.default();
|
|
@@ -59,29 +33,13 @@ function getOpenapiFileOrFail(openapiName) {
|
|
|
59
33
|
*
|
|
60
34
|
* @param openapiName - the openapiName you wish to look up
|
|
61
35
|
*/
|
|
62
|
-
|
|
36
|
+
function cacheOpenapiDoc(openapiName, routes) {
|
|
63
37
|
if (_openapiData[openapiName])
|
|
64
38
|
return;
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// we only cache the components from the openapi files,
|
|
70
|
-
// since that is all that we currently need for validation,
|
|
71
|
-
// which is the only thing leveraging these cached openapi files.
|
|
72
|
-
_openapiData[openapiName] = openapiDoc?.components
|
|
73
|
-
? { components: openapiDoc.components }
|
|
74
|
-
: FILE_DOES_NOT_EXIST;
|
|
75
|
-
}
|
|
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.
|
|
83
|
-
_openapiData[openapiName] = FILE_DOES_NOT_EXIST;
|
|
84
|
-
}
|
|
39
|
+
const openapiDoc = app_js_1.default._toObject(routes, openapiName);
|
|
40
|
+
_openapiData[openapiName] = openapiDoc?.components
|
|
41
|
+
? { components: openapiDoc.components }
|
|
42
|
+
: FILE_DOES_NOT_EXIST;
|
|
85
43
|
}
|
|
86
44
|
/**
|
|
87
45
|
* Reads the openapi file corresponding to the openapiName,
|
|
@@ -92,7 +50,7 @@ async function readAndCacheOpenapiFile(openapiName) {
|
|
|
92
50
|
*
|
|
93
51
|
* @param openapiName - the openapiName you wish to look up
|
|
94
52
|
*/
|
|
95
|
-
function
|
|
53
|
+
function ignoreOpenapiDoc(openapiName) {
|
|
96
54
|
_openapiData[openapiName] = FILE_WAS_IGNORED;
|
|
97
55
|
}
|
|
98
56
|
const FILE_DOES_NOT_EXIST = 'FILE_DOES_NOT_EXIST';
|
|
@@ -19,15 +19,10 @@ const route_manager_js_1 = __importDefault(require("./route-manager.js"));
|
|
|
19
19
|
const types_js_1 = require("./types.js");
|
|
20
20
|
class PsychicRouter {
|
|
21
21
|
app;
|
|
22
|
-
config;
|
|
23
22
|
currentNamespaces = [];
|
|
24
23
|
routeManager = new route_manager_js_1.default();
|
|
25
|
-
constructor(app
|
|
24
|
+
constructor(app) {
|
|
26
25
|
this.app = app;
|
|
27
|
-
this.config = config;
|
|
28
|
-
}
|
|
29
|
-
get routingMechanism() {
|
|
30
|
-
return this.app;
|
|
31
26
|
}
|
|
32
27
|
get routes() {
|
|
33
28
|
return this.routeManager.routes;
|
|
@@ -115,13 +110,13 @@ suggested fix: "${(0, helpers_js_1.convertRouteParams)(path)}"
|
|
|
115
110
|
`);
|
|
116
111
|
}
|
|
117
112
|
namespace(namespace, cb) {
|
|
118
|
-
const nestedRouter = new PsychicNestedRouter(this.app, this.
|
|
113
|
+
const nestedRouter = new PsychicNestedRouter(this.app, this.routeManager, {
|
|
119
114
|
namespaces: this.currentNamespaces,
|
|
120
115
|
});
|
|
121
116
|
this.runNestedCallbacks(namespace, nestedRouter, cb);
|
|
122
117
|
}
|
|
123
118
|
scope(scope, cb) {
|
|
124
|
-
const nestedRouter = new PsychicNestedRouter(this.app, this.
|
|
119
|
+
const nestedRouter = new PsychicNestedRouter(this.app, this.routeManager, {
|
|
125
120
|
namespaces: this.currentNamespaces,
|
|
126
121
|
});
|
|
127
122
|
this.runNestedCallbacks(scope, nestedRouter, cb, { treatNamespaceAsScope: true });
|
|
@@ -134,7 +129,7 @@ suggested fix: "${(0, helpers_js_1.convertRouteParams)(path)}"
|
|
|
134
129
|
}
|
|
135
130
|
collection(cb) {
|
|
136
131
|
const replacedNamespaces = this.currentNamespaces.slice(0, this.currentNamespaces.length - 1);
|
|
137
|
-
const nestedRouter = new PsychicNestedRouter(this.app, this.
|
|
132
|
+
const nestedRouter = new PsychicNestedRouter(this.app, this.routeManager, {
|
|
138
133
|
namespaces: replacedNamespaces,
|
|
139
134
|
});
|
|
140
135
|
const currentNamespace = replacedNamespaces[replacedNamespaces.length - 1];
|
|
@@ -156,7 +151,7 @@ suggested fix: "${(0, helpers_js_1.convertRouteParams)(path)}"
|
|
|
156
151
|
}
|
|
157
152
|
}
|
|
158
153
|
_makeResource(path, options, cb, plural) {
|
|
159
|
-
const nestedRouter = new PsychicNestedRouter(this.app, this.
|
|
154
|
+
const nestedRouter = new PsychicNestedRouter(this.app, this.routeManager, {
|
|
160
155
|
namespaces: this.currentNamespaces,
|
|
161
156
|
});
|
|
162
157
|
const { only, except } = options || {};
|
|
@@ -289,9 +284,9 @@ suggested fix: "${(0, helpers_js_1.convertRouteParams)(path)}"
|
|
|
289
284
|
index_js_1.default.log('ATTENTION: a server error was detected:');
|
|
290
285
|
index_js_1.default.logWithLevel('error', err);
|
|
291
286
|
}
|
|
292
|
-
if (
|
|
287
|
+
if (index_js_1.default.getOrFail().specialHooks.serverError.length) {
|
|
293
288
|
try {
|
|
294
|
-
for (const hook of
|
|
289
|
+
for (const hook of index_js_1.default.getOrFail().specialHooks.serverError) {
|
|
295
290
|
await hook(err, req, res);
|
|
296
291
|
}
|
|
297
292
|
}
|
|
@@ -321,7 +316,6 @@ suggested fix: "${(0, helpers_js_1.convertRouteParams)(path)}"
|
|
|
321
316
|
}
|
|
322
317
|
_initializeController(ControllerClass, req, res, action) {
|
|
323
318
|
return new ControllerClass(req, res, {
|
|
324
|
-
config: this.config,
|
|
325
319
|
action,
|
|
326
320
|
});
|
|
327
321
|
}
|
|
@@ -329,8 +323,8 @@ suggested fix: "${(0, helpers_js_1.convertRouteParams)(path)}"
|
|
|
329
323
|
exports.default = PsychicRouter;
|
|
330
324
|
class PsychicNestedRouter extends PsychicRouter {
|
|
331
325
|
router;
|
|
332
|
-
constructor(expressApp,
|
|
333
|
-
super(expressApp
|
|
326
|
+
constructor(expressApp, routeManager, { namespaces = [], } = {}) {
|
|
327
|
+
super(expressApp);
|
|
334
328
|
this.router = (0, express_1.Router)();
|
|
335
329
|
this.currentNamespaces = namespaces;
|
|
336
330
|
this.routeManager = routeManager;
|