@rvoh/psychic 3.0.0-alpha.8 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cjs/src/cli/index.js +4 -22
  2. package/dist/cjs/src/controller/index.js +15 -2
  3. package/dist/cjs/src/generate/controller.js +3 -3
  4. package/dist/cjs/src/generate/helpers/generateResourceControllerSpecContent.js +6 -5
  5. package/dist/cjs/src/openapi-renderer/body-segment.js +0 -1
  6. package/dist/cjs/src/openapi-renderer/endpoint.js +13 -29
  7. package/dist/cjs/src/openapi-renderer/helpers/safelyAttachCursorPaginationParamToRequestBodySegment.js +3 -15
  8. package/dist/cjs/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +14 -1
  9. package/dist/cjs/src/router/helpers.js +12 -28
  10. package/dist/cjs/src/router/index.js +2 -7
  11. package/dist/cjs/src/server/params.js +71 -89
  12. package/dist/esm/src/cli/index.js +4 -22
  13. package/dist/esm/src/controller/index.js +15 -2
  14. package/dist/esm/src/generate/controller.js +3 -3
  15. package/dist/esm/src/generate/helpers/generateResourceControllerSpecContent.js +6 -5
  16. package/dist/esm/src/openapi-renderer/body-segment.js +0 -1
  17. package/dist/esm/src/openapi-renderer/endpoint.js +13 -29
  18. package/dist/esm/src/openapi-renderer/helpers/safelyAttachCursorPaginationParamToRequestBodySegment.js +3 -15
  19. package/dist/esm/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +14 -1
  20. package/dist/esm/src/router/helpers.js +12 -28
  21. package/dist/esm/src/router/index.js +2 -7
  22. package/dist/esm/src/server/params.js +71 -89
  23. package/dist/types/src/controller/index.d.ts +12 -1
  24. package/dist/types/src/openapi-renderer/helpers/safelyAttachCursorPaginationParamToRequestBodySegment.d.ts +1 -1
  25. package/dist/types/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.d.ts +6 -0
  26. package/dist/types/src/router/helpers.d.ts +3 -0
  27. package/package.json +30 -20
  28. package/dist/cjs/src/generate/helpers/zustandBindings/printFinalStepsMessage.js +0 -36
  29. package/dist/cjs/src/generate/helpers/zustandBindings/promptForOptions.js +0 -55
  30. package/dist/cjs/src/generate/helpers/zustandBindings/writeApiClientFile.js +0 -50
  31. package/dist/cjs/src/generate/helpers/zustandBindings/writeInitializer.js +0 -46
  32. package/dist/cjs/src/generate/openapi/zustandBindings.js +0 -27
  33. package/dist/esm/src/generate/helpers/zustandBindings/printFinalStepsMessage.js +0 -36
  34. package/dist/esm/src/generate/helpers/zustandBindings/promptForOptions.js +0 -55
  35. package/dist/esm/src/generate/helpers/zustandBindings/writeApiClientFile.js +0 -50
  36. package/dist/esm/src/generate/helpers/zustandBindings/writeInitializer.js +0 -46
  37. package/dist/esm/src/generate/openapi/zustandBindings.js +0 -27
  38. package/dist/types/src/generate/helpers/zustandBindings/printFinalStepsMessage.d.ts +0 -2
  39. package/dist/types/src/generate/helpers/zustandBindings/promptForOptions.d.ts +0 -2
  40. package/dist/types/src/generate/helpers/zustandBindings/writeApiClientFile.d.ts +0 -5
  41. package/dist/types/src/generate/helpers/zustandBindings/writeInitializer.d.ts +0 -5
  42. package/dist/types/src/generate/openapi/zustandBindings.d.ts +0 -21
@@ -197,7 +197,10 @@ export default class PsychicController {
197
197
  };
198
198
  }
