@rvoh/psychic 1.6.4 → 1.7.0
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 +6 -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 +8 -1
- 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 +8 -1
- 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 +4 -2
- 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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## 1.7.0
|
|
2
|
+
|
|
3
|
+
- `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")
|
|
4
|
+
|
|
5
|
+
- Fix openapi serializer fallback issue introduced in 1.6.3, where we mistakenly double render data that has already been serialized.
|
|
6
|
+
|
|
1
7
|
## 1.6.4
|
|
2
8
|
|
|
3
9
|
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;
|
|
@@ -111,7 +111,7 @@ class PsychicApp {
|
|
|
111
111
|
async buildRoutesCache() {
|
|
112
112
|
if (this._routesCache)
|
|
113
113
|
return;
|
|
114
|
-
const r = new index_js_1.default(null
|
|
114
|
+
const r = new index_js_1.default(null);
|
|
115
115
|
await this.routesCb(r);
|
|
116
116
|
this._routesCache = r.routes;
|
|
117
117
|
}
|
|
@@ -273,6 +273,10 @@ Try setting it to something valid, like:
|
|
|
273
273
|
get saltRounds() {
|
|
274
274
|
return this._saltRounds;
|
|
275
275
|
}
|
|
276
|
+
_sanitizeResponseJson = false;
|
|
277
|
+
get sanitizeResponseJson() {
|
|
278
|
+
return this._sanitizeResponseJson;
|
|
279
|
+
}
|
|
276
280
|
_packageManager;
|
|
277
281
|
get packageManager() {
|
|
278
282
|
return this._packageManager;
|
|
@@ -564,6 +568,9 @@ Try setting it to something valid, like:
|
|
|
564
568
|
case 'saltRounds':
|
|
565
569
|
this._saltRounds = value;
|
|
566
570
|
break;
|
|
571
|
+
case 'sanitizeResponseJson':
|
|
572
|
+
this._sanitizeResponseJson = value;
|
|
573
|
+
break;
|
|
567
574
|
case 'openapi':
|
|
568
575
|
this._openapi = {
|
|
569
576
|
...this.openapi,
|
|
@@ -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;
|
|
@@ -30,10 +30,10 @@ const dream_1 = require("@rvoh/dream");
|
|
|
30
30
|
const cookieParser = __importStar(require("cookie-parser"));
|
|
31
31
|
const cors = __importStar(require("cors"));
|
|
32
32
|
const express = __importStar(require("express"));
|
|
33
|
+
const EnvInternal_js_1 = __importDefault(require("../helpers/EnvInternal.js"));
|
|
33
34
|
const index_js_1 = __importDefault(require("../psychic-app/index.js"));
|
|
34
35
|
const index_js_2 = __importDefault(require("../router/index.js"));
|
|
35
36
|
const startPsychicServer_js_1 = __importStar(require("./helpers/startPsychicServer.js"));
|
|
36
|
-
const EnvInternal_js_1 = __importDefault(require("../helpers/EnvInternal.js"));
|
|
37
37
|
// const debugEnabled = debuglog('psychic').enabled
|
|
38
38
|
class PsychicServer {
|
|
39
39
|
static async startPsychicServer(opts) {
|
|
@@ -51,13 +51,9 @@ class PsychicServer {
|
|
|
51
51
|
constructor() {
|
|
52
52
|
this.buildApp();
|
|
53
53
|
}
|
|
54
|
-
get config() {
|
|
55
|
-
return index_js_1.default.getOrFail();
|
|
56
|
-
}
|
|
57
54
|
async routes() {
|
|
58
|
-
const r = new index_js_2.default(this.expressApp
|
|
59
|
-
|
|
60
|
-
await psychicApp.routesCb(r);
|
|
55
|
+
const r = new index_js_2.default(this.expressApp);
|
|
56
|
+
await index_js_1.default.getOrFail().routesCb(r);
|
|
61
57
|
return r.routes;
|
|
62
58
|
}
|
|
63
59
|
async boot() {
|
|
@@ -71,13 +67,14 @@ class PsychicServer {
|
|
|
71
67
|
});
|
|
72
68
|
next();
|
|
73
69
|
});
|
|
74
|
-
for (const serverInitBeforeMiddlewareHook of
|
|
70
|
+
for (const serverInitBeforeMiddlewareHook of index_js_1.default.getOrFail().specialHooks
|
|
71
|
+
.serverInitBeforeMiddleware) {
|
|
75
72
|
await serverInitBeforeMiddlewareHook(this);
|
|
76
73
|
}
|
|
77
74
|
this.initializeCors();
|
|
78
75
|
this.initializeJSON();
|
|
79
76
|
try {
|
|
80
|
-
await
|
|
77
|
+
await index_js_1.default.getOrFail().boot();
|
|
81
78
|
}
|
|
82
79
|
catch (err) {
|
|
83
80
|
const error = err;
|
|
@@ -87,11 +84,12 @@ class PsychicServer {
|
|
|
87
84
|
${error.message}
|
|
88
85
|
`);
|
|
89
86
|
}
|
|
90
|
-
for (const serverInitAfterMiddlewareHook of
|
|
87
|
+
for (const serverInitAfterMiddlewareHook of index_js_1.default.getOrFail().specialHooks
|
|
88
|
+
.serverInitAfterMiddleware) {
|
|
91
89
|
await serverInitAfterMiddlewareHook(this);
|
|
92
90
|
}
|
|
93
91
|
await this.buildRoutes();
|
|
94
|
-
for (const afterRoutesHook of
|
|
92
|
+
for (const afterRoutesHook of index_js_1.default.getOrFail().specialHooks.serverInitAfterRoutes) {
|
|
95
93
|
await afterRoutesHook(this);
|
|
96
94
|
}
|
|
97
95
|
this.booted = true;
|
|
@@ -119,7 +117,7 @@ class PsychicServer {
|
|
|
119
117
|
const httpServer = await (0, startPsychicServer_js_1.default)({
|
|
120
118
|
app: this.expressApp,
|
|
121
119
|
port: port || psychicApp.port,
|
|
122
|
-
sslCredentials:
|
|
120
|
+
sslCredentials: index_js_1.default.getOrFail().sslCredentials,
|
|
123
121
|
});
|
|
124
122
|
this.httpServer = httpServer;
|
|
125
123
|
}
|
|
@@ -146,8 +144,7 @@ class PsychicServer {
|
|
|
146
144
|
process.exit();
|
|
147
145
|
}
|
|
148
146
|
async stop({ bypassClosingDbConnections = false } = {}) {
|
|
149
|
-
const
|
|
150
|
-
for (const hook of psychicApp.specialHooks.serverShutdown) {
|
|
147
|
+
for (const hook of index_js_1.default.getOrFail().specialHooks.serverShutdown) {
|
|
151
148
|
await hook(this);
|
|
152
149
|
}
|
|
153
150
|
this.httpServer?.close();
|
|
@@ -156,8 +153,7 @@ class PsychicServer {
|
|
|
156
153
|
}
|
|
157
154
|
}
|
|
158
155
|
async serveForRequestSpecs(block) {
|
|
159
|
-
const
|
|
160
|
-
const port = psychicApp.port;
|
|
156
|
+
const port = index_js_1.default.getOrFail().port;
|
|
161
157
|
await this.boot();
|
|
162
158
|
let server;
|
|
163
159
|
await new Promise(accept => {
|
|
@@ -172,16 +168,16 @@ class PsychicServer {
|
|
|
172
168
|
this.expressApp.use(cookieParser.default());
|
|
173
169
|
}
|
|
174
170
|
initializeCors() {
|
|
171
|
+
this.expressApp.use(
|
|
175
172
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
176
|
-
|
|
173
|
+
cors.default(index_js_1.default.getOrFail().corsOptions));
|
|
177
174
|
}
|
|
178
175
|
initializeJSON() {
|
|
179
|
-
this.expressApp.use(express.json(
|
|
176
|
+
this.expressApp.use(express.json(index_js_1.default.getOrFail().jsonOptions));
|
|
180
177
|
}
|
|
181
178
|
async buildRoutes() {
|
|
182
|
-
const r = new index_js_2.default(this.expressApp
|
|
183
|
-
|
|
184
|
-
await psychicApp.routesCb(r);
|
|
179
|
+
const r = new index_js_2.default(this.expressApp);
|
|
180
|
+
await index_js_1.default.getOrFail().routesCb(r);
|
|
185
181
|
r.commit();
|
|
186
182
|
}
|
|
187
183
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { inferSerializerFromDreamOrViewModel, isDreamSerializer, } from '@rvoh/dream';
|
|
2
|
+
export default function renderDreamOrVewModel(data, serializerKey,
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
passthrough, renderOpts) {
|
|
5
|
+
const serializer = inferSerializerFromDreamOrViewModel(data, serializerKey);
|
|
6
|
+
if (serializer && isDreamSerializer(serializer)) {
|
|
7
|
+
// passthrough data going into the serializer is the argument that gets
|
|
8
|
+
// used in the custom attribute callback function
|
|
9
|
+
return serializer(data, passthrough).render(
|
|
10
|
+
// passthrough data must be passed both into the serializer and render
|
|
11
|
+
// because, if the serializer does accept passthrough data, then passing it in is how
|
|
12
|
+
// it gets into the serializer, but if it does not accept passthrough data, and therefore
|
|
13
|
+
// does not pass it into the call to DreamSerializer/ObjectSerializer,
|
|
14
|
+
// then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
|
|
15
|
+
// handles passing its passthrough data into those
|
|
16
|
+
passthrough, renderOpts);
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`${serializer?.constructor?.name} is not a Dream serializer`);
|
|
19
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Dream, DreamSerializerBuilder, GlobalNameNotSet,
|
|
1
|
+
import { Dream, 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';
|
|
@@ -31,10 +31,13 @@ import HttpStatusUnauthorized from '../error/http/Unauthorized.js';
|
|
|
31
31
|
import HttpStatusUnavailableForLegalReasons from '../error/http/UnavailableForLegalReasons.js';
|
|
32
32
|
import HttpStatusUnprocessableContent from '../error/http/UnprocessableContent.js';
|
|
33
33
|
import HttpStatusUnsupportedMediaType from '../error/http/UnsupportedMediaType.js';
|
|
34
|
+
import toJson from '../helpers/toJson.js';
|
|
34
35
|
import OpenapiPayloadValidator from '../openapi-renderer/helpers/OpenapiPayloadValidator.js';
|
|
36
|
+
import PsychicApp from '../psychic-app/index.js';
|
|
35
37
|
import Params from '../server/params.js';
|
|
36
38
|
import Session from '../session/index.js';
|
|
37
39
|
import isPaginatedResult from './helpers/isPaginatedResult.js';
|
|
40
|
+
import renderDreamOrVewModel from './helpers/renderDreamOrViewModel.js';
|
|
38
41
|
export const PsychicParamsPrimitiveLiterals = [
|
|
39
42
|
'bigint',
|
|
40
43
|
'bigint[]',
|
|
@@ -102,26 +105,6 @@ export default class PsychicController {
|
|
|
102
105
|
*/
|
|
103
106
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
107
|
static openapi;
|
|
105
|
-
/**
|
|
106
|
-
* Enables you to specify specific serializers to use
|
|
107
|
-
* when encountering specific models, i.e.
|
|
108
|
-
*
|
|
109
|
-
* ```ts
|
|
110
|
-
* class MyController extends AuthedController {
|
|
111
|
-
* static {
|
|
112
|
-
* this.serializes(User).with(UserCustomSerializer)
|
|
113
|
-
* }
|
|
114
|
-
* }
|
|
115
|
-
* ````
|
|
116
|
-
*/
|
|
117
|
-
static serializes(ModelClass) {
|
|
118
|
-
return {
|
|
119
|
-
with: (SerializerClass) => {
|
|
120
|
-
controllerSerializerIndex.add(this, SerializerClass, ModelClass);
|
|
121
|
-
return this;
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
108
|
/**
|
|
126
109
|
* @internal
|
|
127
110
|
*
|
|
@@ -186,13 +169,11 @@ export default class PsychicController {
|
|
|
186
169
|
req;
|
|
187
170
|
res;
|
|
188
171
|
session;
|
|
189
|
-
config;
|
|
190
172
|
action;
|
|
191
173
|
renderOpts;
|
|
192
|
-
constructor(req, res, {
|
|
174
|
+
constructor(req, res, { action, }) {
|
|
193
175
|
this.req = req;
|
|
194
176
|
this.res = res;
|
|
195
|
-
this.config = config;
|
|
196
177
|
this.session = new Session(req, res);
|
|
197
178
|
this.action = action;
|
|
198
179
|
// TODO: read casing from Dream app config
|
|
@@ -351,13 +332,13 @@ export default class PsychicController {
|
|
|
351
332
|
return this.session.setCookie(name, data, opts);
|
|
352
333
|
}
|
|
353
334
|
startSession(user) {
|
|
354
|
-
return this.setCookie(
|
|
335
|
+
return this.setCookie(PsychicApp.getOrFail().sessionCookieName, JSON.stringify({
|
|
355
336
|
id: user.primaryKeyValue().toString(),
|
|
356
337
|
modelKey: user.constructor.globalName,
|
|
357
338
|
}));
|
|
358
339
|
}
|
|
359
340
|
endSession() {
|
|
360
|
-
return this.session.clearCookie(
|
|
341
|
+
return this.session.clearCookie(PsychicApp.getOrFail().sessionCookieName);
|
|
361
342
|
}
|
|
362
343
|
singleObjectJson(data, opts) {
|
|
363
344
|
if (!data)
|
|
@@ -367,8 +348,6 @@ export default class PsychicController {
|
|
|
367
348
|
if (data instanceof DreamSerializerBuilder || data instanceof ObjectSerializerBuilder) {
|
|
368
349
|
return data.render(this.defaultSerializerPassthrough, this.renderOpts);
|
|
369
350
|
}
|
|
370
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
371
|
-
const lookup = controllerSerializerIndex.lookupModel(this.constructor, data.constructor);
|
|
372
351
|
const openapiDef = this.constructor?.openapi?.[this.action];
|
|
373
352
|
// passthrough data must be passed both into the serializer and render
|
|
374
353
|
// because, if the serializer does accept passthrough data, then passing it in is how
|
|
@@ -377,48 +356,38 @@ export default class PsychicController {
|
|
|
377
356
|
// then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
|
|
378
357
|
// handles passing its passthrough data into those
|
|
379
358
|
const passthrough = this.defaultSerializerPassthrough;
|
|
380
|
-
if (
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// passthrough data going into the serializer is the argument that gets
|
|
384
|
-
// used in the custom attribute callback function
|
|
359
|
+
if (data instanceof Dream || data.serializers) {
|
|
360
|
+
if (!opts.serializerKey && isDreamSerializer(openapiDef?.dreamsOrSerializers)) {
|
|
361
|
+
const serializer = openapiDef.dreamsOrSerializers;
|
|
385
362
|
return serializer(data, passthrough).render(passthrough, this.renderOpts);
|
|
386
363
|
}
|
|
387
|
-
|
|
388
|
-
else if (isDreamSerializer(openapiDef?.dreamsOrSerializers)) {
|
|
389
|
-
const serializer = openapiDef.dreamsOrSerializers;
|
|
390
|
-
return serializer(data, passthrough).render(passthrough, this.renderOpts);
|
|
391
|
-
}
|
|
392
|
-
else if (data instanceof Dream || data.serializers) {
|
|
393
|
-
const serializer = inferSerializerFromDreamOrViewModel(data, opts.serializerKey ||
|
|
364
|
+
return renderDreamOrVewModel(data, opts.serializerKey ||
|
|
394
365
|
psychicControllerClass['controllerActionMetadata'][this.action]?.['serializerKey'] ||
|
|
395
|
-
'default');
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return serializer(data, passthrough).render(
|
|
400
|
-
// passthrough data must be passed both into the serializer and render
|
|
401
|
-
// because, if the serializer does accept passthrough data, then passing it in is how
|
|
402
|
-
// it gets into the serializer, but if it does not accept passthrough data, and therefore
|
|
403
|
-
// does not pass it into the call to DreamSerializer/ObjectSerializer,
|
|
404
|
-
// then it would be lost to serializers rendered via rendersOne/Many, and SerializerRenderer
|
|
405
|
-
// handles passing its passthrough data into those
|
|
406
|
-
passthrough, this.renderOpts);
|
|
407
|
-
}
|
|
366
|
+
'default', passthrough, this.renderOpts);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
return data;
|
|
408
370
|
}
|
|
409
|
-
return data;
|
|
410
371
|
}
|
|
411
372
|
json(data, opts = {}) {
|
|
412
|
-
|
|
413
|
-
|
|
373
|
+
return this.validateAndRenderJsonResponse(this._json(data, opts));
|
|
374
|
+
}
|
|
375
|
+
_json(data, opts = {}) {
|
|
376
|
+
if (Array.isArray(data)) {
|
|
414
377
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
415
|
-
this.singleObjectJson(d, opts))
|
|
416
|
-
|
|
417
|
-
|
|
378
|
+
return data.map(d => this.singleObjectJson(d, opts));
|
|
379
|
+
//
|
|
380
|
+
}
|
|
381
|
+
else if (isPaginatedResult(data)) {
|
|
382
|
+
return {
|
|
418
383
|
...data,
|
|
419
384
|
results: data.results.map(result => this.singleObjectJson(result, opts)),
|
|
420
|
-
}
|
|
421
|
-
|
|
385
|
+
};
|
|
386
|
+
//
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
return this.singleObjectJson(data, opts);
|
|
390
|
+
}
|
|
422
391
|
}
|
|
423
392
|
/**
|
|
424
393
|
* Runs the data through openapi response validation, and then renders
|
|
@@ -430,7 +399,7 @@ export default class PsychicController {
|
|
|
430
399
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
431
400
|
data) {
|
|
432
401
|
this.validateOpenapiResponseBody(data);
|
|
433
|
-
this.res.json(data);
|
|
402
|
+
this.res.type('json').send(toJson(data, PsychicApp.getOrFail().sanitizeResponseJson));
|
|
434
403
|
}
|
|
435
404
|
defaultSerializerPassthrough = {};
|
|
436
405
|
serializerPassthrough(passthrough) {
|
|
@@ -788,13 +757,3 @@ export default class PsychicController {
|
|
|
788
757
|
}
|
|
789
758
|
}
|
|
790
759
|
}
|
|
791
|
-
export class ControllerSerializerIndex {
|
|
792
|
-
associations = [];
|
|
793
|
-
add(ControllerClass, SerializerClass, ModelClass) {
|
|
794
|
-
this.associations.push([ControllerClass, SerializerClass, ModelClass]);
|
|
795
|
-
}
|
|
796
|
-
lookupModel(ControllerClass, ModelClass) {
|
|
797
|
-
return this.associations.find(association => association[0] === ControllerClass && association[2] === ModelClass);
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
export const controllerSerializerIndex = new ControllerSerializerIndex();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const CHARACTERS_TO_SANITIZE_REGEXP = /[\\&<>/'"]/g;
|
|
2
|
+
export default function sanitizeString(str) {
|
|
3
|
+
if (str === null || str === undefined)
|
|
4
|
+
return str;
|
|
5
|
+
return str.replace(CHARACTERS_TO_SANITIZE_REGEXP, function (char) {
|
|
6
|
+
switch (char) {
|
|
7
|
+
case '\\':
|
|
8
|
+
return '\\u005c';
|
|
9
|
+
case '/':
|
|
10
|
+
return '\\u002f';
|
|
11
|
+
case '<':
|
|
12
|
+
return '\\u003c';
|
|
13
|
+
case '>':
|
|
14
|
+
return '\\u003e';
|
|
15
|
+
case '&':
|
|
16
|
+
return '\\u0026';
|
|
17
|
+
case "'":
|
|
18
|
+
return '\\u0027';
|
|
19
|
+
case '"':
|
|
20
|
+
return '\\u0022';
|
|
21
|
+
default:
|
|
22
|
+
return char;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import sanitizeString from './sanitizeString.js';
|
|
2
|
+
export default function toJson(data, sanitize) {
|
|
3
|
+
/**
|
|
4
|
+
* 'undefined' is invalid json, and `JSON.stringify(undefined)` returns `undefined`
|
|
5
|
+
* so follow the pattern established by Express and return '{}' for `undefined`
|
|
6
|
+
*/
|
|
7
|
+
if (data === undefined)
|
|
8
|
+
return '{}';
|
|
9
|
+
if (sanitize) {
|
|
10
|
+
return JSON.stringify(data, (_, x) => (typeof x !== 'string' ? x : sanitizeString(x))).replace(/\\\\/g, '\\');
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
return JSON.stringify(data);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/dist/esm/src/index.js
CHANGED
|
@@ -41,6 +41,9 @@ export { default as generateController } from './generate/controller.js';
|
|
|
41
41
|
export { default as generateResource } from './generate/resource.js';
|
|
42
42
|
export { default as cookieMaxAgeFromCookieOpts } from './helpers/cookieMaxAgeFromCookieOpts.js';
|
|
43
43
|
export { default as pathifyNestedObject } from './helpers/pathifyNestedObject.js';
|
|
44
|
+
export { default as sanitizeString } from './helpers/sanitizeString.js';
|
|
45
|
+
export { default as ParamValidationError } from './error/controller/ParamValidationError.js';
|
|
46
|
+
export { default as ParamValidationErrors } from './error/controller/ParamValidationErrors.js';
|
|
44
47
|
export { MissingControllerActionPairingInRoutes, } from './openapi-renderer/endpoint.js';
|
|
45
48
|
export { default as PsychicImporter } from './psychic-app/helpers/PsychicImporter.js';
|
|
46
49
|
export { default as PsychicApp, } from './psychic-app/index.js';
|
|
@@ -48,6 +51,4 @@ export { default as PsychicRouter } from './router/index.js';
|
|
|
48
51
|
export { createPsychicHttpInstance as getPsychicHttpInstance } from './server/helpers/startPsychicServer.js';
|
|
49
52
|
export { default as PsychicServer } from './server/index.js';
|
|
50
53
|
export { default as Params } from './server/params.js';
|
|
51
|
-
export { default as ParamValidationError } from './error/controller/ParamValidationError.js';
|
|
52
|
-
export { default as ParamValidationErrors } from './error/controller/ParamValidationErrors.js';
|
|
53
54
|
export { default as PsychicSession } from './session/index.js';
|
|
@@ -32,10 +32,7 @@ importCb) {
|
|
|
32
32
|
* at decoration time such that the class of a property being decorated is only avilable during instance instantiation. In order
|
|
33
33
|
* to only apply static values once, on boot, `globallyInitializingDecorators` is set to true on Dream, and all Dream models are instantiated.
|
|
34
34
|
*/
|
|
35
|
-
new controllerClass({}, {}, {
|
|
36
|
-
action: 'a',
|
|
37
|
-
config: psychicApp,
|
|
38
|
-
});
|
|
35
|
+
new controllerClass({}, {}, { action: 'a' });
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
38
|
PsychicController['globallyInitializingDecorators'] = false;
|
|
@@ -82,7 +82,7 @@ export default class PsychicApp {
|
|
|
82
82
|
async buildRoutesCache() {
|
|
83
83
|
if (this._routesCache)
|
|
84
84
|
return;
|
|
85
|
-
const r = new PsychicRouter(null
|
|
85
|
+
const r = new PsychicRouter(null);
|
|
86
86
|
await this.routesCb(r);
|
|
87
87
|
this._routesCache = r.routes;
|
|
88
88
|
}
|
|
@@ -244,6 +244,10 @@ Try setting it to something valid, like:
|
|
|
244
244
|
get saltRounds() {
|
|
245
245
|
return this._saltRounds;
|
|
246
246
|
}
|
|
247
|
+
_sanitizeResponseJson = false;
|
|
248
|
+
get sanitizeResponseJson() {
|
|
249
|
+
return this._sanitizeResponseJson;
|
|
250
|
+
}
|
|
247
251
|
_packageManager;
|
|
248
252
|
get packageManager() {
|
|
249
253
|
return this._packageManager;
|
|
@@ -535,6 +539,9 @@ Try setting it to something valid, like:
|
|
|
535
539
|
case 'saltRounds':
|
|
536
540
|
this._saltRounds = value;
|
|
537
541
|
break;
|
|
542
|
+
case 'sanitizeResponseJson':
|
|
543
|
+
this._sanitizeResponseJson = value;
|
|
544
|
+
break;
|
|
538
545
|
case 'openapi':
|
|
539
546
|
this._openapi = {
|
|
540
547
|
...this.openapi,
|
|
@@ -13,15 +13,10 @@ import RouteManager from './route-manager.js';
|
|
|
13
13
|
import { ResourceMethods, ResourcesMethods, } from './types.js';
|
|
14
14
|
export default class PsychicRouter {
|
|
15
15
|
app;
|
|
16
|
-
config;
|
|
17
16
|
currentNamespaces = [];
|
|
18
17
|
routeManager = new RouteManager();
|
|
19
|
-
constructor(app
|
|
18
|
+
constructor(app) {
|
|
20
19
|
this.app = app;
|
|
21
|
-
this.config = config;
|
|
22
|
-
}
|
|
23
|
-
get routingMechanism() {
|
|
24
|
-
return this.app;
|
|
25
20
|
}
|
|
26
21
|
get routes() {
|
|
27
22
|
return this.routeManager.routes;
|
|
@@ -109,13 +104,13 @@ suggested fix: "${convertRouteParams(path)}"
|
|
|
109
104
|
`);
|
|
110
105
|
}
|
|
111
106
|
namespace(namespace, cb) {
|
|
112
|
-
const nestedRouter = new PsychicNestedRouter(this.app, this.
|
|
107
|
+
const nestedRouter = new PsychicNestedRouter(this.app, this.routeManager, {
|
|
113
108
|
namespaces: this.currentNamespaces,
|
|
114
109
|
});
|
|
115
110
|
this.runNestedCallbacks(namespace, nestedRouter, cb);
|
|
116
111
|
}
|
|
117
112
|
scope(scope, 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(scope, nestedRouter, cb, { treatNamespaceAsScope: true });
|
|
@@ -128,7 +123,7 @@ suggested fix: "${convertRouteParams(path)}"
|
|
|
128
123
|
}
|
|
129
124
|
collection(cb) {
|
|
130
125
|
const replacedNamespaces = this.currentNamespaces.slice(0, this.currentNamespaces.length - 1);
|
|
131
|
-
const nestedRouter = new PsychicNestedRouter(this.app, this.
|
|
126
|
+
const nestedRouter = new PsychicNestedRouter(this.app, this.routeManager, {
|
|
132
127
|
namespaces: replacedNamespaces,
|
|
133
128
|
});
|
|
134
129
|
const currentNamespace = replacedNamespaces[replacedNamespaces.length - 1];
|
|
@@ -150,7 +145,7 @@ suggested fix: "${convertRouteParams(path)}"
|
|
|
150
145
|
}
|
|
151
146
|
}
|
|
152
147
|
_makeResource(path, options, cb, plural) {
|
|
153
|
-
const nestedRouter = new PsychicNestedRouter(this.app, this.
|
|
148
|
+
const nestedRouter = new PsychicNestedRouter(this.app, this.routeManager, {
|
|
154
149
|
namespaces: this.currentNamespaces,
|
|
155
150
|
});
|
|
156
151
|
const { only, except } = options || {};
|
|
@@ -283,9 +278,9 @@ suggested fix: "${convertRouteParams(path)}"
|
|
|
283
278
|
PsychicApp.log('ATTENTION: a server error was detected:');
|
|
284
279
|
PsychicApp.logWithLevel('error', err);
|
|
285
280
|
}
|
|
286
|
-
if (
|
|
281
|
+
if (PsychicApp.getOrFail().specialHooks.serverError.length) {
|
|
287
282
|
try {
|
|
288
|
-
for (const hook of
|
|
283
|
+
for (const hook of PsychicApp.getOrFail().specialHooks.serverError) {
|
|
289
284
|
await hook(err, req, res);
|
|
290
285
|
}
|
|
291
286
|
}
|
|
@@ -315,15 +310,14 @@ suggested fix: "${convertRouteParams(path)}"
|
|
|
315
310
|
}
|
|
316
311
|
_initializeController(ControllerClass, req, res, action) {
|
|
317
312
|
return new ControllerClass(req, res, {
|
|
318
|
-
config: this.config,
|
|
319
313
|
action,
|
|
320
314
|
});
|
|
321
315
|
}
|
|
322
316
|
}
|
|
323
317
|
export class PsychicNestedRouter extends PsychicRouter {
|
|
324
318
|
router;
|
|
325
|
-
constructor(expressApp,
|
|
326
|
-
super(expressApp
|
|
319
|
+
constructor(expressApp, routeManager, { namespaces = [], } = {}) {
|
|
320
|
+
super(expressApp);
|
|
327
321
|
this.router = Router();
|
|
328
322
|
this.currentNamespaces = namespaces;
|
|
329
323
|
this.routeManager = routeManager;
|
|
@@ -2,10 +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 EnvInternal from '../helpers/EnvInternal.js';
|
|
5
6
|
import PsychicApp from '../psychic-app/index.js';
|
|
6
7
|
import PsychicRouter from '../router/index.js';
|
|
7
8
|
import startPsychicServer, { createPsychicHttpInstance, } from './helpers/startPsychicServer.js';
|
|
8
|
-
import EnvInternal from '../helpers/EnvInternal.js';
|
|
9
9
|
// const debugEnabled = debuglog('psychic').enabled
|
|
10
10
|
export default class PsychicServer {
|
|
11
11
|
static async startPsychicServer(opts) {
|
|
@@ -23,13 +23,9 @@ export default class PsychicServer {
|
|
|
23
23
|
constructor() {
|
|
24
24
|
this.buildApp();
|
|
25
25
|
}
|
|
26
|
-
get config() {
|
|
27
|
-
return PsychicApp.getOrFail();
|
|
28
|
-
}
|
|
29
26
|
async routes() {
|
|
30
|
-
const r = new PsychicRouter(this.expressApp
|
|
31
|
-
|
|
32
|
-
await psychicApp.routesCb(r);
|
|
27
|
+
const r = new PsychicRouter(this.expressApp);
|
|
28
|
+
await PsychicApp.getOrFail().routesCb(r);
|
|
33
29
|
return r.routes;
|
|
34
30
|
}
|
|
35
31
|
async boot() {
|
|
@@ -43,13 +39,14 @@ export default class PsychicServer {
|
|
|
43
39
|
});
|
|
44
40
|
next();
|
|
45
41
|
});
|
|
46
|
-
for (const serverInitBeforeMiddlewareHook of
|
|
42
|
+
for (const serverInitBeforeMiddlewareHook of PsychicApp.getOrFail().specialHooks
|
|
43
|
+
.serverInitBeforeMiddleware) {
|
|
47
44
|
await serverInitBeforeMiddlewareHook(this);
|
|
48
45
|
}
|
|
49
46
|
this.initializeCors();
|
|
50
47
|
this.initializeJSON();
|
|
51
48
|
try {
|
|
52
|
-
await
|
|
49
|
+
await PsychicApp.getOrFail().boot();
|
|
53
50
|
}
|
|
54
51
|
catch (err) {
|
|
55
52
|
const error = err;
|
|
@@ -59,11 +56,12 @@ export default class PsychicServer {
|
|
|
59
56
|
${error.message}
|
|
60
57
|
`);
|
|
61
58
|
}
|
|
62
|
-
for (const serverInitAfterMiddlewareHook of
|
|
59
|
+
for (const serverInitAfterMiddlewareHook of PsychicApp.getOrFail().specialHooks
|
|
60
|
+
.serverInitAfterMiddleware) {
|
|
63
61
|
await serverInitAfterMiddlewareHook(this);
|
|
64
62
|
}
|
|
65
63
|
await this.buildRoutes();
|
|
66
|
-
for (const afterRoutesHook of
|
|
64
|
+
for (const afterRoutesHook of PsychicApp.getOrFail().specialHooks.serverInitAfterRoutes) {
|
|
67
65
|
await afterRoutesHook(this);
|
|
68
66
|
}
|
|
69
67
|
this.booted = true;
|
|
@@ -91,7 +89,7 @@ export default class PsychicServer {
|
|
|
91
89
|
const httpServer = await startPsychicServer({
|
|
92
90
|
app: this.expressApp,
|
|
93
91
|
port: port || psychicApp.port,
|
|
94
|
-
sslCredentials:
|
|
92
|
+
sslCredentials: PsychicApp.getOrFail().sslCredentials,
|
|
95
93
|
});
|
|
96
94
|
this.httpServer = httpServer;
|
|
97
95
|
}
|
|
@@ -118,8 +116,7 @@ export default class PsychicServer {
|
|
|
118
116
|
process.exit();
|
|
119
117
|
}
|
|
120
118
|
async stop({ bypassClosingDbConnections = false } = {}) {
|
|
121
|
-
const
|
|
122
|
-
for (const hook of psychicApp.specialHooks.serverShutdown) {
|
|
119
|
+
for (const hook of PsychicApp.getOrFail().specialHooks.serverShutdown) {
|
|
123
120
|
await hook(this);
|
|
124
121
|
}
|
|
125
122
|
this.httpServer?.close();
|
|
@@ -128,8 +125,7 @@ export default class PsychicServer {
|
|
|
128
125
|
}
|
|
129
126
|
}
|
|
130
127
|
async serveForRequestSpecs(block) {
|
|
131
|
-
const
|
|
132
|
-
const port = psychicApp.port;
|
|
128
|
+
const port = PsychicApp.getOrFail().port;
|
|
133
129
|
await this.boot();
|
|
134
130
|
let server;
|
|
135
131
|
await new Promise(accept => {
|
|
@@ -144,16 +140,16 @@ export default class PsychicServer {
|
|
|
144
140
|
this.expressApp.use(cookieParser.default());
|
|
145
141
|
}
|
|
146
142
|
initializeCors() {
|
|
143
|
+
this.expressApp.use(
|
|
147
144
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
|
-
|
|
145
|
+
cors.default(PsychicApp.getOrFail().corsOptions));
|
|
149
146
|
}
|
|
150
147
|
initializeJSON() {
|
|
151
|
-
this.expressApp.use(express.json(
|
|
148
|
+
this.expressApp.use(express.json(PsychicApp.getOrFail().jsonOptions));
|
|
152
149
|
}
|
|
153
150
|
async buildRoutes() {
|
|
154
|
-
const r = new PsychicRouter(this.expressApp
|
|
155
|
-
|
|
156
|
-
await psychicApp.routesCb(r);
|
|
151
|
+
const r = new PsychicRouter(this.expressApp);
|
|
152
|
+
await PsychicApp.getOrFail().routesCb(r);
|
|
157
153
|
r.commit();
|
|
158
154
|
}
|
|
159
155
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Dream,
|
|
1
|
+
import { Dream, DreamParamSafeAttributes, DreamParamSafeColumnNames, OpenapiSchemaBody, SerializerRendererOpts, UpdateableProperties } 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';
|
|
5
5
|
import OpenapiEndpointRenderer from '../openapi-renderer/endpoint.js';
|
|
6
|
-
import PsychicApp from '../psychic-app/index.js';
|
|
7
6
|
import { ParamsCastOptions, ParamsForOpts, ValidatedAllowsNull, ValidatedReturnType } from '../server/params.js';
|
|
8
7
|
import Session, { CustomSessionCookieOptions } from '../session/index.js';
|
|
9
8
|
type SerializerResult = {
|
|
@@ -58,21 +57,6 @@ export default class PsychicController {
|
|
|
58
57
|
* by using the `@Openapi` decorator on your controller methods
|
|
59
58
|
*/
|
|
60
59
|
static openapi: Record<string, OpenapiEndpointRenderer<any, any>>;
|
|
61
|
-
/**
|
|
62
|
-
* Enables you to specify specific serializers to use
|
|
63
|
-
* when encountering specific models, i.e.
|
|
64
|
-
*
|
|
65
|
-
* ```ts
|
|
66
|
-
* class MyController extends AuthedController {
|
|
67
|
-
* static {
|
|
68
|
-
* this.serializes(User).with(UserCustomSerializer)
|
|
69
|
-
* }
|
|
70
|
-
* }
|
|
71
|
-
* ````
|
|
72
|
-
*/
|
|
73
|
-
static serializes(ModelClass: typeof Dream): {
|
|
74
|
-
with: (SerializerClass: DreamModelSerializerType | SimpleObjectSerializerType) => typeof PsychicController;
|
|
75
|
-
};
|
|
76
60
|
/**
|
|
77
61
|
* @internal
|
|
78
62
|
*
|
|
@@ -118,11 +102,9 @@ export default class PsychicController {
|
|
|
118
102
|
req: Request;
|
|
119
103
|
res: Response;
|
|
120
104
|
session: Session;
|
|
121
|
-
config: PsychicApp;
|
|
122
105
|
action: string;
|
|
123
106
|
renderOpts: SerializerRendererOpts;
|
|
124
|
-
constructor(req: Request, res: Response, {
|
|
125
|
-
config: PsychicApp;
|
|
107
|
+
constructor(req: Request, res: Response, { action, }: {
|
|
126
108
|
action: string;
|
|
127
109
|
});
|
|
128
110
|
/**
|
|
@@ -242,6 +224,7 @@ export default class PsychicController {
|
|
|
242
224
|
endSession(): void;
|
|
243
225
|
private singleObjectJson;
|
|
244
226
|
json<T>(data: T, opts?: RenderOptions): any;
|
|
227
|
+
private _json;
|
|
245
228
|
/**
|
|
246
229
|
* Runs the data through openapi response validation, and then renders
|
|
247
230
|
* the data if no errors were found.
|
|
@@ -359,16 +342,6 @@ export default class PsychicController {
|
|
|
359
342
|
*/
|
|
360
343
|
runBeforeActionsFor(action: string): Promise<void>;
|
|
361
344
|
}
|
|
362
|
-
export declare class ControllerSerializerIndex {
|
|
363
|
-
associations: [
|
|
364
|
-
typeof PsychicController,
|
|
365
|
-
DreamModelSerializerType | SimpleObjectSerializerType,
|
|
366
|
-
typeof Dream
|
|
367
|
-
][];
|
|
368
|
-
add(ControllerClass: typeof PsychicController, SerializerClass: DreamModelSerializerType | SimpleObjectSerializerType, ModelClass: typeof Dream): void;
|
|
369
|
-
lookupModel(ControllerClass: typeof PsychicController, ModelClass: typeof Dream): [typeof PsychicController, DreamModelSerializerType | SimpleObjectSerializerType, typeof Dream] | undefined;
|
|
370
|
-
}
|
|
371
|
-
export declare const controllerSerializerIndex: ControllerSerializerIndex;
|
|
372
345
|
export type RenderOptions = {
|
|
373
346
|
serializerKey?: string;
|
|
374
347
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function sanitizeString<T extends string | null | undefined>(str: T): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function toJson<T>(data: T, sanitize: boolean): string;
|
|
@@ -50,6 +50,9 @@ export { default as generateController } from './generate/controller.js';
|
|
|
50
50
|
export { default as generateResource } from './generate/resource.js';
|
|
51
51
|
export { default as cookieMaxAgeFromCookieOpts } from './helpers/cookieMaxAgeFromCookieOpts.js';
|
|
52
52
|
export { default as pathifyNestedObject } from './helpers/pathifyNestedObject.js';
|
|
53
|
+
export { default as sanitizeString } from './helpers/sanitizeString.js';
|
|
54
|
+
export { default as ParamValidationError } from './error/controller/ParamValidationError.js';
|
|
55
|
+
export { default as ParamValidationErrors } from './error/controller/ParamValidationErrors.js';
|
|
53
56
|
export { MissingControllerActionPairingInRoutes, type OpenapiContent, type OpenapiEndpointRendererOpts, type OpenapiEndpointResponse, type OpenapiHeaderOption, type OpenapiHeaders, type OpenapiHeaderType, type OpenapiMethodBody, type OpenapiParameterResponse, type OpenapiPathParams, type OpenapiQueryOption, type OpenapiResponses, type OpenapiSchema, type OpenapiPathParamOption as OpenapiUriOption, } from './openapi-renderer/endpoint.js';
|
|
54
57
|
export { default as PsychicImporter } from './psychic-app/helpers/PsychicImporter.js';
|
|
55
58
|
export { default as PsychicApp, type DefaultPsychicOpenapiOptions, type NamedPsychicOpenapiOptions, type PsychicAppInitOptions, } from './psychic-app/index.js';
|
|
@@ -59,6 +62,4 @@ export { type HttpMethod } from './router/types.js';
|
|
|
59
62
|
export { createPsychicHttpInstance as getPsychicHttpInstance } from './server/helpers/startPsychicServer.js';
|
|
60
63
|
export { default as PsychicServer } from './server/index.js';
|
|
61
64
|
export { default as Params } from './server/params.js';
|
|
62
|
-
export { default as ParamValidationError } from './error/controller/ParamValidationError.js';
|
|
63
|
-
export { default as ParamValidationErrors } from './error/controller/ParamValidationErrors.js';
|
|
64
65
|
export { default as PsychicSession } from './session/index.js';
|
|
@@ -127,6 +127,8 @@ export default class PsychicApp {
|
|
|
127
127
|
get sslCredentials(): PsychicSslCredentials | undefined;
|
|
128
128
|
private _saltRounds;
|
|
129
129
|
get saltRounds(): number | undefined;
|
|
130
|
+
private _sanitizeResponseJson;
|
|
131
|
+
get sanitizeResponseJson(): boolean;
|
|
130
132
|
private _packageManager;
|
|
131
133
|
get packageManager(): "yarn" | "npm" | "pnpm";
|
|
132
134
|
private _importExtension;
|
|
@@ -219,10 +221,10 @@ export default class PsychicApp {
|
|
|
219
221
|
plugin(cb: (app: PsychicApp) => void | Promise<void>): void;
|
|
220
222
|
on<T extends PsychicHookEventType>(hookEventType: T, cb: T extends 'server:error' ? (err: Error, req: Request, res: Response) => void | Promise<void> : T extends 'server:init:before-middleware' ? (psychicServer: PsychicServer) => void | Promise<void> : T extends 'server:init:after-middleware' ? (psychicServer: PsychicServer) => void | Promise<void> : T extends 'server:start' ? (psychicServer: PsychicServer) => void | Promise<void> : T extends 'server:shutdown' ? (psychicServer: PsychicServer) => void | Promise<void> : T extends 'server:init:after-routes' ? (psychicServer: PsychicServer) => void | Promise<void> : T extends 'cli:start' ? (program: Command) => void | Promise<void> : T extends 'cli:sync' ? () => any : (conf: PsychicApp) => void | Promise<void>): void;
|
|
221
223
|
set(option: 'openapi', name: string, value: NamedPsychicOpenapiOptions): void;
|
|
222
|
-
set<Opt extends PsychicAppOption>(option: Opt, value: Opt extends 'appName' ? string : Opt extends 'apiOnly' ? boolean : Opt extends 'defaultResponseHeaders' ? Record<string, string | null> : Opt extends 'encryption' ? PsychicAppEncryptionOptions : Opt extends 'cors' ? CorsOptions : Opt extends 'cookie' ? CustomCookieOptions : Opt extends 'apiRoot' ? string : Opt extends 'importExtension' ? GeneratorImportStyle : Opt extends 'sessionCookieName' ? string : Opt extends 'clientRoot' ? string : Opt extends 'json' ? bodyParser.Options : Opt extends 'logger' ? PsychicLogger : Opt extends 'ssl' ? PsychicSslCredentials : Opt extends 'openapi' ? DefaultPsychicOpenapiOptions : Opt extends 'paths' ? PsychicPathOptions : Opt extends 'port' ? number : Opt extends 'saltRounds' ? number : Opt extends 'packageManager' ? DreamAppAllowedPackageManagersEnum : Opt extends 'inflections' ? () => void | Promise<void> : Opt extends 'routes' ? (r: PsychicRouter) => void | Promise<void> : never): void;
|
|
224
|
+
set<Opt extends PsychicAppOption>(option: Opt, value: Opt extends 'appName' ? string : Opt extends 'apiOnly' ? boolean : Opt extends 'defaultResponseHeaders' ? Record<string, string | null> : Opt extends 'encryption' ? PsychicAppEncryptionOptions : Opt extends 'cors' ? CorsOptions : Opt extends 'cookie' ? CustomCookieOptions : Opt extends 'apiRoot' ? string : Opt extends 'importExtension' ? GeneratorImportStyle : Opt extends 'sessionCookieName' ? string : Opt extends 'clientRoot' ? string : Opt extends 'json' ? bodyParser.Options : Opt extends 'logger' ? PsychicLogger : Opt extends 'ssl' ? PsychicSslCredentials : Opt extends 'openapi' ? DefaultPsychicOpenapiOptions : Opt extends 'paths' ? PsychicPathOptions : Opt extends 'port' ? number : Opt extends 'saltRounds' ? number : Opt extends 'sanitizeResponseJson' ? boolean : Opt extends 'packageManager' ? DreamAppAllowedPackageManagersEnum : Opt extends 'inflections' ? () => void | Promise<void> : Opt extends 'routes' ? (r: PsychicRouter) => void | Promise<void> : never): void;
|
|
223
225
|
override<Override extends keyof PsychicAppOverrides>(override: Override, value: PsychicAppOverrides[Override]): void;
|
|
224
226
|
}
|
|
225
|
-
export type PsychicAppOption = 'appName' | 'apiOnly' | 'apiRoot' | 'importExtension' | 'encryption' | 'sessionCookieName' | 'clientRoot' | 'cookie' | 'cors' | 'defaultResponseHeaders' | 'inflections' | 'json' | 'logger' | 'openapi' | 'packageManager' | 'paths' | 'port' | 'routes' | 'saltRounds' | 'ssl';
|
|
227
|
+
export type PsychicAppOption = 'appName' | 'apiOnly' | 'apiRoot' | 'importExtension' | 'encryption' | 'sessionCookieName' | 'clientRoot' | 'cookie' | 'cors' | 'defaultResponseHeaders' | 'inflections' | 'json' | 'logger' | 'openapi' | 'packageManager' | 'paths' | 'port' | 'routes' | 'saltRounds' | 'sanitizeResponseJson' | 'ssl';
|
|
226
228
|
export interface PsychicAppSpecialHooks {
|
|
227
229
|
cliSync: (() => any)[];
|
|
228
230
|
serverInitBeforeMiddleware: ((server: PsychicServer) => void | Promise<void>)[];
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { Application, Request, RequestHandler, Response, Router } from 'express';
|
|
2
2
|
import PsychicController from '../controller/index.js';
|
|
3
|
-
import PsychicApp from '../psychic-app/index.js';
|
|
4
3
|
import { NamespaceConfig, PsychicControllerActions } from '../router/helpers.js';
|
|
5
4
|
import RouteManager from './route-manager.js';
|
|
6
5
|
import { HttpMethod, ResourcesOptions } from './types.js';
|
|
7
6
|
export default class PsychicRouter {
|
|
8
7
|
app: Application | null;
|
|
9
|
-
config: PsychicApp;
|
|
10
8
|
currentNamespaces: NamespaceConfig[];
|
|
11
9
|
routeManager: RouteManager;
|
|
12
|
-
constructor(app: Application | null
|
|
13
|
-
get routingMechanism(): Application | Router | null;
|
|
10
|
+
constructor(app: Application | null);
|
|
14
11
|
get routes(): import("./route-manager.js").RouteConfig[];
|
|
15
12
|
private get currentNamespacePaths();
|
|
16
13
|
commit(): void;
|
|
@@ -56,7 +53,7 @@ export default class PsychicRouter {
|
|
|
56
53
|
}
|
|
57
54
|
export declare class PsychicNestedRouter extends PsychicRouter {
|
|
58
55
|
router: Router;
|
|
59
|
-
constructor(expressApp: Application | null,
|
|
56
|
+
constructor(expressApp: Application | null, routeManager: RouteManager, { namespaces, }?: {
|
|
60
57
|
namespaces?: NamespaceConfig[];
|
|
61
58
|
});
|
|
62
59
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Express } from 'express';
|
|
2
2
|
import { Server } from 'node:http';
|
|
3
|
-
import
|
|
3
|
+
import { PsychicSslCredentials } from '../psychic-app/index.js';
|
|
4
4
|
import { StartPsychicServerOptions } from './helpers/startPsychicServer.js';
|
|
5
5
|
export default class PsychicServer {
|
|
6
6
|
static startPsychicServer(opts: StartPsychicServerOptions): Promise<Server>;
|
|
@@ -10,7 +10,6 @@ export default class PsychicServer {
|
|
|
10
10
|
httpServer: Server;
|
|
11
11
|
private booted;
|
|
12
12
|
constructor();
|
|
13
|
-
get config(): PsychicApp;
|
|
14
13
|
routes(): Promise<import("../router/route-manager.js").RouteConfig[]>;
|
|
15
14
|
boot(): Promise<true | undefined>;
|
|
16
15
|
private setSecureDefaultHeaders;
|
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.
|
|
5
|
+
"version": "1.7.0",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@eslint/js": "^9.19.0",
|
|
64
64
|
"@jest-mock/express": "^3.0.0",
|
|
65
|
-
"@rvoh/dream": "^1.
|
|
65
|
+
"@rvoh/dream": "^1.7.0",
|
|
66
66
|
"@rvoh/dream-spec-helpers": "^1.1.1",
|
|
67
67
|
"@rvoh/psychic-spec-helpers": "^1.0.0",
|
|
68
68
|
"@types/express": "^5.0.1",
|