@rvoh/psychic 3.0.0-alpha.8 → 3.0.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/cli/index.js +4 -22
- package/dist/cjs/src/controller/index.js +1 -1
- package/dist/cjs/src/generate/controller.js +3 -3
- package/dist/cjs/src/generate/helpers/generateResourceControllerSpecContent.js +6 -5
- package/dist/cjs/src/openapi-renderer/body-segment.js +0 -1
- package/dist/cjs/src/openapi-renderer/endpoint.js +13 -29
- package/dist/cjs/src/openapi-renderer/helpers/safelyAttachCursorPaginationParamToRequestBodySegment.js +3 -15
- package/dist/cjs/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +14 -1
- package/dist/cjs/src/router/helpers.js +12 -28
- package/dist/cjs/src/router/index.js +2 -7
- package/dist/cjs/src/server/params.js +71 -89
- package/dist/esm/src/cli/index.js +4 -22
- package/dist/esm/src/controller/index.js +1 -1
- package/dist/esm/src/generate/controller.js +3 -3
- package/dist/esm/src/generate/helpers/generateResourceControllerSpecContent.js +6 -5
- package/dist/esm/src/openapi-renderer/body-segment.js +0 -1
- package/dist/esm/src/openapi-renderer/endpoint.js +13 -29
- package/dist/esm/src/openapi-renderer/helpers/safelyAttachCursorPaginationParamToRequestBodySegment.js +3 -15
- package/dist/esm/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +14 -1
- package/dist/esm/src/router/helpers.js +12 -28
- package/dist/esm/src/router/index.js +2 -7
- package/dist/esm/src/server/params.js +71 -89
- package/dist/types/src/openapi-renderer/helpers/safelyAttachCursorPaginationParamToRequestBodySegment.d.ts +1 -1
- package/dist/types/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.d.ts +6 -0
- package/dist/types/src/router/helpers.d.ts +3 -0
- package/package.json +30 -20
- package/dist/cjs/src/generate/helpers/zustandBindings/printFinalStepsMessage.js +0 -36
- package/dist/cjs/src/generate/helpers/zustandBindings/promptForOptions.js +0 -55
- package/dist/cjs/src/generate/helpers/zustandBindings/writeApiClientFile.js +0 -50
- package/dist/cjs/src/generate/helpers/zustandBindings/writeInitializer.js +0 -46
- package/dist/cjs/src/generate/openapi/zustandBindings.js +0 -27
- package/dist/esm/src/generate/helpers/zustandBindings/printFinalStepsMessage.js +0 -36
- package/dist/esm/src/generate/helpers/zustandBindings/promptForOptions.js +0 -55
- package/dist/esm/src/generate/helpers/zustandBindings/writeApiClientFile.js +0 -50
- package/dist/esm/src/generate/helpers/zustandBindings/writeInitializer.js +0 -46
- package/dist/esm/src/generate/openapi/zustandBindings.js +0 -27
- package/dist/types/src/generate/helpers/zustandBindings/printFinalStepsMessage.d.ts +0 -2
- package/dist/types/src/generate/helpers/zustandBindings/promptForOptions.d.ts +0 -2
- package/dist/types/src/generate/helpers/zustandBindings/writeApiClientFile.d.ts +0 -5
- package/dist/types/src/generate/helpers/zustandBindings/writeInitializer.d.ts +0 -5
- package/dist/types/src/generate/openapi/zustandBindings.d.ts +0 -21
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
495
|
+
const subjectFunctionName = 'destroy';
|
|
495
496
|
const subjectFunction = singular
|
|
496
497
|
? `
|
|
497
498
|
const ${subjectFunctionName} = async <StatusCode extends 204 | 400 | 404>(expectedStatus: StatusCode) => {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
77
|
-
routingMechanism.patch(
|
|
77
|
+
routingMechanism.put(memberPath, controller, 'update');
|
|
78
|
+
routingMechanism.patch(memberPath, controller, 'update');
|
|
78
79
|
break;
|
|
79
80
|
case 'show':
|
|
80
|
-
routingMechanism.get(
|
|
81
|
+
routingMechanism.get(memberPath, controller, 'show');
|
|
81
82
|
break;
|
|
82
83
|
case 'destroy':
|
|
83
|
-
routingMechanism.delete(
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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',
|
package/dist/types/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.d.ts
CHANGED
|
@@ -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
|
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": "3.0.0
|
|
5
|
+
"version": "3.0.0",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -47,6 +47,24 @@
|
|
|
47
47
|
"files": [
|
|
48
48
|
"dist/**/*"
|
|
49
49
|
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"client": "cd client && pnpm start",
|
|
52
|
+
"client:fspec": "cd client && VITE_PSYCHIC_ENV=test BROWSER=none pnpm start",
|
|
53
|
+
"psy": "NODE_ENV=${NODE_ENV:-test} pnpm psyts",
|
|
54
|
+
"psyjs": "node ./dist/test-app/src/cli/index.js",
|
|
55
|
+
"psyts": "NODE_ENV=${NODE_ENV:-test} tsx ./test-app/src/cli/index.ts",
|
|
56
|
+
"gpsy": "tsx ./global-cli/main.ts",
|
|
57
|
+
"build": "echo \"building cjs...\" && rm -rf dist && tsc -p ./tsconfig.cjs.build.json && echo \"building esm...\" && tsc -p ./tsconfig.esm.build.json",
|
|
58
|
+
"build:test-app": "rm -rf dist && echo \"building test app to esm...\" && tsc -p ./tsconfig.esm.build.test-app.json && echo \"building test app to cjs...\" && tsc -p ./tsconfig.cjs.build.test-app.json",
|
|
59
|
+
"types:esm:trace": "rm -rf dist && tsc -p ./tsconfig.esm.build.json --generateTrace ./typetrace --diagnostics && pnpm analyze-trace ./typetrace --skipMillis 100 --forceMillis 300",
|
|
60
|
+
"dev": "nodemon --quiet --no-stdin",
|
|
61
|
+
"console": "tsx ./test-app/src/conf/repl.ts",
|
|
62
|
+
"uspec": "vitest --config ./spec/unit/vite.config.ts",
|
|
63
|
+
"fspec": "vitest run --config=./spec/features/vite.config.ts",
|
|
64
|
+
"format": "pnpm prettier . --write",
|
|
65
|
+
"lint": "pnpm eslint --no-warn-ignored \"src/**/*.ts\" \"spec/**/*.ts\" \"test-app/**/*.ts\" && pnpm prettier . --check",
|
|
66
|
+
"prepack": "pnpm build"
|
|
67
|
+
},
|
|
50
68
|
"dependencies": {
|
|
51
69
|
"ajv": "^8.17.1",
|
|
52
70
|
"ajv-formats": "^3.0.1",
|
|
@@ -74,9 +92,9 @@
|
|
|
74
92
|
"@koa/cors": "^5.0.0",
|
|
75
93
|
"@koa/etag": "^5.0.2",
|
|
76
94
|
"@koa/router": "^15.3.1",
|
|
77
|
-
"@rvoh/dream": "^2.4.0
|
|
95
|
+
"@rvoh/dream": "^2.4.0",
|
|
78
96
|
"@rvoh/dream-spec-helpers": "^2.1.1",
|
|
79
|
-
"@rvoh/psychic-spec-helpers": "3.0.0
|
|
97
|
+
"@rvoh/psychic-spec-helpers": "3.0.0",
|
|
80
98
|
"@types/koa": "^3.0.1",
|
|
81
99
|
"@types/koa-bodyparser": "^4.3.12",
|
|
82
100
|
"@types/koa-conditional-get": "^2.0.3",
|
|
@@ -120,21 +138,13 @@
|
|
|
120
138
|
"vitest": "^4.0.9",
|
|
121
139
|
"winston": "^3.14.2"
|
|
122
140
|
},
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"build:test-app": "rm -rf dist && echo \"building test app to esm...\" && tsc -p ./tsconfig.esm.build.test-app.json && echo \"building test app to cjs...\" && tsc -p ./tsconfig.cjs.build.test-app.json",
|
|
132
|
-
"types:esm:trace": "rm -rf dist && tsc -p ./tsconfig.esm.build.json --generateTrace ./typetrace --diagnostics && pnpm analyze-trace ./typetrace --skipMillis 100 --forceMillis 300",
|
|
133
|
-
"dev": "nodemon --quiet --no-stdin",
|
|
134
|
-
"console": "tsx ./test-app/src/conf/repl.ts",
|
|
135
|
-
"uspec": "vitest --config ./spec/unit/vite.config.ts",
|
|
136
|
-
"fspec": "vitest run --config=./spec/features/vite.config.ts",
|
|
137
|
-
"format": "pnpm prettier . --write",
|
|
138
|
-
"lint": "pnpm eslint --no-warn-ignored \"src/**/*.ts\" \"spec/**/*.ts\" \"test-app/**/*.ts\" && pnpm prettier . --check"
|
|
141
|
+
"packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402",
|
|
142
|
+
"pnpm": {
|
|
143
|
+
"overrides": {
|
|
144
|
+
"diff": ">=8.0.3",
|
|
145
|
+
"minimatch@3": "3.1.3",
|
|
146
|
+
"minimatch@5": "5.1.7",
|
|
147
|
+
"minimatch@9": "9.0.6"
|
|
148
|
+
}
|
|
139
149
|
}
|
|
140
|
-
}
|
|
150
|
+
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { DreamCLI } from '@rvoh/dream/system';
|
|
2
|
-
import { camelize } from '@rvoh/dream/utils';
|
|
3
|
-
import colorize from '../../../cli/helpers/colorize.js';
|
|
4
|
-
export default function printFinalStepsMessage(opts) {
|
|
5
|
-
const clientFile = `${opts.outputDir}/${camelize(opts.exportName)}.ts`;
|
|
6
|
-
const importLine = colorize(`+ import { ${opts.exportName} } from '${clientFile}'`, { color: 'green' });
|
|
7
|
-
DreamCLI.logger.log(`
|
|
8
|
-
Finished generating zustand + openapi-fetch bindings for your application,
|
|
9
|
-
but to wire them into your app, we're going to need your help.
|
|
10
|
-
|
|
11
|
-
First, you will need to be sure to sync, so that the new openapi
|
|
12
|
-
types are sent over to your client application.
|
|
13
|
-
|
|
14
|
-
Next, you can use the generated api client in your zustand stores, i.e.
|
|
15
|
-
|
|
16
|
-
${importLine}
|
|
17
|
-
|
|
18
|
-
${colorize(`+ import { create } from 'zustand'`, { color: 'green' })}
|
|
19
|
-
|
|
20
|
-
interface MyState {
|
|
21
|
-
items: Item[]
|
|
22
|
-
loading: boolean
|
|
23
|
-
fetchItems: () => Promise<void>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const useMyStore = create<MyState>((set) => ({
|
|
27
|
-
items: [],
|
|
28
|
-
loading: false,
|
|
29
|
-
fetchItems: async () => {
|
|
30
|
-
set({ loading: true })
|
|
31
|
-
const { data } = await ${opts.exportName}.GET('/items')
|
|
32
|
-
set({ items: data ?? [], loading: false })
|
|
33
|
-
},
|
|
34
|
-
}))
|
|
35
|
-
`, { logPrefix: '' });
|
|
36
|
-
}
|