199
199
  /**
200
- * Gets the HTTP request headers from the Express request object.
200
+ * Gets the HTTP request headers from the Koa ctx object.
201
+ * We recommend using the #header method instead when looking
202
+ * to retreive the value for a specific header, since that method
203
+ * will be safe with regards to case sensitivity.
201
204
  *
202
205
  * @returns The request headers as a key-value object where header names are lowercase strings
203
206
  * and values can be strings, string arrays, or undefined.
@@ -216,6 +219,16 @@ export default class PsychicController {
216
219
  get headers() {
217
220
  return this.ctx.request.headers;
218
221
  }
222
+ /**
223
+ * returns the value for the requested header. This method is case insensitive.
224
+ * If the header requested is not found, a blank string is returned.
225
+ *
226
+ * @param headerName - the name of the header
227
+ * @returns string
228
+ */
229
+ header(headerName) {
230
+ return this.ctx.request.get(headerName);
231
+ }
219
232
  /**
220
233
  * Gets the combined parameters from the HTTP request. This includes URL parameters,
221
234
  * request body, and query string parameters merged together. The merge order is:
@@ -796,7 +809,7 @@ export default class PsychicController {
796
809
  * ```
797
810
  */
798
811
  redirect(path) {
799
- this.ctx.redirect(path);
812
+ this.koaRedirect(302, path);
800
813
  }
801
814
  // begin: http status codes
