@rvoh/psychic 0.34.5 → 0.35.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/dist/cjs/src/bin/index.js +1 -1
- package/dist/cjs/src/generate/helpers/reduxBindings/writeApiFile.js +1 -1
- package/dist/cjs/src/openapi-renderer/endpoint.js +53 -9
- package/dist/cjs/src/openapi-renderer/helpers/pageParamOpenapiProperty.js +9 -0
- package/dist/cjs/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +35 -0
- package/dist/cjs/src/psychic-app/index.js +10 -35
- package/dist/cjs/src/server/index.js +5 -3
- package/dist/esm/src/bin/index.js +1 -1
- package/dist/esm/src/generate/helpers/reduxBindings/writeApiFile.js +1 -1
- package/dist/esm/src/openapi-renderer/endpoint.js +53 -9
- package/dist/esm/src/openapi-renderer/helpers/pageParamOpenapiProperty.js +6 -0
- package/dist/esm/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +29 -0
- package/dist/esm/src/psychic-app/index.js +10 -35
- package/dist/esm/src/server/index.js +5 -3
- package/dist/types/src/openapi-renderer/endpoint.d.ts +49 -1
- package/dist/types/src/openapi-renderer/helpers/pageParamOpenapiProperty.d.ts +4 -0
- package/dist/types/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.d.ts +15 -0
- package/dist/types/src/psychic-app/index.d.ts +5 -7
- package/dist/types/src/psychic-app/types.d.ts +1 -2
- package/package.json +2 -2
|
@@ -71,7 +71,7 @@ class PsychicBin {
|
|
|
71
71
|
await PsychicBin.syncOpenapiJson();
|
|
72
72
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
73
|
let output = {};
|
|
74
|
-
for (const hook of psychicApp.specialHooks.
|
|
74
|
+
for (const hook of psychicApp.specialHooks.cliSync) {
|
|
75
75
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
76
76
|
const res = await hook();
|
|
77
77
|
if ((0, typechecks_js_1.isObject)(res)) {
|
|
@@ -15,6 +15,8 @@ const openapiRoute_js_1 = __importDefault(require("./helpers/openapiRoute.js"));
|
|
|
15
15
|
const primitiveOpenapiStatementToOpenapi_js_1 = __importDefault(require("./helpers/primitiveOpenapiStatementToOpenapi.js"));
|
|
16
16
|
const serializer_js_1 = __importDefault(require("./serializer.js"));
|
|
17
17
|
const FailedToLookupSerializerForEndpoint_js_1 = __importDefault(require("../error/openapi/FailedToLookupSerializerForEndpoint.js"));
|
|
18
|
+
const pageParamOpenapiProperty_js_1 = __importDefault(require("./helpers/pageParamOpenapiProperty.js"));
|
|
19
|
+
const safelyAttachPaginationParamsToBodySegment_js_1 = __importDefault(require("./helpers/safelyAttachPaginationParamsToBodySegment.js"));
|
|
18
20
|
class OpenapiEndpointRenderer {
|
|
19
21
|
dreamsOrSerializers;
|
|
20
22
|
controllerClass;
|
|
@@ -299,7 +301,7 @@ class OpenapiEndpointRenderer {
|
|
|
299
301
|
* "parameters" field for a single endpoint.
|
|
300
302
|
*/
|
|
301
303
|
queryArray(openapiName) {
|
|
302
|
-
|
|
304
|
+
const queryParams = Object.keys(this.query || {}).map((queryName) => {
|
|
303
305
|
const queryParam = this.query[queryName];
|
|
304
306
|
let output = {
|
|
305
307
|
in: 'query',
|
|
@@ -325,7 +327,21 @@ class OpenapiEndpointRenderer {
|
|
|
325
327
|
};
|
|
326
328
|
}
|
|
327
329
|
return output;
|
|
328
|
-
}) || []
|
|
330
|
+
}) || [];
|
|
331
|
+
const paginationName = this.paginate?.query;
|
|
332
|
+
if (paginationName) {
|
|
333
|
+
queryParams.push({
|
|
334
|
+
in: 'query',
|
|
335
|
+
required: false,
|
|
336
|
+
name: paginationName,
|
|
337
|
+
description: 'Page number',
|
|
338
|
+
allowReserved: true,
|
|
339
|
+
schema: {
|
|
340
|
+
type: 'string',
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return queryParams;
|
|
329
345
|
}
|
|
330
346
|
/**
|
|
331
347
|
* @internal
|
|
@@ -335,17 +351,22 @@ class OpenapiEndpointRenderer {
|
|
|
335
351
|
computedRequestBody(openapiName, processedSchemas, routes) {
|
|
336
352
|
const method = this.computedMethod(routes);
|
|
337
353
|
if (this.requestBody === null)
|
|
338
|
-
return
|
|
354
|
+
return this.defaultRequestBody();
|
|
339
355
|
const httpMethodsThatAllowBody = ['post', 'patch', 'put'];
|
|
340
356
|
if (!httpMethodsThatAllowBody.includes(method))
|
|
341
|
-
return
|
|
357
|
+
return this.defaultRequestBody();
|
|
342
358
|
if (this.shouldAutogenerateBody()) {
|
|
343
359
|
return this.generateRequestBodyForModel(openapiName, processedSchemas);
|
|
344
360
|
}
|
|
345
361
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
346
|
-
|
|
362
|
+
let schema = this.recursivelyParseBody(openapiName, this.requestBody, processedSchemas, {
|
|
347
363
|
target: 'request',
|
|
348
364
|
});
|
|
365
|
+
const bodyPageParam = this.paginate?.body;
|
|
366
|
+
if (bodyPageParam) {
|
|
367
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
368
|
+
schema = (0, safelyAttachPaginationParamsToBodySegment_js_1.default)(bodyPageParam, schema);
|
|
369
|
+
}
|
|
349
370
|
if (!schema)
|
|
350
371
|
return undefined;
|
|
351
372
|
return {
|
|
@@ -357,6 +378,24 @@ class OpenapiEndpointRenderer {
|
|
|
357
378
|
},
|
|
358
379
|
};
|
|
359
380
|
}
|
|
381
|
+
defaultRequestBody() {
|
|
382
|
+
const bodyPageParam = this.paginate?.body;
|
|
383
|
+
if (bodyPageParam) {
|
|
384
|
+
return {
|
|
385
|
+
content: {
|
|
386
|
+
'application/json': {
|
|
387
|
+
schema: {
|
|
388
|
+
type: 'object',
|
|
389
|
+
properties: {
|
|
390
|
+
[bodyPageParam]: (0, pageParamOpenapiProperty_js_1.default)(),
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
360
399
|
/**
|
|
361
400
|
* @internal
|
|
362
401
|
*
|
|
@@ -390,7 +429,7 @@ class OpenapiEndpointRenderer {
|
|
|
390
429
|
const forDreamClass = this.requestBody?.for;
|
|
391
430
|
const dreamClass = forDreamClass || this.getSingleDreamModelClass();
|
|
392
431
|
if (!dreamClass)
|
|
393
|
-
return
|
|
432
|
+
return this.defaultRequestBody();
|
|
394
433
|
let paramSafeColumns = dreamClass.paramSafeColumnsOrFallback();
|
|
395
434
|
const only = this.requestBody?.only;
|
|
396
435
|
if (only) {
|
|
@@ -541,12 +580,17 @@ class OpenapiEndpointRenderer {
|
|
|
541
580
|
}
|
|
542
581
|
}
|
|
543
582
|
}
|
|
583
|
+
let processedSchema = this.recursivelyParseBody(openapiName, paramsShape, processedSchemas, {
|
|
584
|
+
target: 'request',
|
|
585
|
+
});
|
|
586
|
+
const bodyPageParam = this.paginate?.body;
|
|
587
|
+
if (bodyPageParam) {
|
|
588
|
+
processedSchema = (0, safelyAttachPaginationParamsToBodySegment_js_1.default)(bodyPageParam, processedSchema);
|
|
589
|
+
}
|
|
544
590
|
return {
|
|
545
591
|
content: {
|
|
546
592
|
'application/json': {
|
|
547
|
-
schema:
|
|
548
|
-
target: 'request',
|
|
549
|
-
}),
|
|
593
|
+
schema: processedSchema,
|
|
550
594
|
},
|
|
551
595
|
},
|
|
552
596
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
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 = safelyAttachPaginationParamToRequestBodySegment;
|
|
7
|
+
const pageParamOpenapiProperty_js_1 = __importDefault(require("./pageParamOpenapiProperty.js"));
|
|
8
|
+
/**
|
|
9
|
+
* @internal
|
|
10
|
+
*
|
|
11
|
+
* Used to carefully bind implicit pagination params
|
|
12
|
+
* to the requestBody properties. It will not apply
|
|
13
|
+
* the pagination param unless the provided bodySegment
|
|
14
|
+
* is:
|
|
15
|
+
*
|
|
16
|
+
* a.) falsey (null, undefined, etc...)
|
|
17
|
+
* b.) an openapi object (must have { type: 'object'})
|
|
18
|
+
*
|
|
19
|
+
* If neither of these apply, it will simply return
|
|
20
|
+
* what was given to it, without any modifications
|
|
21
|
+
*/
|
|
22
|
+
function safelyAttachPaginationParamToRequestBodySegment(paramName, bodySegment) {
|
|
23
|
+
bodySegment ||= {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {},
|
|
26
|
+
};
|
|
27
|
+
if (bodySegment.type === 'object') {
|
|
28
|
+
;
|
|
29
|
+
bodySegment.properties = {
|
|
30
|
+
...bodySegment.properties,
|
|
31
|
+
[paramName]: (0, pageParamOpenapiProperty_js_1.default)(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return bodySegment;
|
|
35
|
+
}
|
|
@@ -220,19 +220,10 @@ Try setting it to something valid, like:
|
|
|
220
220
|
get inflections() {
|
|
221
221
|
return this._inflections;
|
|
222
222
|
}
|
|
223
|
-
_bootHooks = {
|
|
224
|
-
boot: [],
|
|
225
|
-
load: [],
|
|
226
|
-
'load:dev': [],
|
|
227
|
-
'load:test': [],
|
|
228
|
-
'load:prod': [],
|
|
229
|
-
};
|
|
230
|
-
get bootHooks() {
|
|
231
|
-
return this._bootHooks;
|
|
232
|
-
}
|
|
233
223
|
_specialHooks = {
|
|
234
|
-
|
|
235
|
-
|
|
224
|
+
cliSync: [],
|
|
225
|
+
serverInitBeforeMiddleware: [],
|
|
226
|
+
serverInitAfterMiddleware: [],
|
|
236
227
|
serverInitAfterRoutes: [],
|
|
237
228
|
serverStart: [],
|
|
238
229
|
serverError: [],
|
|
@@ -297,18 +288,6 @@ Try setting it to something valid, like:
|
|
|
297
288
|
if (this.booted && !force)
|
|
298
289
|
return;
|
|
299
290
|
// await new IntegrityChecker().check()
|
|
300
|
-
await this.runHooksFor('load');
|
|
301
|
-
switch (EnvInternal_js_1.default.nodeEnv) {
|
|
302
|
-
case 'development':
|
|
303
|
-
await this.runHooksFor('load:dev');
|
|
304
|
-
break;
|
|
305
|
-
case 'production':
|
|
306
|
-
await this.runHooksFor('load:prod');
|
|
307
|
-
break;
|
|
308
|
-
case 'test':
|
|
309
|
-
await this.runHooksFor('load:test');
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
291
|
await this.inflections?.();
|
|
313
292
|
this.booted = true;
|
|
314
293
|
}
|
|
@@ -320,8 +299,11 @@ Try setting it to something valid, like:
|
|
|
320
299
|
case 'server:error':
|
|
321
300
|
this._specialHooks.serverError.push(cb);
|
|
322
301
|
break;
|
|
323
|
-
case 'server:init':
|
|
324
|
-
this._specialHooks.
|
|
302
|
+
case 'server:init:before-middleware':
|
|
303
|
+
this._specialHooks.serverInitBeforeMiddleware.push(cb);
|
|
304
|
+
break;
|
|
305
|
+
case 'server:init:after-middleware':
|
|
306
|
+
this._specialHooks.serverInitAfterMiddleware.push(cb);
|
|
325
307
|
break;
|
|
326
308
|
case 'server:start':
|
|
327
309
|
this._specialHooks.serverStart.push(cb);
|
|
@@ -332,12 +314,10 @@ Try setting it to something valid, like:
|
|
|
332
314
|
case 'server:init:after-routes':
|
|
333
315
|
this._specialHooks.serverInitAfterRoutes.push(cb);
|
|
334
316
|
break;
|
|
335
|
-
case 'sync':
|
|
317
|
+
case 'cli:sync':
|
|
336
318
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
337
|
-
this._specialHooks['
|
|
319
|
+
this._specialHooks['cliSync'].push(cb);
|
|
338
320
|
break;
|
|
339
|
-
default:
|
|
340
|
-
this.bootHooks[hookEventType].push(cb);
|
|
341
321
|
}
|
|
342
322
|
}
|
|
343
323
|
set(option, unknown1, unknown2) {
|
|
@@ -429,11 +409,6 @@ Try setting it to something valid, like:
|
|
|
429
409
|
this.overrides['server:start'] = value;
|
|
430
410
|
}
|
|
431
411
|
}
|
|
432
|
-
async runHooksFor(hookEventType) {
|
|
433
|
-
for (const hook of this.bootHooks[hookEventType]) {
|
|
434
|
-
await hook(this);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
412
|
}
|
|
438
413
|
exports.default = PsychicApp;
|
|
439
414
|
exports.PsychicAppAllowedPackageManagersEnumValues = ['yarn', 'npm', 'pnpm'];
|
|
@@ -73,7 +73,9 @@ class PsychicServer {
|
|
|
73
73
|
});
|
|
74
74
|
next();
|
|
75
75
|
});
|
|
76
|
-
|
|
76
|
+
for (const serverInitBeforeMiddlewareHook of this.config.specialHooks.serverInitBeforeMiddleware) {
|
|
77
|
+
await serverInitBeforeMiddlewareHook(this);
|
|
78
|
+
}
|
|
77
79
|
this.initializeCors();
|
|
78
80
|
this.initializeJSON();
|
|
79
81
|
try {
|
|
@@ -87,8 +89,8 @@ class PsychicServer {
|
|
|
87
89
|
${error.message}
|
|
88
90
|
`);
|
|
89
91
|
}
|
|
90
|
-
for (const
|
|
91
|
-
await
|
|
92
|
+
for (const serverInitAfterMiddlewareHook of this.config.specialHooks.serverInitAfterMiddleware) {
|
|
93
|
+
await serverInitAfterMiddlewareHook(this);
|
|
92
94
|
}
|
|
93
95
|
this.initializeOpenapiValidation();
|
|
94
96
|
await this.buildRoutes();
|
|
@@ -43,7 +43,7 @@ export default class PsychicBin {
|
|
|
43
43
|
await PsychicBin.syncOpenapiJson();
|
|
44
44
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
45
|
let output = {};
|
|
46
|
-
for (const hook of psychicApp.specialHooks.
|
|
46
|
+
for (const hook of psychicApp.specialHooks.cliSync) {
|
|
47
47
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
48
48
|
const res = await hook();
|
|
49
49
|
if (isObject(res)) {
|
|
@@ -9,6 +9,8 @@ import openapiRoute from './helpers/openapiRoute.js';
|
|
|
9
9
|
import primitiveOpenapiStatementToOpenapi from './helpers/primitiveOpenapiStatementToOpenapi.js';
|
|
10
10
|
import OpenapiSerializerRenderer from './serializer.js';
|
|
11
11
|
import OpenApiFailedToLookupSerializerForEndpoint from '../error/openapi/FailedToLookupSerializerForEndpoint.js';
|
|
12
|
+
import openapiPageParamProperty from './helpers/pageParamOpenapiProperty.js';
|
|
13
|
+
import safelyAttachPaginationParamToRequestBodySegment from './helpers/safelyAttachPaginationParamsToBodySegment.js';
|
|
12
14
|
export default class OpenapiEndpointRenderer {
|
|
13
15
|
dreamsOrSerializers;
|
|
14
16
|
controllerClass;
|
|
@@ -293,7 +295,7 @@ export default class OpenapiEndpointRenderer {
|
|
|
293
295
|
* "parameters" field for a single endpoint.
|
|
294
296
|
*/
|
|
295
297
|
queryArray(openapiName) {
|
|
296
|
-
|
|
298
|
+
const queryParams = Object.keys(this.query || {}).map((queryName) => {
|
|
297
299
|
const queryParam = this.query[queryName];
|
|
298
300
|
let output = {
|
|
299
301
|
in: 'query',
|
|
@@ -319,7 +321,21 @@ export default class OpenapiEndpointRenderer {
|
|
|
319
321
|
};
|
|
320
322
|
}
|
|
321
323
|
return output;
|
|
322
|
-
}) || []
|
|
324
|
+
}) || [];
|
|
325
|
+
const paginationName = this.paginate?.query;
|
|
326
|
+
if (paginationName) {
|
|
327
|
+
queryParams.push({
|
|
328
|
+
in: 'query',
|
|
329
|
+
required: false,
|
|
330
|
+
name: paginationName,
|
|
331
|
+
description: 'Page number',
|
|
332
|
+
allowReserved: true,
|
|
333
|
+
schema: {
|
|
334
|
+
type: 'string',
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return queryParams;
|
|
323
339
|
}
|
|
324
340
|
/**
|
|
325
341
|
* @internal
|
|
@@ -329,17 +345,22 @@ export default class OpenapiEndpointRenderer {
|
|
|
329
345
|
computedRequestBody(openapiName, processedSchemas, routes) {
|
|
330
346
|
const method = this.computedMethod(routes);
|
|
331
347
|
if (this.requestBody === null)
|
|
332
|
-
return
|
|
348
|
+
return this.defaultRequestBody();
|
|
333
349
|
const httpMethodsThatAllowBody = ['post', 'patch', 'put'];
|
|
334
350
|
if (!httpMethodsThatAllowBody.includes(method))
|
|
335
|
-
return
|
|
351
|
+
return this.defaultRequestBody();
|
|
336
352
|
if (this.shouldAutogenerateBody()) {
|
|
337
353
|
return this.generateRequestBodyForModel(openapiName, processedSchemas);
|
|
338
354
|
}
|
|
339
355
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
340
|
-
|
|
356
|
+
let schema = this.recursivelyParseBody(openapiName, this.requestBody, processedSchemas, {
|
|
341
357
|
target: 'request',
|
|
342
358
|
});
|
|
359
|
+
const bodyPageParam = this.paginate?.body;
|
|
360
|
+
if (bodyPageParam) {
|
|
361
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
362
|
+
schema = safelyAttachPaginationParamToRequestBodySegment(bodyPageParam, schema);
|
|
363
|
+
}
|
|
343
364
|
if (!schema)
|
|
344
365
|
return undefined;
|
|
345
366
|
return {
|
|
@@ -351,6 +372,24 @@ export default class OpenapiEndpointRenderer {
|
|
|
351
372
|
},
|
|
352
373
|
};
|
|
353
374
|
}
|
|
375
|
+
defaultRequestBody() {
|
|
376
|
+
const bodyPageParam = this.paginate?.body;
|
|
377
|
+
if (bodyPageParam) {
|
|
378
|
+
return {
|
|
379
|
+
content: {
|
|
380
|
+
'application/json': {
|
|
381
|
+
schema: {
|
|
382
|
+
type: 'object',
|
|
383
|
+
properties: {
|
|
384
|
+
[bodyPageParam]: openapiPageParamProperty(),
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
354
393
|
/**
|
|
355
394
|
* @internal
|
|
356
395
|
*
|
|
@@ -384,7 +423,7 @@ export default class OpenapiEndpointRenderer {
|
|
|
384
423
|
const forDreamClass = this.requestBody?.for;
|
|
385
424
|
const dreamClass = forDreamClass || this.getSingleDreamModelClass();
|
|
386
425
|
if (!dreamClass)
|
|
387
|
-
return
|
|
426
|
+
return this.defaultRequestBody();
|
|
388
427
|
let paramSafeColumns = dreamClass.paramSafeColumnsOrFallback();
|
|
389
428
|
const only = this.requestBody?.only;
|
|
390
429
|
if (only) {
|
|
@@ -535,12 +574,17 @@ export default class OpenapiEndpointRenderer {
|
|
|
535
574
|
}
|
|
536
575
|
}
|
|
537
576
|
}
|
|
577
|
+
let processedSchema = this.recursivelyParseBody(openapiName, paramsShape, processedSchemas, {
|
|
578
|
+
target: 'request',
|
|
579
|
+
});
|
|
580
|
+
const bodyPageParam = this.paginate?.body;
|
|
581
|
+
if (bodyPageParam) {
|
|
582
|
+
processedSchema = safelyAttachPaginationParamToRequestBodySegment(bodyPageParam, processedSchema);
|
|
583
|
+
}
|
|
538
584
|
return {
|
|
539
585
|
content: {
|
|
540
586
|
'application/json': {
|
|
541
|
-
schema:
|
|
542
|
-
target: 'request',
|
|
543
|
-
}),
|
|
587
|
+
schema: processedSchema,
|
|
544
588
|
},
|
|
545
589
|
},
|
|
546
590
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import openapiPageParamProperty from './pageParamOpenapiProperty.js';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* Used to carefully bind implicit pagination params
|
|
6
|
+
* to the requestBody properties. It will not apply
|
|
7
|
+
* the pagination param unless the provided bodySegment
|
|
8
|
+
* is:
|
|
9
|
+
*
|
|
10
|
+
* a.) falsey (null, undefined, etc...)
|
|
11
|
+
* b.) an openapi object (must have { type: 'object'})
|
|
12
|
+
*
|
|
13
|
+
* If neither of these apply, it will simply return
|
|
14
|
+
* what was given to it, without any modifications
|
|
15
|
+
*/
|
|
16
|
+
export default function safelyAttachPaginationParamToRequestBodySegment(paramName, bodySegment) {
|
|
17
|
+
bodySegment ||= {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {},
|
|
20
|
+
};
|
|
21
|
+
if (bodySegment.type === 'object') {
|
|
22
|
+
;
|
|
23
|
+
bodySegment.properties = {
|
|
24
|
+
...bodySegment.properties,
|
|
25
|
+
[paramName]: openapiPageParamProperty(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return bodySegment;
|
|
29
|
+
}
|
|
@@ -191,19 +191,10 @@ Try setting it to something valid, like:
|
|
|
191
191
|
get inflections() {
|
|
192
192
|
return this._inflections;
|
|
193
193
|
}
|
|
194
|
-
_bootHooks = {
|
|
195
|
-
boot: [],
|
|
196
|
-
load: [],
|
|
197
|
-
'load:dev': [],
|
|
198
|
-
'load:test': [],
|
|
199
|
-
'load:prod': [],
|
|
200
|
-
};
|
|
201
|
-
get bootHooks() {
|
|
202
|
-
return this._bootHooks;
|
|
203
|
-
}
|
|
204
194
|
_specialHooks = {
|
|
205
|
-
|
|
206
|
-
|
|
195
|
+
cliSync: [],
|
|
196
|
+
serverInitBeforeMiddleware: [],
|
|
197
|
+
serverInitAfterMiddleware: [],
|
|
207
198
|
serverInitAfterRoutes: [],
|
|
208
199
|
serverStart: [],
|
|
209
200
|
serverError: [],
|
|
@@ -268,18 +259,6 @@ Try setting it to something valid, like:
|
|
|
268
259
|
if (this.booted && !force)
|
|
269
260
|
return;
|
|
270
261
|
// await new IntegrityChecker().check()
|
|
271
|
-
await this.runHooksFor('load');
|
|
272
|
-
switch (EnvInternal.nodeEnv) {
|
|
273
|
-
case 'development':
|
|
274
|
-
await this.runHooksFor('load:dev');
|
|
275
|
-
break;
|
|
276
|
-
case 'production':
|
|
277
|
-
await this.runHooksFor('load:prod');
|
|
278
|
-
break;
|
|
279
|
-
case 'test':
|
|
280
|
-
await this.runHooksFor('load:test');
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
262
|
await this.inflections?.();
|
|
284
263
|
this.booted = true;
|
|
285
264
|
}
|
|
@@ -291,8 +270,11 @@ Try setting it to something valid, like:
|
|
|
291
270
|
case 'server:error':
|
|
292
271
|
this._specialHooks.serverError.push(cb);
|
|
293
272
|
break;
|
|
294
|
-
case 'server:init':
|
|
295
|
-
this._specialHooks.
|
|
273
|
+
case 'server:init:before-middleware':
|
|
274
|
+
this._specialHooks.serverInitBeforeMiddleware.push(cb);
|
|
275
|
+
break;
|
|
276
|
+
case 'server:init:after-middleware':
|
|
277
|
+
this._specialHooks.serverInitAfterMiddleware.push(cb);
|
|
296
278
|
break;
|
|
297
279
|
case 'server:start':
|
|
298
280
|
this._specialHooks.serverStart.push(cb);
|
|
@@ -303,12 +285,10 @@ Try setting it to something valid, like:
|
|
|
303
285
|
case 'server:init:after-routes':
|
|
304
286
|
this._specialHooks.serverInitAfterRoutes.push(cb);
|
|
305
287
|
break;
|
|
306
|
-
case 'sync':
|
|
288
|
+
case 'cli:sync':
|
|
307
289
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
308
|
-
this._specialHooks['
|
|
290
|
+
this._specialHooks['cliSync'].push(cb);
|
|
309
291
|
break;
|
|
310
|
-
default:
|
|
311
|
-
this.bootHooks[hookEventType].push(cb);
|
|
312
292
|
}
|
|
313
293
|
}
|
|
314
294
|
set(option, unknown1, unknown2) {
|
|
@@ -400,10 +380,5 @@ Try setting it to something valid, like:
|
|
|
400
380
|
this.overrides['server:start'] = value;
|
|
401
381
|
}
|
|
402
382
|
}
|
|
403
|
-
async runHooksFor(hookEventType) {
|
|
404
|
-
for (const hook of this.bootHooks[hookEventType]) {
|
|
405
|
-
await hook(this);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
383
|
}
|
|
409
384
|
export const PsychicAppAllowedPackageManagersEnumValues = ['yarn', 'npm', 'pnpm'];
|
|
@@ -45,7 +45,9 @@ export default class PsychicServer {
|
|
|
45
45
|
});
|
|
46
46
|
next();
|
|
47
47
|
});
|
|
48
|
-
|
|
48
|
+
for (const serverInitBeforeMiddlewareHook of this.config.specialHooks.serverInitBeforeMiddleware) {
|
|
49
|
+
await serverInitBeforeMiddlewareHook(this);
|
|
50
|
+
}
|
|
49
51
|
this.initializeCors();
|
|
50
52
|
this.initializeJSON();
|
|
51
53
|
try {
|
|
@@ -59,8 +61,8 @@ export default class PsychicServer {
|
|
|
59
61
|
${error.message}
|
|
60
62
|
`);
|
|
61
63
|
}
|
|
62
|
-
for (const
|
|
63
|
-
await
|
|
64
|
+
for (const serverInitAfterMiddlewareHook of this.config.specialHooks.serverInitAfterMiddleware) {
|
|
65
|
+
await serverInitAfterMiddlewareHook(this);
|
|
64
66
|
}
|
|
65
67
|
this.initializeOpenapiValidation();
|
|
66
68
|
await this.buildRoutes();
|
|
@@ -137,6 +137,7 @@ export default class OpenapiEndpointRenderer<DreamsOrSerializersOrViewModels ext
|
|
|
137
137
|
* Generates the requestBody portion of the endpoint
|
|
138
138
|
*/
|
|
139
139
|
private computedRequestBody;
|
|
140
|
+
private defaultRequestBody;
|
|
140
141
|
/**
|
|
141
142
|
* @internal
|
|
142
143
|
*
|
|
@@ -284,7 +285,7 @@ export interface OpenapiEndpointRendererOpts<I extends DreamSerializable | Dream
|
|
|
284
285
|
* }
|
|
285
286
|
* ```
|
|
286
287
|
*/
|
|
287
|
-
paginate?: boolean;
|
|
288
|
+
paginate?: boolean | CustomPaginationOpts;
|
|
288
289
|
/**
|
|
289
290
|
* specify path params. This is usually not necessary, since path params
|
|
290
291
|
* are automatically computed for your endpoint by matching against the
|
|
@@ -489,6 +490,53 @@ export interface OpenapiEndpointRendererOpts<I extends DreamSerializable | Dream
|
|
|
489
490
|
*/
|
|
490
491
|
omitDefaultResponses?: boolean;
|
|
491
492
|
}
|
|
493
|
+
export type CustomPaginationOpts = {
|
|
494
|
+
/**
|
|
495
|
+
* if provided, a query param will be added to the
|
|
496
|
+
* openapi spec with the name provided.
|
|
497
|
+
*
|
|
498
|
+
* ```ts
|
|
499
|
+
* @OpenAPI(Post, {
|
|
500
|
+
* paginate: {
|
|
501
|
+
* query: 'page'
|
|
502
|
+
* },
|
|
503
|
+
* })
|
|
504
|
+
* public async index() {
|
|
505
|
+
* this.ok(
|
|
506
|
+
* await this.currentUser
|
|
507
|
+
* .associationQuery('posts')
|
|
508
|
+
* .paginate({
|
|
509
|
+
* page: this.castParam('page', 'integer')
|
|
510
|
+
* })
|
|
511
|
+
* )
|
|
512
|
+
* }
|
|
513
|
+
* ```
|
|
514
|
+
*/
|
|
515
|
+
query: string;
|
|
516
|
+
} | {
|
|
517
|
+
/**
|
|
518
|
+
* if provided, a requestBody field will be added
|
|
519
|
+
* to the openapi spec with the name provided.
|
|
520
|
+
*
|
|
521
|
+
* ```ts
|
|
522
|
+
* @OpenAPI(Post, {
|
|
523
|
+
* paginate: {
|
|
524
|
+
* body: 'page'
|
|
525
|
+
* },
|
|
526
|
+
* })
|
|
527
|
+
* public async index() {
|
|
528
|
+
* this.ok(
|
|
529
|
+
* await this.currentUser
|
|
530
|
+
* .associationQuery('posts')
|
|
531
|
+
* .paginate({
|
|
532
|
+
* page: this.castParam('page', 'integer')
|
|
533
|
+
* })
|
|
534
|
+
* )
|
|
535
|
+
* }
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
body: string;
|
|
539
|
+
};
|
|
492
540
|
export interface OpenapiEndpointRendererDefaultResponseOption {
|
|
493
541
|
description?: string;
|
|
494
542
|
maybeNull?: boolean;
|
package/dist/types/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
*
|
|
4
|
+
* Used to carefully bind implicit pagination params
|
|
5
|
+
* to the requestBody properties. It will not apply
|
|
6
|
+
* the pagination param unless the provided bodySegment
|
|
7
|
+
* is:
|
|
8
|
+
*
|
|
9
|
+
* a.) falsey (null, undefined, etc...)
|
|
10
|
+
* b.) an openapi object (must have { type: 'object'})
|
|
11
|
+
*
|
|
12
|
+
* If neither of these apply, it will simply return
|
|
13
|
+
* what was given to it, without any modifications
|
|
14
|
+
*/
|
|
15
|
+
export default function safelyAttachPaginationParamToRequestBodySegment<T>(paramName: string, bodySegment: T): T;
|
|
@@ -7,7 +7,7 @@ import * as http from 'node:http';
|
|
|
7
7
|
import { OpenapiContent, OpenapiHeaders, OpenapiResponses, OpenapiSecurity, OpenapiSecuritySchemes, OpenapiServer } from '../openapi-renderer/endpoint.js';
|
|
8
8
|
import PsychicRouter from '../router/index.js';
|
|
9
9
|
import PsychicServer from '../server/index.js';
|
|
10
|
-
import { PsychicHookEventType
|
|
10
|
+
import { PsychicHookEventType } from './types.js';
|
|
11
11
|
import { Command } from 'commander';
|
|
12
12
|
export default class PsychicApp {
|
|
13
13
|
static init(cb: (app: PsychicApp) => void | Promise<void>, dreamCb: (app: DreamApp) => void | Promise<void>, opts?: PsychicAppInitOptions): Promise<PsychicApp>;
|
|
@@ -70,8 +70,6 @@ export default class PsychicApp {
|
|
|
70
70
|
get paths(): Required<PsychicPathOptions>;
|
|
71
71
|
private _inflections?;
|
|
72
72
|
get inflections(): (() => void | Promise<void>) | undefined;
|
|
73
|
-
private _bootHooks;
|
|
74
|
-
get bootHooks(): Record<PsychicHookLoadEventTypes, ((conf: PsychicApp) => void | Promise<void>)[]>;
|
|
75
73
|
private _specialHooks;
|
|
76
74
|
get specialHooks(): PsychicAppSpecialHooks;
|
|
77
75
|
private _overrides;
|
|
@@ -93,18 +91,18 @@ export default class PsychicApp {
|
|
|
93
91
|
private booted;
|
|
94
92
|
boot(force?: boolean): Promise<void>;
|
|
95
93
|
plugin(cb: (app: PsychicApp) => void | Promise<void>): void;
|
|
96
|
-
on<T extends PsychicHookEventType>(hookEventType: T, cb: T extends 'server:error' ? (err: Error, req: Request, res: Response) => void | Promise<void> : T extends 'server:init' ? (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 'sync' ? () => any : (conf: PsychicApp) => void | Promise<void>): void;
|
|
94
|
+
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;
|
|
97
95
|
set(option: 'openapi', name: string, value: NamedPsychicOpenapiOptions): void;
|
|
98
96
|
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 'sessionCookieName' ? string : Opt extends 'clientRoot' ? string : Opt extends 'json' ? bodyParser.Options : Opt extends 'logger' ? PsychicLogger : Opt extends 'client' ? PsychicClientOptions : Opt extends 'ssl' ? PsychicSslCredentials : Opt extends 'openapi' ? DefaultPsychicOpenapiOptions : Opt extends 'paths' ? PsychicPathOptions : Opt extends 'port' ? number : Opt extends 'saltRounds' ? number : Opt extends 'packageManager' ? PsychicAppAllowedPackageManagersEnum : Opt extends 'inflections' ? () => void | Promise<void> : Opt extends 'routes' ? (r: PsychicRouter) => void | Promise<void> : never): void;
|
|
99
97
|
override<Override extends keyof PsychicAppOverrides>(override: Override, value: PsychicAppOverrides[Override]): void;
|
|
100
|
-
private runHooksFor;
|
|
101
98
|
}
|
|
102
99
|
export type PsychicAppOption = 'appName' | 'apiOnly' | 'apiRoot' | 'encryption' | 'sessionCookieName' | 'client' | 'clientRoot' | 'cookie' | 'cors' | 'defaultResponseHeaders' | 'inflections' | 'json' | 'logger' | 'openapi' | 'packageManager' | 'paths' | 'port' | 'routes' | 'saltRounds' | 'ssl';
|
|
103
100
|
export declare const PsychicAppAllowedPackageManagersEnumValues: readonly ["yarn", "npm", "pnpm"];
|
|
104
101
|
export type PsychicAppAllowedPackageManagersEnum = (typeof PsychicAppAllowedPackageManagersEnumValues)[number];
|
|
105
102
|
export interface PsychicAppSpecialHooks {
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
cliSync: (() => any)[];
|
|
104
|
+
serverInitBeforeMiddleware: ((server: PsychicServer) => void | Promise<void>)[];
|
|
105
|
+
serverInitAfterMiddleware: ((server: PsychicServer) => void | Promise<void>)[];
|
|
108
106
|
serverInitAfterRoutes: ((server: PsychicServer) => void | Promise<void>)[];
|
|
109
107
|
serverStart: ((server: PsychicServer) => void | Promise<void>)[];
|
|
110
108
|
serverShutdown: ((server: PsychicServer) => void | Promise<void>)[];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import PsychicApp from './index.js';
|
|
2
2
|
export type UUID = string;
|
|
3
|
-
export type PsychicHookEventType = '
|
|
4
|
-
export type PsychicHookLoadEventTypes = Exclude<PsychicHookEventType, 'server:error' | 'server:init' | 'server:init:after-routes' | 'server:start' | 'server:shutdown' | 'sync'>;
|
|
3
|
+
export type PsychicHookEventType = 'cli:sync' | 'server:init:before-middleware' | 'server:init:after-middleware' | 'server:init:after-routes' | 'server:start' | 'server:error' | 'server:shutdown';
|
|
5
4
|
export type PsychicAppInitializerCb = (psychicApp: PsychicApp) => void | Promise<void>;
|
|
6
5
|
type Only<T, U> = T & Partial<Record<Exclude<keyof U, keyof T>, never>>;
|
|
7
6
|
export type Either<T, U> = Only<T, U> | Only<U, T>;
|
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": "0.
|
|
5
|
+
"version": "0.35.0",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"typedoc": "^0.26.6",
|
|
84
84
|
"typescript": "^5.5.4",
|
|
85
85
|
"typescript-eslint": "=7.18.0",
|
|
86
|
-
"vitest": "^3.1.
|
|
86
|
+
"vitest": "^3.1.3",
|
|
87
87
|
"winston": "^3.14.2"
|
|
88
88
|
},
|
|
89
89
|
"packageManager": "yarn@4.7.0"
|