@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.
@@ -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.sync) {
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)) {
@@ -65,7 +65,7 @@ export const ${apiImport} = createApi({
65
65
  keepUnusedDataFor: 0,
66
66
 
67
67
  baseQuery: fetchBaseQuery({
68
- baseUrl: baseURL(),
68
+ baseUrl: baseUrl(),
69
69
  credentials: 'include',
70
70
 
71
71
  // we recommend that you use a function like this for preparing
@@ -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
- return (Object.keys(this.query || {}).map((queryName) => {
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 undefined;
354
+ return this.defaultRequestBody();
339
355
  const httpMethodsThatAllowBody = ['post', 'patch', 'put'];
340
356
  if (!httpMethodsThatAllowBody.includes(method))
341
- return undefined;
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
- const schema = this.recursivelyParseBody(openapiName, this.requestBody, processedSchemas, {
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 undefined;
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: this.recursivelyParseBody(openapiName, paramsShape, processedSchemas, {
548
- target: 'request',
549
- }),
593
+ schema: processedSchema,
550
594
  },
551
595
  },
552
596
  };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = openapiPageParamProperty;
4
+ function openapiPageParamProperty() {
5
+ return {
6
+ type: 'integer',
7
+ description: 'Page number',
8
+ };
9
+ }
@@ -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
- sync: [],
235
- serverInit: [],
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.serverInit.push(cb);
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['sync'].push(cb);
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
- await this.config['runHooksFor']('boot');
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 expressInitHook of this.config.specialHooks.serverInit) {
91
- await expressInitHook(this);
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.sync) {
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)) {
@@ -39,7 +39,7 @@ export const ${apiImport} = createApi({
39
39
  keepUnusedDataFor: 0,
40
40
 
41
41
  baseQuery: fetchBaseQuery({
42
- baseUrl: baseURL(),
42
+ baseUrl: baseUrl(),
43
43
  credentials: 'include',
44
44
 
45
45
  // we recommend that you use a function like this for preparing
@@ -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
- return (Object.keys(this.query || {}).map((queryName) => {
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 undefined;
348
+ return this.defaultRequestBody();
333
349
  const httpMethodsThatAllowBody = ['post', 'patch', 'put'];
334
350
  if (!httpMethodsThatAllowBody.includes(method))
335
- return undefined;
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
- const schema = this.recursivelyParseBody(openapiName, this.requestBody, processedSchemas, {
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 undefined;
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: this.recursivelyParseBody(openapiName, paramsShape, processedSchemas, {
542
- target: 'request',
543
- }),
587
+ schema: processedSchema,
544
588
  },
545
589
  },
546
590
  };
@@ -0,0 +1,6 @@
1
+ export default function openapiPageParamProperty() {
2
+ return {
3
+ type: 'integer',
4
+ description: 'Page number',
5
+ };
6
+ }
@@ -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
- sync: [],
206
- serverInit: [],
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.serverInit.push(cb);
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['sync'].push(cb);
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
- await this.config['runHooksFor']('boot');
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 expressInitHook of this.config.specialHooks.serverInit) {
63
- await expressInitHook(this);
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;
@@ -0,0 +1,4 @@
1
+ export default function openapiPageParamProperty(): {
2
+ readonly type: "integer";
3
+ readonly description: "Page number";
4
+ };
@@ -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, PsychicHookLoadEventTypes } from './types.js';
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
- sync: (() => any)[];
107
- serverInit: ((server: PsychicServer) => void | Promise<void>)[];
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 = 'boot' | 'sync' | 'load' | 'load:dev' | 'load:prod' | 'load:test' | 'server:init' | 'server:init:after-routes' | 'server:start' | 'server:error' | 'server:shutdown';
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.34.5",
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.1",
86
+ "vitest": "^3.1.3",
87
87
  "winston": "^3.14.2"
88
88
  },
89
89
  "packageManager": "yarn@4.7.0"