802
815
  /**
@@ -20,9 +20,9 @@ export default async function generateController({ fullyQualifiedControllerName,
20
20
  const allControllerNameParts = fullyQualifiedControllerName.split('/');
21
21
  const forAdmin = allControllerNameParts[0] === 'Admin';
22
22
  const forInternal = allControllerNameParts[0] === 'Internal';
23
- const controllerNameParts = (forAdmin || forInternal) ? [allControllerNameParts.shift()] : [];
23
+ const controllerNameParts = forAdmin || forInternal ? [allControllerNameParts.shift()] : [];
24
24
  for (let index = 0; index < allControllerNameParts.length; index++) {
25
- if (controllerNameParts.length > ((forAdmin || forInternal) ? 1 : 0)) {
25
+ if (controllerNameParts.length > (forAdmin || forInternal ? 1 : 0)) {
26
26
  // Write the ancestor controller
27
27
  const [baseAncestorName, baseAncestorImportStatement] = baseAncestorNameAndImport(controllerNameParts, forAdmin, forInternal, { forBaseController: true });
28
28
  if (baseAncestorName === undefined)
@@ -97,7 +97,7 @@ export default async function generateController({ fullyQualifiedControllerName,
97
97
  function baseAncestorNameAndImport(controllerNameParts, forAdmin, forInternal, { forBaseController }) {
98
98
  const maybeAncestorNameForBase = `${controllerNameParts.slice(0, controllerNameParts.length - 1).join('')}BaseController`;
99
99
  const dotFiles = forBaseController ? '..' : '.';
100
- return controllerNameParts.length === ((forAdmin || forInternal) ? 2 : 1)
100
+ return controllerNameParts.length === (forAdmin || forInternal ? 2 : 1)
101
101
  ? forAdmin
102
102
  ? [
103
103
  `AdminAuthedController`,
@@ -124,6 +124,7 @@ function processAttributeByType({ attributeType, attributeName, isArray, enumVal
124
124
  case 'string':
125
125
  case 'text':
126
126
  case 'citext':
127
+ case 'encrypted':
127
128
  processStringAttribute({
128
129
  attributeName,
129
130
  isArray,
@@ -267,7 +268,7 @@ function generateIndexActionSpec(options) {
267
268
  if (options.actionConfig.omitIndex)
268
269
  return '';
269
270
  const { path, pathParams, modelConfig, fullyQualifiedModelName, singular } = options;
270
- const subjectFunctionName = `index${pluralize(modelConfig.modelClassName)}`;
271
+ const subjectFunctionName = 'index';
271
272
  return `
272
273
 
273
274
  describe('GET index', () => {
@@ -304,7 +305,7 @@ function generateShowActionSpec(options) {
304
305
  if (options.actionConfig.omitShow)
305
306
  return '';
306
307
  const { path, pathParams, modelConfig, fullyQualifiedModelName, singular, attributeData } = options;
307
- const subjectFunctionName = `show${modelConfig.modelClassName}`;
308
+ const subjectFunctionName = 'show';
308
309
  const subjectFunction = singular
309
310
  ? `
310
311
  const ${subjectFunctionName} = async <StatusCode extends 200 | 400 | 404>(expectedStatus: StatusCode) => {
@@ -345,7 +346,7 @@ function generateCreateActionSpec(options) {
345
346
  if (options.actionConfig.omitCreate)
346
347
  return '';
347
348
  const { path, pathParams, modelConfig, fullyQualifiedModelName, singular, attributeData } = options;
348
- const subjectFunctionName = `create${modelConfig.modelClassName}`;
349
+ const subjectFunctionName = 'create';
349
350
  const uuidSetup = attributeData.uuidAttributes
350
351
  .map(attrName => {
351
352
  const isArray = attributeData.uuidArrayAttributes.includes(attrName);
@@ -396,7 +397,7 @@ function generateUpdateActionSpec(options) {
396
397
  if (options.actionConfig.omitUpdate)
397
398
  return '';
398
399
  const { path, pathParams, modelConfig, fullyQualifiedModelName, singular, attributeData } = options;
399
- const subjectFunctionName = `update${modelConfig.modelClassName}`;
400
+ const subjectFunctionName = 'update';
400
401
  const uuidSetup = attributeData.uuidAttributes
401
402
  .map(attrName => {
402
403
  const isArray = attributeData.uuidArrayAttributes.includes(attrName);
@@ -491,7 +492,7 @@ function generateDestroyActionSpec(options) {
491
492
  if (options.actionConfig.omitDestroy)
492
493
  return '';
493
494
  const { path, pathParams, modelConfig, fullyQualifiedModelName, singular } = options;
494
- const subjectFunctionName = `destroy${modelConfig.modelClassName}`;
495
+ const subjectFunctionName = 'destroy';
495
496
  const subjectFunction = singular
496
497
  ? `
497
498
  const ${subjectFunctionName} = async <StatusCode extends 204 | 400 | 404>(expectedStatus: StatusCode) => {
@@ -310,7 +310,6 @@ export default class OpenapiSegmentExpander {
310
310
  }
311
311
  let referencedSerializers = [];
312
312
  if (objectBodySegment.additionalProperties === false) {
313
- ;
314
313
  data.additionalProperties = false;
315
314
  }
316
315
  else if (objectBodySegment.additionalProperties) {
@@ -9,11 +9,9 @@ import PsychicApp from '../psychic-app/index.js';
9
9
  import openapiParamNamesForDreamClass from '../server/helpers/openapiParamNamesForDreamClass.js';
10
10
  import OpenapiSegmentExpander from './body-segment.js';
11
11
  import { DEFAULT_OPENAPI_RESPONSES } from './defaults.js';
12
- import cursorPaginationParamOpenapiProperty from './helpers/cursorPaginationParamOpenapiProperty.js';
13
12
  import { dreamColumnOpenapiShape } from './helpers/dreamColumnOpenapiShape.js';
14
13
  import openapiOpts from './helpers/openapiOpts.js';
15
14
  import openapiRoute from './helpers/openapiRoute.js';
16
- import paginationPageParamOpenapiProperty from './helpers/paginationPageParamOpenapiProperty.js';
17
15
  import safelyAttachCursorPaginationParamToRequestBodySegment from './helpers/safelyAttachCursorPaginationParamToRequestBodySegment.js';
18
16
  import safelyAttachPaginationParamToRequestBodySegment from './helpers/safelyAttachPaginationParamsToBodySegment.js';
19
17
  import SerializerOpenapiRenderer from './SerializerOpenapiRenderer.js';
@@ -464,37 +462,23 @@ export default class OpenapiEndpointRenderer {
464
462
  defaultRequestBody() {
465
463
  const bodyPaginationPageParam = this.paginate?.body;
466
464
  const bodyCursorPaginationParam = this.cursorPaginate?.body ?? this.scrollPaginate?.body;
465
+ const paramName = bodyPaginationPageParam || bodyCursorPaginationParam;
466
+ if (!paramName)
467
+ return undefined;
468
+ let schema = undefined;
467
469
  if (bodyPaginationPageParam) {
468
- return {
469
- content: {
470
- 'application/json': {
471
- schema: {
472
- type: 'object',
473
- properties: {
474
- [bodyPaginationPageParam]: paginationPageParamOpenapiProperty(),
475
- },
476
- },
477
- },
478
- },
479
- };
470
+ schema = safelyAttachPaginationParamToRequestBodySegment(bodyPaginationPageParam, schema);
480
471
  }
481
472
  else if (bodyCursorPaginationParam) {
482
- return {
483
- content: {
484
- 'application/json': {
485
- schema: {
486
- type: 'object',
487
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
488
- properties: {
489
- [bodyCursorPaginationParam]: cursorPaginationParamOpenapiProperty(),
490
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
491
- },
492
- },
493
- },
494
- },
495
- };
473
+ schema = safelyAttachCursorPaginationParamToRequestBodySegment(bodyCursorPaginationParam, schema);
496
474
  }
497
- return undefined;
475
+ return {
476
+ content: {
477
+ 'application/json': {
478
+ schema: schema,
479
+ },
480
+ },
481
+ };
498
482
  }
499
483
  /**
500
484
  * @internal
@@ -1,8 +1,9 @@
1
1
  import cursorPaginationParamOpenapiProperty from './cursorPaginationParamOpenapiProperty.js';
2
+ import { safelyAttachParamToRequestBodySegment } from './safelyAttachPaginationParamsToBodySegment.js';
2
3
  /**
3
4
  * @internal
4
5
  *
5
- * Used to carefully bind implicit pagination params
6
+ * Used to carefully bind implicit cursor pagination params
6
7
  * to the requestBody properties. It will not apply
7
8
  * the pagination param unless the provided bodySegment
8
9
  * is:
@@ -14,18 +15,5 @@ import cursorPaginationParamOpenapiProperty from './cursorPaginationParamOpenapi
14
15
  * what was given to it, without any modifications
15
16
  */
16
17
  export default function safelyAttachCursorPaginationParamToRequestBodySegment(paramName, bodySegment) {
17
- bodySegment ||= {
18
- type: 'object',
19
- properties: {},
20
- };
21
- if (bodySegment.type === 'object') {
22
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
23
- ;
24
- bodySegment.properties = {
25
- ...bodySegment.properties,
26
- [paramName]: cursorPaginationParamOpenapiProperty(),
27
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
- };
29
- }
30
- return bodySegment;
18
+ return safelyAttachParamToRequestBodySegment(paramName, cursorPaginationParamOpenapiProperty(), bodySegment);
31
19
  }
@@ -14,15 +14,28 @@ import paginationPageParamOpenapiProperty from './paginationPageParamOpenapiProp
14
14
  * what was given to it, without any modifications
15
15
  */
16
16
  export default function safelyAttachPaginationParamToRequestBodySegment(paramName, bodySegment) {
17
+ return safelyAttachParamToRequestBodySegment(paramName, paginationPageParamOpenapiProperty(), bodySegment);
18
+ }
19
+ /**
20
+ * @internal
21
+ *
22
+ * Generic version: attaches any OpenAPI property definition to a body segment.
23
+ */
24
+ export function safelyAttachParamToRequestBodySegment(paramName,
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ property, bodySegment) {
17
27
  bodySegment ||= {
18
28
  type: 'object',
19
29
  properties: {},
20
30
  };
21
31
  if (bodySegment.type === 'object') {
32
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
22
33
  ;
23
34
  bodySegment.properties = {
24
35
  ...bodySegment.properties,
25
- [paramName]: paginationPageParamOpenapiProperty(),
36
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
37
+ [paramName]: property,
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
39
  };
27
40
  }
28
41
  return bodySegment;
@@ -58,13 +58,14 @@ function inferControllerOrFail(filteredNamespaces, opts) {
58
58
  });
59
59
  return controller;
60
60
  }
61
- export function applyResourcesAction(path, action, routingMechanism, options) {
61
+ export function applyResourcefulAction(path, action, routingMechanism, options, plural) {
62
62
  const controller = options?.controller ||
63
63
  lookupControllerOrFail(routingMechanism, {
64
64
  path: action,
65
65
  httpMethod: httpMethodFromResourcefulAction(action),
66
66
  resourceName: path,
67
67
  });
68
+ const memberPath = plural ? `${path}/:id` : path;
68
69
  switch (action) {
69
70
  case 'index':
70
71
  routingMechanism.get(path, controller, 'index');
@@ -73,41 +74,24 @@ export function applyResourcesAction(path, action, routingMechanism, options) {
73
74
  routingMechanism.post(path, controller, 'create');
74
75
  break;
75
76
  case 'update':
76
- routingMechanism.put(`${path}/:id`, controller, 'update');
77
- routingMechanism.patch(`${path}/:id`, controller, 'update');
77
+ routingMechanism.put(memberPath, controller, 'update');
78
+ routingMechanism.patch(memberPath, controller, 'update');
78
79
  break;
79
80
  case 'show':
80
- routingMechanism.get(`${path}/:id`, controller, 'show');
81
+ routingMechanism.get(memberPath, controller, 'show');
81
82
  break;
82
83
  case 'destroy':
83
- routingMechanism.delete(`${path}/:id`, controller, 'destroy');
84
+ routingMechanism.delete(memberPath, controller, 'destroy');
84
85
  break;
85
86
  }
86
87
  }
88
+ /** @deprecated Use applyResourcefulAction with plural parameter instead */
89
+ export function applyResourcesAction(path, action, routingMechanism, options) {
90
+ return applyResourcefulAction(path, action, routingMechanism, options, true);
91
+ }
92
+ /** @deprecated Use applyResourcefulAction with plural parameter instead */
87
93
  export function applyResourceAction(path, action, routingMechanism, options) {
88
- const controller = options?.controller ||
89
- lookupControllerOrFail(routingMechanism, {
90
- path: action,
91
- httpMethod: httpMethodFromResourcefulAction(action),
92
- resourceName: path,
93
- });
94
- switch (action) {
95
- case 'create':
96
- routingMechanism.post(path, controller, 'create');
97
- break;
98
- case 'update':
99
- routingMechanism.put(path, controller, 'update');
100
- routingMechanism.patch(path, controller, 'update');
101
- break;
102
- case 'show':
103
- routingMechanism.get(path, controller, 'show');
104
- break;
105
- case 'destroy':
106
- routingMechanism.delete(path, controller, 'destroy');
107
- break;
108
- default:
109
- throw new Error(`unsupported resource method type: ${action}`);
110
- }
94
+ return applyResourcefulAction(path, action, routingMechanism, options, false);
111
95
  }
112
96
  /**
113
97
  * Converts OpenAPI-style route parameters to Express.js-style parameters
@@ -10,7 +10,7 @@ import CannotCommitRoutesWithoutKoaApp from '../error/router/cannot-commit-route
10
10
  import EnvInternal from '../helpers/EnvInternal.js';
11
11
  import errorIsRescuableHttpError from '../helpers/error/errorIsRescuableHttpError.js';
12
12
  import PsychicApp from '../psychic-app/index.js';
13
- import { applyResourceAction, applyResourcesAction, convertRouteParams, lookupControllerOrFail, routePath, } from '../router/helpers.js';
13
+ import { applyResourcefulAction, convertRouteParams, lookupControllerOrFail, routePath, } from '../router/helpers.js';
14
14
  import RouteManager from './route-manager.js';
15
15
  import { ResourceMethods, ResourcesMethods, } from './types.js';
16
16
  const ERROR_LOGGING_DEPTH = 6;
@@ -170,12 +170,7 @@ suggested fix: "${convertRouteParams(path)}"
170
170
  this.runNestedCallbacks(path, nestedRouter, cb, { asMember: plural, resourceful: true });
171
171
  this.currentNamespaces = originalCurrentNamespaces;
172
172
  resourceMethods.forEach(action => {
173
- if (plural) {
174
- applyResourcesAction(path, action, nestedRouter, options);
175
- }
176
- else {
177
- applyResourceAction(path, action, nestedRouter, options);
178
- }
173
+ applyResourcefulAction(path, action, nestedRouter, options, plural);
179
174
  });
180
175
  }
181
176
  runNestedCallbacks(namespace, nestedRouter, cb, { asMember = false, resourceful = false, treatNamespaceAsScope = false, } = {}) {
@@ -48,97 +48,31 @@ export default class Params {
48
48
  continue;
49
49
  const columnMetadata = columns[columnName];
50
50
  try {
51
- switch (columnMetadata?.dbType) {
52
- case 'bigint':
53
- case 'bigint[]':
54
- case 'boolean':
55
- case 'boolean[]':
56
- case 'date':
57
- case 'date[]':
58
- case 'integer':
59
- case 'integer[]':
60
- case 'uuid':
61
- case 'uuid[]':
62
- case 'json':
63
- case 'json[]':
64
- returnObj[columnName] = this.cast(params, columnName.toString(), columnMetadata.dbType, { allowNull: columnMetadata.allowNull });
65
- break;
66
- case 'character varying':
67
- case 'citext':
68
- case 'text':
69
- returnObj[columnName] = this.cast(params, columnName.toString(), 'string', { allowNull: columnMetadata.allowNull });
70
- break;
71
- case 'character varying[]':
72
- case 'citext[]':
73
- case 'text[]':
74
- returnObj[columnName] = this.cast(params, columnName.toString(), 'string[]', {
51
+ const castType = DB_TYPE_TO_CAST_TYPE[columnMetadata?.dbType];
52
+ if (castType) {
53
+ returnObj[columnName] = this.cast(params, columnName.toString(), castType, { allowNull: columnMetadata.allowNull });
54
+ }
55
+ else if (dreamClass.isVirtualColumn(columnName)) {
56
+ returnObj[columnName] = params[columnName];
57
+ }
58
+ else if (columnMetadata?.enumValues) {
59
+ const paramValue = params[columnName];
60
+ if (columnMetadata.isArray) {
61
+ if (!Array.isArray(paramValue))
62
+ returnObj[columnName] = ['expected an array of enum values'];
63
+ returnObj[columnName] = paramValue.map(p => {
64
+ return new this(params).cast(columnName.toString(), p, 'string', {
65
+ allowNull: columnMetadata.allowNull,
66
+ enum: columnMetadata.enumValues,
67
+ });
68
+ });
69
+ }
70
+ else {
71
+ returnObj[columnName] = this.cast(params, columnName.toString(), 'string', {
75
72
  allowNull: columnMetadata.allowNull,
73
+ enum: columnMetadata.enumValues,
76
74
  });
77
- break;
78
- case 'timestamp':
79
- case 'timestamp with time zone':
80
- case 'timestamp without time zone':
81
- returnObj[columnName] = this.cast(params, columnName.toString(), 'datetime', { allowNull: columnMetadata.allowNull });
82
- break;
83
- case 'timestamp[]':
84
- case 'timestamp with time zone[]':
85
- case 'timestamp without time zone[]':
86
- returnObj[columnName] = this.cast(params, columnName.toString(), 'datetime[]', { allowNull: columnMetadata.allowNull });
87
- break;
88
- case 'time':
89
- case 'time without time zone':
90
- returnObj[columnName] = this.cast(params, columnName.toString(), 'time', { allowNull: columnMetadata.allowNull });
91
- break;
92
- case 'time[]':
93
- case 'time without time zone[]':
94
- returnObj[columnName] = this.cast(params, columnName.toString(), 'time[]', { allowNull: columnMetadata.allowNull });
95
- break;
96
- case 'timetz':
97
- case 'time with time zone':
98
- returnObj[columnName] = this.cast(params, columnName.toString(), 'timetz', { allowNull: columnMetadata.allowNull });
99
- break;
100
- case 'timetz[]':
101
- case 'time with time zone[]':
102
- returnObj[columnName] = this.cast(params, columnName.toString(), 'timetz[]', { allowNull: columnMetadata.allowNull });
103
- break;
104
- case 'jsonb':
105
- returnObj[columnName] = this.cast(params, columnName.toString(), 'json', { allowNull: columnMetadata.allowNull });
106
- break;
107
- case 'jsonb[]':
108
- returnObj[columnName] = this.cast(params, columnName.toString(), 'json[]', { allowNull: columnMetadata.allowNull });
109
- break;
110
- case 'numeric':
111
- returnObj[columnName] = this.cast(params, columnName.toString(), 'number', { allowNull: columnMetadata.allowNull });
112
- break;
113
- case 'numeric[]':
114
- returnObj[columnName] = this.cast(params, columnName.toString(), 'number[]', { allowNull: columnMetadata.allowNull });
115
- break;
116
- default:
117
- if (dreamClass.isVirtualColumn(columnName))
118
- returnObj[columnName] = params[columnName];
119
- if (columnMetadata?.enumValues) {
120
- const paramValue = params[columnName];
121
- if (columnMetadata.isArray) {
122
- if (!Array.isArray(paramValue))
123
- returnObj[columnName] = ['expected an array of enum values'];
124
- returnObj[columnName] = paramValue.map(p => {
125
- return new this(params).cast(columnName.toString(), p,
126
- // casting to allow enum handling at lower level
127
- 'string', {
128
- allowNull: columnMetadata.allowNull,
129
- enum: columnMetadata.enumValues,
130
- });
131
- });
132
- }
133
- else {
134
- returnObj[columnName] = this.cast(params, columnName.toString(),
135
- // casting to allow enum handling at lower level
136
- 'string', {
137
- allowNull: columnMetadata.allowNull,
138
- enum: columnMetadata.enumValues,
139
- });
140
- }
141
- }
75
+ }
142
76
  }
143
77
  }
144
78
  catch (err) {
@@ -402,6 +336,54 @@ export default class Params {
402
336
  throw new ParamValidationError(paramName, [message]);
403
337
  }
404
338
  }
339
+ /**
340
+ * Maps PostgreSQL database column types to Psychic cast types.
341
+ * Used by Params.for() to determine how to validate and cast each column value.
342
+ */
343
+ const DB_TYPE_TO_CAST_TYPE = {
344
+ // identity mappings (db type matches cast type)
345
+ bigint: 'bigint',
346
+ 'bigint[]': 'bigint[]',
347
+ boolean: 'boolean',
348
+ 'boolean[]': 'boolean[]',
349
+ date: 'date',
350
+ 'date[]': 'date[]',
351
+ integer: 'integer',
352
+ 'integer[]': 'integer[]',
353
+ uuid: 'uuid',
354
+ 'uuid[]': 'uuid[]',
355
+ json: 'json',
356
+ 'json[]': 'json[]',
357
+ // text variants → string
358
+ 'character varying': 'string',
359
+ citext: 'string',
360
+ text: 'string',
361
+ 'character varying[]': 'string[]',
362
+ 'citext[]': 'string[]',
363
+ 'text[]': 'string[]',
364
+ // timestamp variants → datetime
365
+ timestamp: 'datetime',
366
+ 'timestamp with time zone': 'datetime',
367
+ 'timestamp without time zone': 'datetime',
368
+ 'timestamp[]': 'datetime[]',
369
+ 'timestamp with time zone[]': 'datetime[]',
370
+ 'timestamp without time zone[]': 'datetime[]',
371
+ // time variants
372
+ time: 'time',
373
+ 'time without time zone': 'time',
374
+ 'time[]': 'time[]',
375
+ 'time without time zone[]': 'time[]',
376
+ timetz: 'timetz',
377
+ 'time with time zone': 'timetz',
378
+ 'timetz[]': 'timetz[]',
379
+ 'time with time zone[]': 'timetz[]',
380
+ // jsonb → json
381
+ jsonb: 'json',
382
+ 'jsonb[]': 'json[]',
383
+ // numeric → number
384
+ numeric: 'number',
385
+ 'numeric[]': 'number[]',
386
+ };
405
387
  const typeToErrorMap = {
406
388
  bigint: 'expected bigint',
407
389
  boolean: 'expected boolean',
@@ -111,7 +111,10 @@ export default class PsychicController {
111
111
  action: string;
112
112
  });
113
113
  /**
114
- * Gets the HTTP request headers from the Express request object.
114
+ * Gets the HTTP request headers from the Koa ctx object.
115
+ * We recommend using the #header method instead when looking
116
+ * to retreive the value for a specific header, since that method
117
+ * will be safe with regards to case sensitivity.
115
118
  *
116
119
  * @returns The request headers as a key-value object where header names are lowercase strings
117
120
  * and values can be strings, string arrays, or undefined.
@@ -128,6 +131,14 @@ export default class PsychicController {
128
131
  * ```
129
132
  */
130
133
  get headers(): import("http").IncomingHttpHeaders;
134
+ /**
135
+ * returns the value for the requested header. This method is case insensitive.
136
+ * If the header requested is not found, a blank string is returned.
137
+ *
138
+ * @param headerName - the name of the header
139
+ * @returns string
140
+ */
141
+ header(headerName: string): string;
131
142
  /**
132
143
  * Gets the combined parameters from the HTTP request. This includes URL parameters,
133
144
  * request body, and query string parameters merged together. The merge order is:
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @internal
3
3
  *
4
- * Used to carefully bind implicit pagination params
4
+ * Used to carefully bind implicit cursor pagination params
5
5
  * to the requestBody properties. It will not apply
6
6
  * the pagination param unless the provided bodySegment
7
7
  * is:
@@ -13,3 +13,9 @@
13
13
  * what was given to it, without any modifications
14
14
  */
15
15
  export default function safelyAttachPaginationParamToRequestBodySegment<T>(paramName: string, bodySegment: T): T;
16
+ /**
17
+ * @internal
18
+ *
19
+ * Generic version: attaches any OpenAPI property definition to a body segment.
20
+ */
21
+ export declare function safelyAttachParamToRequestBodySegment<T>(paramName: string, property: any, bodySegment: T): T;
@@ -21,7 +21,10 @@ export interface NamespaceConfig {
21
21
  resourceful: boolean;
22
22
  isScope: boolean;
23
23
  }
24
+ export declare function applyResourcefulAction(path: string, action: ResourcesMethodType, routingMechanism: RoutingMechanism, options?: ResourcesOptions, plural?: boolean): void;
25
+ /** @deprecated Use applyResourcefulAction with plural parameter instead */
24
26
  export declare function applyResourcesAction(path: string, action: ResourcesMethodType, routingMechanism: RoutingMechanism, options?: ResourcesOptions): void;
27
+ /** @deprecated Use applyResourcefulAction with plural parameter instead */
25
28
  export declare function applyResourceAction(path: string, action: ResourcesMethodType, routingMechanism: RoutingMechanism, options?: ResourcesOptions): void;
26
29
  /**
27
30
  * Converts OpenAPI-style route parameters to Express.js-style parameters