@rvoh/psychic 2.3.8 → 2.3.9
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 -0
- package/dist/cjs/src/controller/index.js +4 -0
- package/dist/cjs/src/devtools/helpers/launchDevServer.js +16 -1
- package/dist/cjs/src/error/openapi/UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute.js +44 -0
- package/dist/cjs/src/openapi-renderer/SerializerOpenapiRenderer.js +2 -2
- package/dist/cjs/src/openapi-renderer/endpoint.js +2 -2
- package/dist/cjs/src/openapi-renderer/helpers/{dreamAttributeOpenapiShape.js → dreamColumnOpenapiShape.js} +19 -6
- package/dist/cjs/src/server/params.js +56 -3
- package/dist/esm/src/cli/index.js +4 -0
- package/dist/esm/src/controller/index.js +4 -0
- package/dist/esm/src/devtools/helpers/launchDevServer.js +16 -1
- package/dist/esm/src/error/openapi/UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute.js +44 -0
- package/dist/esm/src/openapi-renderer/SerializerOpenapiRenderer.js +2 -2
- package/dist/esm/src/openapi-renderer/endpoint.js +2 -2
- package/dist/esm/src/openapi-renderer/helpers/{dreamAttributeOpenapiShape.js → dreamColumnOpenapiShape.js} +19 -6
- package/dist/esm/src/server/params.js +56 -3
- package/dist/types/src/controller/index.d.ts +1 -1
- package/dist/types/src/devtools/helpers/launchDevServer.d.ts +2 -1
- package/dist/types/src/error/openapi/UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute.d.ts +7 -0
- package/dist/types/src/openapi-renderer/helpers/{dreamAttributeOpenapiShape.d.ts → dreamColumnOpenapiShape.d.ts} +1 -1
- package/dist/types/src/server/params.d.ts +2 -2
- package/package.json +4 -4
|
@@ -6,7 +6,7 @@ import UnexpectedUndefined from '../../error/UnexpectedUndefined.js';
|
|
|
6
6
|
import PsychicApp from '../../psychic-app/index.js';
|
|
7
7
|
const devServerProcesses = {};
|
|
8
8
|
const debugEnabled = debuglog('psychic').enabled;
|
|
9
|
-
export async function launchDevServer(key, { port = 3000, cmd = 'pnpm client', timeout = 5000 } = {}) {
|
|
9
|
+
export async function launchDevServer(key, { port = 3000, cmd = 'pnpm client', timeout = 5000, onStdOut } = {}) {
|
|
10
10
|
if (devServerProcesses[key])
|
|
11
11
|
return;
|
|
12
12
|
if (debugEnabled)
|
|
@@ -20,6 +20,21 @@ export async function launchDevServer(key, { port = 3000, cmd = 'pnpm client', t
|
|
|
20
20
|
...process.env,
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
+
// NOTE: adding this stdout spy so that
|
|
24
|
+
// when this cli utility runs node commands,
|
|
25
|
+
// it can properly hijack the stdout from the command
|
|
26
|
+
proc.stdout?.on('data', chunk => {
|
|
27
|
+
const txt = chunk?.toString()?.trim();
|
|
28
|
+
if (typeof txt !== 'string' || !txt)
|
|
29
|
+
return;
|
|
30
|
+
if (onStdOut) {
|
|
31
|
+
onStdOut(txt);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.log(txt);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
23
38
|
devServerProcesses[key] = proc;
|
|
24
39
|
await waitForPort(key, port, timeout);
|
|
25
40
|
proc.on('error', err => {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export default class UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute extends Error {
|
|
2
|
+
source;
|
|
3
|
+
attributeName;
|
|
4
|
+
dbType;
|
|
5
|
+
constructor(source, attributeName, dbType) {
|
|
6
|
+
super();
|
|
7
|
+
this.source = source;
|
|
8
|
+
this.attributeName = attributeName;
|
|
9
|
+
this.dbType = dbType;
|
|
10
|
+
}
|
|
11
|
+
get message() {
|
|
12
|
+
return `
|
|
13
|
+
While trying to compute the openapi shape for either a serializer or a controller's
|
|
14
|
+
request body, we ran into a db type that we didn't know how to automatically infer the
|
|
15
|
+
openapi shape for. In these cases, we recommend that you provide an explicit openapi
|
|
16
|
+
shape for these attributes, so that the openapi shape can be properly generated.
|
|
17
|
+
|
|
18
|
+
source: ${this.source}
|
|
19
|
+
attribute: ${this.attributeName}
|
|
20
|
+
unexpected db type: ${this.dbType}
|
|
21
|
+
|
|
22
|
+
If the culprit is a serializer attribute, you should provide an explicit openapi definition
|
|
23
|
+
to the attribute causing your problems, like so:
|
|
24
|
+
|
|
25
|
+
.attribute('${this.attributeName}', { openapi: { type: 'string' }})
|
|
26
|
+
|
|
27
|
+
If, instead, it is a controller's request body causing your problems, identify the controller
|
|
28
|
+
method responsible for this exception, and ensure that the openapi request body shape is explicitly
|
|
29
|
+
defined, so that you do not force psychic to autocompute the openapi body shape for this endpoint.
|
|
30
|
+
|
|
31
|
+
@OpenAPI(MyModel, {
|
|
32
|
+
requestBody: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
${this.attributeName}: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -14,7 +14,7 @@ import NoSerializerFoundForRendersOneAndMany from '../error/openapi/NoSerializer
|
|
|
14
14
|
import ObjectSerializerRendersOneAndManyRequireClassType from '../error/openapi/ObjectSerializerRendersOneAndManyRequireClassType.js';
|
|
15
15
|
import allSerializersFromHandWrittenOpenapi from './helpers/allSerializersFromHandWrittenOpenapi.js';
|
|
16
16
|
import allSerializersToRefsInOpenapi from './helpers/allSerializersToRefsInOpenapi.js';
|
|
17
|
-
import { dreamColumnOpenapiShape } from './helpers/
|
|
17
|
+
import { dreamColumnOpenapiShape } from './helpers/dreamColumnOpenapiShape.js';
|
|
18
18
|
import openapiShorthandToOpenapi from './helpers/openapiShorthandToOpenapi.js';
|
|
19
19
|
const NULL_OBJECT_OPENAPI = { type: 'null' };
|
|
20
20
|
export default class SerializerOpenapiRenderer {
|
|
@@ -153,7 +153,7 @@ export default class SerializerOpenapiRenderer {
|
|
|
153
153
|
target = DataTypeForOpenapi;
|
|
154
154
|
}
|
|
155
155
|
accumulator[outputAttributeName] = allSerializersToRefsInOpenapi(target?.isDream
|
|
156
|
-
? dreamColumnOpenapiShape(target, attribute.name, openapi, {
|
|
156
|
+
? dreamColumnOpenapiShape(this.serializer.globalName, target, attribute.name, openapi, {
|
|
157
157
|
suppressResponseEnums: this.suppressResponseEnums,
|
|
158
158
|
})
|
|
159
159
|
: openapiShorthandToOpenapi(openapi));
|
|
@@ -10,7 +10,7 @@ import openapiParamNamesForDreamClass from '../server/helpers/openapiParamNamesF
|
|
|
10
10
|
import OpenapiSegmentExpander from './body-segment.js';
|
|
11
11
|
import { DEFAULT_OPENAPI_RESPONSES } from './defaults.js';
|
|
12
12
|
import cursorPaginationParamOpenapiProperty from './helpers/cursorPaginationParamOpenapiProperty.js';
|
|
13
|
-
import { dreamColumnOpenapiShape } from './helpers/
|
|
13
|
+
import { dreamColumnOpenapiShape } from './helpers/dreamColumnOpenapiShape.js';
|
|
14
14
|
import openapiOpts from './helpers/openapiOpts.js';
|
|
15
15
|
import openapiRoute from './helpers/openapiRoute.js';
|
|
16
16
|
import paginationPageParamOpenapiProperty from './helpers/paginationPageParamOpenapiProperty.js';
|
|
@@ -545,7 +545,7 @@ export default class OpenapiEndpointRenderer {
|
|
|
545
545
|
paramsShape.required = required;
|
|
546
546
|
}
|
|
547
547
|
paramsShape.properties = paramSafeColumns.reduce((acc, columnName) => {
|
|
548
|
-
acc[columnName] = dreamColumnOpenapiShape(dreamClass, columnName, undefined, {
|
|
548
|
+
acc[columnName] = dreamColumnOpenapiShape(this.controllerClass.controllerActionPath(this.action), dreamClass, columnName, undefined, {
|
|
549
549
|
allowGenericJson: true,
|
|
550
550
|
});
|
|
551
551
|
return acc;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { SerializingPlainPropertyWithoutOpenapiShape } from '../../error/openapi/SerializingPlainPropertyWithoutOpenapiShape.js';
|
|
2
|
+
import UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute from '../../error/openapi/UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute.js';
|
|
2
3
|
import OpenapiSegmentExpander from '../body-segment.js';
|
|
3
4
|
import openapiShorthandToOpenapi from './openapiShorthandToOpenapi.js';
|
|
4
|
-
export function dreamColumnOpenapiShape(
|
|
5
|
+
export function dreamColumnOpenapiShape(
|
|
6
|
+
// this is the global name of the serializer or controller calling down
|
|
7
|
+
// to get this information. If an unrecognized db type is provided, the
|
|
8
|
+
// source will be rendered in the exception that is returned, enabling
|
|
9
|
+
// the dev to identify the source of the issue and fix it
|
|
10
|
+
source, dreamClass, column, openapi = undefined, { suppressResponseEnums = false, allowGenericJson = false, } = {}) {
|
|
5
11
|
if (dreamClass.isVirtualColumn(column)) {
|
|
6
12
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
|
7
13
|
const openapiObject = openapiShorthandToOpenapi((openapi ?? {}));
|
|
@@ -46,7 +52,7 @@ export function dreamColumnOpenapiShape(dreamClass, column, openapi = undefined,
|
|
|
46
52
|
}
|
|
47
53
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
|
48
54
|
const openapiObject = openapiShorthandToOpenapi((openapi ?? {}));
|
|
49
|
-
const singleType = singularAttributeOpenapiShape(dreamColumnInfo, suppressResponseEnums, openapiObject);
|
|
55
|
+
const singleType = singularAttributeOpenapiShape(source, column, dreamColumnInfo, suppressResponseEnums, openapiObject);
|
|
50
56
|
if (dreamColumnInfo.isArray) {
|
|
51
57
|
return {
|
|
52
58
|
type: dreamColumnInfo.allowNull ? ['array', 'null'] : 'array',
|
|
@@ -71,7 +77,12 @@ export function dreamColumnOpenapiShape(dreamClass, column, openapi = undefined,
|
|
|
71
77
|
function baseDbType(dreamColumnInfo) {
|
|
72
78
|
return dreamColumnInfo.dbType.replace('[]', '');
|
|
73
79
|
}
|
|
74
|
-
function singularAttributeOpenapiShape(
|
|
80
|
+
function singularAttributeOpenapiShape(
|
|
81
|
+
// this is the global name of the serializer or controller calling down
|
|
82
|
+
// to get this information. If an unrecognized db type is provided, the
|
|
83
|
+
// source will be rendered in the exception that is returned, enabling
|
|
84
|
+
// the dev to identify the source of the issue and fix it
|
|
85
|
+
source, column, dreamColumnInfo, suppressResponseEnums, openapiSchema) {
|
|
75
86
|
if (dreamColumnInfo.enumValues) {
|
|
76
87
|
const enumOverrides = openapiSchema.enum || dreamColumnInfo.enumValues;
|
|
77
88
|
if (suppressResponseEnums) {
|
|
@@ -102,6 +113,10 @@ function singularAttributeOpenapiShape(dreamColumnInfo, suppressResponseEnums, o
|
|
|
102
113
|
case 'money':
|
|
103
114
|
case 'path':
|
|
104
115
|
case 'text':
|
|
116
|
+
case 'time':
|
|
117
|
+
case 'time without time zone':
|
|
118
|
+
case 'timetz':
|
|
119
|
+
case 'time with time zone':
|
|
105
120
|
case 'uuid':
|
|
106
121
|
case 'varbit':
|
|
107
122
|
case 'varchar':
|
|
@@ -121,8 +136,6 @@ function singularAttributeOpenapiShape(dreamColumnInfo, suppressResponseEnums, o
|
|
|
121
136
|
case 'real':
|
|
122
137
|
return { type: 'number' };
|
|
123
138
|
case 'datetime':
|
|
124
|
-
case 'time':
|
|
125
|
-
case 'time with time zone':
|
|
126
139
|
case 'timestamp':
|
|
127
140
|
case 'timestamp with time zone':
|
|
128
141
|
case 'timestamp without time zone':
|
|
@@ -133,7 +146,7 @@ function singularAttributeOpenapiShape(dreamColumnInfo, suppressResponseEnums, o
|
|
|
133
146
|
case 'jsonb':
|
|
134
147
|
return { type: 'object' };
|
|
135
148
|
default:
|
|
136
|
-
throw new
|
|
149
|
+
throw new UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute(source, column, dreamColumnInfo.dbType);
|
|
137
150
|
}
|
|
138
151
|
}
|
|
139
152
|
export class UseCustomOpenapiForJson extends Error {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CalendarDate, DateTime } from '@rvoh/dream';
|
|
1
|
+
import { CalendarDate, ClockTime, ClockTimeTz, DateTime } from '@rvoh/dream';
|
|
2
2
|
import { camelize, compact, snakeify } from '@rvoh/dream/utils';
|
|
3
3
|
import ParamValidationError from '../error/controller/ParamValidationError.js';
|
|
4
4
|
import ParamValidationErrors from '../error/controller/ParamValidationErrors.js';
|
|
@@ -85,6 +85,22 @@ export default class Params {
|
|
|
85
85
|
case 'timestamp without time zone[]':
|
|
86
86
|
returnObj[columnName] = this.cast(params, columnName.toString(), 'datetime[]', { allowNull: columnMetadata.allowNull });
|
|
87
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;
|
|
88
104
|
case 'jsonb':
|
|
89
105
|
returnObj[columnName] = this.cast(params, columnName.toString(), 'json', { allowNull: columnMetadata.allowNull });
|
|
90
106
|
break;
|
|
@@ -203,7 +219,6 @@ export default class Params {
|
|
|
203
219
|
}
|
|
204
220
|
return paramValue;
|
|
205
221
|
}
|
|
206
|
-
let dateClass;
|
|
207
222
|
const integerRegexp = /^-?\d+$/;
|
|
208
223
|
switch (expectedType) {
|
|
209
224
|
case 'string':
|
|
@@ -227,7 +242,8 @@ export default class Params {
|
|
|
227
242
|
return false;
|
|
228
243
|
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
229
244
|
case 'datetime':
|
|
230
|
-
case 'date':
|
|
245
|
+
case 'date': {
|
|
246
|
+
let dateClass;
|
|
231
247
|
switch (expectedType) {
|
|
232
248
|
case 'datetime':
|
|
233
249
|
dateClass = DateTime;
|
|
@@ -252,6 +268,35 @@ export default class Params {
|
|
|
252
268
|
}
|
|
253
269
|
}
|
|
254
270
|
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
271
|
+
}
|
|
272
|
+
case 'time':
|
|
273
|
+
case 'timetz': {
|
|
274
|
+
let timeClass;
|
|
275
|
+
switch (expectedType) {
|
|
276
|
+
case 'time':
|
|
277
|
+
timeClass = ClockTime;
|
|
278
|
+
break;
|
|
279
|
+
case 'timetz':
|
|
280
|
+
timeClass = ClockTimeTz;
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
if (typeof expectedType === 'string')
|
|
284
|
+
throw Error(`${expectedType} must be "time" or "timetz"`);
|
|
285
|
+
else
|
|
286
|
+
throw Error(`expectedType is not a string`);
|
|
287
|
+
}
|
|
288
|
+
if (paramValue instanceof ClockTime || paramValue instanceof ClockTimeTz)
|
|
289
|
+
return paramValue;
|
|
290
|
+
if (typeof paramValue === 'string') {
|
|
291
|
+
try {
|
|
292
|
+
return timeClass.fromISO(paramValue);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
299
|
+
}
|
|
255
300
|
case 'integer':
|
|
256
301
|
if (typeof paramValue !== 'string' && typeof paramValue !== 'number')
|
|
257
302
|
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
@@ -287,6 +332,8 @@ export default class Params {
|
|
|
287
332
|
case 'json[]':
|
|
288
333
|
case 'number[]':
|
|
289
334
|
case 'string[]':
|
|
335
|
+
case 'time[]':
|
|
336
|
+
case 'timetz[]':
|
|
290
337
|
case 'uuid[]':
|
|
291
338
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
292
339
|
if (!Array.isArray(paramValue))
|
|
@@ -365,6 +412,8 @@ const typeToErrorMap = {
|
|
|
365
412
|
null: 'expecting null',
|
|
366
413
|
number: 'expected number or string number',
|
|
367
414
|
string: 'expected string',
|
|
415
|
+
time: 'expected ISO time string',
|
|
416
|
+
timetz: 'expected ISO timetz string',
|
|
368
417
|
uuid: 'expected uuid',
|
|
369
418
|
'bigint[]': 'expected bigint array',
|
|
370
419
|
'boolean[]': 'expected boolean array',
|
|
@@ -375,6 +424,8 @@ const typeToErrorMap = {
|
|
|
375
424
|
'null[]': 'expecting null array',
|
|
376
425
|
'number[]': 'expected number or string number array',
|
|
377
426
|
'string[]': 'expected string array',
|
|
427
|
+
'time[]': 'expected ISO time string array',
|
|
428
|
+
'timetz[]': 'expected ISO timetz string array',
|
|
378
429
|
'uuid[]': 'expected uuid array',
|
|
379
430
|
};
|
|
380
431
|
function typeToError(param) {
|
|
@@ -393,6 +444,8 @@ const arrayTypeToNonArrayTypeMap = {
|
|
|
393
444
|
'null[]': 'null',
|
|
394
445
|
'number[]': 'number',
|
|
395
446
|
'string[]': 'string',
|
|
447
|
+
'time[]': 'time',
|
|
448
|
+
'timetz[]': 'timetz',
|
|
396
449
|
'uuid[]': 'uuid',
|
|
397
450
|
};
|
|
398
451
|
function arrayTypeToNonArrayType(param) {
|
|
@@ -6,7 +6,7 @@ import UnexpectedUndefined from '../../error/UnexpectedUndefined.js';
|
|
|
6
6
|
import PsychicApp from '../../psychic-app/index.js';
|
|
7
7
|
const devServerProcesses = {};
|
|
8
8
|
const debugEnabled = debuglog('psychic').enabled;
|
|
9
|
-
export async function launchDevServer(key, { port = 3000, cmd = 'pnpm client', timeout = 5000 } = {}) {
|
|
9
|
+
export async function launchDevServer(key, { port = 3000, cmd = 'pnpm client', timeout = 5000, onStdOut } = {}) {
|
|
10
10
|
if (devServerProcesses[key])
|
|
11
11
|
return;
|
|
12
12
|
if (debugEnabled)
|
|
@@ -20,6 +20,21 @@ export async function launchDevServer(key, { port = 3000, cmd = 'pnpm client', t
|
|
|
20
20
|
...process.env,
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
|
+
// NOTE: adding this stdout spy so that
|
|
24
|
+
// when this cli utility runs node commands,
|
|
25
|
+
// it can properly hijack the stdout from the command
|
|
26
|
+
proc.stdout?.on('data', chunk => {
|
|
27
|
+
const txt = chunk?.toString()?.trim();
|
|
28
|
+
if (typeof txt !== 'string' || !txt)
|
|
29
|
+
return;
|
|
30
|
+
if (onStdOut) {
|
|
31
|
+
onStdOut(txt);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.log(txt);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
23
38
|
devServerProcesses[key] = proc;
|
|
24
39
|
await waitForPort(key, port, timeout);
|
|
25
40
|
proc.on('error', err => {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export default class UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute extends Error {
|
|
2
|
+
source;
|
|
3
|
+
attributeName;
|
|
4
|
+
dbType;
|
|
5
|
+
constructor(source, attributeName, dbType) {
|
|
6
|
+
super();
|
|
7
|
+
this.source = source;
|
|
8
|
+
this.attributeName = attributeName;
|
|
9
|
+
this.dbType = dbType;
|
|
10
|
+
}
|
|
11
|
+
get message() {
|
|
12
|
+
return `
|
|
13
|
+
While trying to compute the openapi shape for either a serializer or a controller's
|
|
14
|
+
request body, we ran into a db type that we didn't know how to automatically infer the
|
|
15
|
+
openapi shape for. In these cases, we recommend that you provide an explicit openapi
|
|
16
|
+
shape for these attributes, so that the openapi shape can be properly generated.
|
|
17
|
+
|
|
18
|
+
source: ${this.source}
|
|
19
|
+
attribute: ${this.attributeName}
|
|
20
|
+
unexpected db type: ${this.dbType}
|
|
21
|
+
|
|
22
|
+
If the culprit is a serializer attribute, you should provide an explicit openapi definition
|
|
23
|
+
to the attribute causing your problems, like so:
|
|
24
|
+
|
|
25
|
+
.attribute('${this.attributeName}', { openapi: { type: 'string' }})
|
|
26
|
+
|
|
27
|
+
If, instead, it is a controller's request body causing your problems, identify the controller
|
|
28
|
+
method responsible for this exception, and ensure that the openapi request body shape is explicitly
|
|
29
|
+
defined, so that you do not force psychic to autocompute the openapi body shape for this endpoint.
|
|
30
|
+
|
|
31
|
+
@OpenAPI(MyModel, {
|
|
32
|
+
requestBody: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
${this.attributeName}: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -14,7 +14,7 @@ import NoSerializerFoundForRendersOneAndMany from '../error/openapi/NoSerializer
|
|
|
14
14
|
import ObjectSerializerRendersOneAndManyRequireClassType from '../error/openapi/ObjectSerializerRendersOneAndManyRequireClassType.js';
|
|
15
15
|
import allSerializersFromHandWrittenOpenapi from './helpers/allSerializersFromHandWrittenOpenapi.js';
|
|
16
16
|
import allSerializersToRefsInOpenapi from './helpers/allSerializersToRefsInOpenapi.js';
|
|
17
|
-
import { dreamColumnOpenapiShape } from './helpers/
|
|
17
|
+
import { dreamColumnOpenapiShape } from './helpers/dreamColumnOpenapiShape.js';
|
|
18
18
|
import openapiShorthandToOpenapi from './helpers/openapiShorthandToOpenapi.js';
|
|
19
19
|
const NULL_OBJECT_OPENAPI = { type: 'null' };
|
|
20
20
|
export default class SerializerOpenapiRenderer {
|
|
@@ -153,7 +153,7 @@ export default class SerializerOpenapiRenderer {
|
|
|
153
153
|
target = DataTypeForOpenapi;
|
|
154
154
|
}
|
|
155
155
|
accumulator[outputAttributeName] = allSerializersToRefsInOpenapi(target?.isDream
|
|
156
|
-
? dreamColumnOpenapiShape(target, attribute.name, openapi, {
|
|
156
|
+
? dreamColumnOpenapiShape(this.serializer.globalName, target, attribute.name, openapi, {
|
|
157
157
|
suppressResponseEnums: this.suppressResponseEnums,
|
|
158
158
|
})
|
|
159
159
|
: openapiShorthandToOpenapi(openapi));
|
|
@@ -10,7 +10,7 @@ import openapiParamNamesForDreamClass from '../server/helpers/openapiParamNamesF
|
|
|
10
10
|
import OpenapiSegmentExpander from './body-segment.js';
|
|
11
11
|
import { DEFAULT_OPENAPI_RESPONSES } from './defaults.js';
|
|
12
12
|
import cursorPaginationParamOpenapiProperty from './helpers/cursorPaginationParamOpenapiProperty.js';
|
|
13
|
-
import { dreamColumnOpenapiShape } from './helpers/
|
|
13
|
+
import { dreamColumnOpenapiShape } from './helpers/dreamColumnOpenapiShape.js';
|
|
14
14
|
import openapiOpts from './helpers/openapiOpts.js';
|
|
15
15
|
import openapiRoute from './helpers/openapiRoute.js';
|
|
16
16
|
import paginationPageParamOpenapiProperty from './helpers/paginationPageParamOpenapiProperty.js';
|
|
@@ -545,7 +545,7 @@ export default class OpenapiEndpointRenderer {
|
|
|
545
545
|
paramsShape.required = required;
|
|
546
546
|
}
|
|
547
547
|
paramsShape.properties = paramSafeColumns.reduce((acc, columnName) => {
|
|
548
|
-
acc[columnName] = dreamColumnOpenapiShape(dreamClass, columnName, undefined, {
|
|
548
|
+
acc[columnName] = dreamColumnOpenapiShape(this.controllerClass.controllerActionPath(this.action), dreamClass, columnName, undefined, {
|
|
549
549
|
allowGenericJson: true,
|
|
550
550
|
});
|
|
551
551
|
return acc;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { SerializingPlainPropertyWithoutOpenapiShape } from '../../error/openapi/SerializingPlainPropertyWithoutOpenapiShape.js';
|
|
2
|
+
import UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute from '../../error/openapi/UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute.js';
|
|
2
3
|
import OpenapiSegmentExpander from '../body-segment.js';
|
|
3
4
|
import openapiShorthandToOpenapi from './openapiShorthandToOpenapi.js';
|
|
4
|
-
export function dreamColumnOpenapiShape(
|
|
5
|
+
export function dreamColumnOpenapiShape(
|
|
6
|
+
// this is the global name of the serializer or controller calling down
|
|
7
|
+
// to get this information. If an unrecognized db type is provided, the
|
|
8
|
+
// source will be rendered in the exception that is returned, enabling
|
|
9
|
+
// the dev to identify the source of the issue and fix it
|
|
10
|
+
source, dreamClass, column, openapi = undefined, { suppressResponseEnums = false, allowGenericJson = false, } = {}) {
|
|
5
11
|
if (dreamClass.isVirtualColumn(column)) {
|
|
6
12
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
|
7
13
|
const openapiObject = openapiShorthandToOpenapi((openapi ?? {}));
|
|
@@ -46,7 +52,7 @@ export function dreamColumnOpenapiShape(dreamClass, column, openapi = undefined,
|
|
|
46
52
|
}
|
|
47
53
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
|
48
54
|
const openapiObject = openapiShorthandToOpenapi((openapi ?? {}));
|
|
49
|
-
const singleType = singularAttributeOpenapiShape(dreamColumnInfo, suppressResponseEnums, openapiObject);
|
|
55
|
+
const singleType = singularAttributeOpenapiShape(source, column, dreamColumnInfo, suppressResponseEnums, openapiObject);
|
|
50
56
|
if (dreamColumnInfo.isArray) {
|
|
51
57
|
return {
|
|
52
58
|
type: dreamColumnInfo.allowNull ? ['array', 'null'] : 'array',
|
|
@@ -71,7 +77,12 @@ export function dreamColumnOpenapiShape(dreamClass, column, openapi = undefined,
|
|
|
71
77
|
function baseDbType(dreamColumnInfo) {
|
|
72
78
|
return dreamColumnInfo.dbType.replace('[]', '');
|
|
73
79
|
}
|
|
74
|
-
function singularAttributeOpenapiShape(
|
|
80
|
+
function singularAttributeOpenapiShape(
|
|
81
|
+
// this is the global name of the serializer or controller calling down
|
|
82
|
+
// to get this information. If an unrecognized db type is provided, the
|
|
83
|
+
// source will be rendered in the exception that is returned, enabling
|
|
84
|
+
// the dev to identify the source of the issue and fix it
|
|
85
|
+
source, column, dreamColumnInfo, suppressResponseEnums, openapiSchema) {
|
|
75
86
|
if (dreamColumnInfo.enumValues) {
|
|
76
87
|
const enumOverrides = openapiSchema.enum || dreamColumnInfo.enumValues;
|
|
77
88
|
if (suppressResponseEnums) {
|
|
@@ -102,6 +113,10 @@ function singularAttributeOpenapiShape(dreamColumnInfo, suppressResponseEnums, o
|
|
|
102
113
|
case 'money':
|
|
103
114
|
case 'path':
|
|
104
115
|
case 'text':
|
|
116
|
+
case 'time':
|
|
117
|
+
case 'time without time zone':
|
|
118
|
+
case 'timetz':
|
|
119
|
+
case 'time with time zone':
|
|
105
120
|
case 'uuid':
|
|
106
121
|
case 'varbit':
|
|
107
122
|
case 'varchar':
|
|
@@ -121,8 +136,6 @@ function singularAttributeOpenapiShape(dreamColumnInfo, suppressResponseEnums, o
|
|
|
121
136
|
case 'real':
|
|
122
137
|
return { type: 'number' };
|
|
123
138
|
case 'datetime':
|
|
124
|
-
case 'time':
|
|
125
|
-
case 'time with time zone':
|
|
126
139
|
case 'timestamp':
|
|
127
140
|
case 'timestamp with time zone':
|
|
128
141
|
case 'timestamp without time zone':
|
|
@@ -133,7 +146,7 @@ function singularAttributeOpenapiShape(dreamColumnInfo, suppressResponseEnums, o
|
|
|
133
146
|
case 'jsonb':
|
|
134
147
|
return { type: 'object' };
|
|
135
148
|
default:
|
|
136
|
-
throw new
|
|
149
|
+
throw new UnrecognizedDbTypeFoundWhileComputingOpenapiAttribute(source, column, dreamColumnInfo.dbType);
|
|
137
150
|
}
|
|
138
151
|
}
|
|
139
152
|
export class UseCustomOpenapiForJson extends Error {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CalendarDate, DateTime } from '@rvoh/dream';
|
|
1
|
+
import { CalendarDate, ClockTime, ClockTimeTz, DateTime } from '@rvoh/dream';
|
|
2
2
|
import { camelize, compact, snakeify } from '@rvoh/dream/utils';
|
|
3
3
|
import ParamValidationError from '../error/controller/ParamValidationError.js';
|
|
4
4
|
import ParamValidationErrors from '../error/controller/ParamValidationErrors.js';
|
|
@@ -85,6 +85,22 @@ export default class Params {
|
|
|
85
85
|
case 'timestamp without time zone[]':
|
|
86
86
|
returnObj[columnName] = this.cast(params, columnName.toString(), 'datetime[]', { allowNull: columnMetadata.allowNull });
|
|
87
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;
|
|
88
104
|
case 'jsonb':
|
|
89
105
|
returnObj[columnName] = this.cast(params, columnName.toString(), 'json', { allowNull: columnMetadata.allowNull });
|
|
90
106
|
break;
|
|
@@ -203,7 +219,6 @@ export default class Params {
|
|
|
203
219
|
}
|
|
204
220
|
return paramValue;
|
|
205
221
|
}
|
|
206
|
-
let dateClass;
|
|
207
222
|
const integerRegexp = /^-?\d+$/;
|
|
208
223
|
switch (expectedType) {
|
|
209
224
|
case 'string':
|
|
@@ -227,7 +242,8 @@ export default class Params {
|
|
|
227
242
|
return false;
|
|
228
243
|
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
229
244
|
case 'datetime':
|
|
230
|
-
case 'date':
|
|
245
|
+
case 'date': {
|
|
246
|
+
let dateClass;
|
|
231
247
|
switch (expectedType) {
|
|
232
248
|
case 'datetime':
|
|
233
249
|
dateClass = DateTime;
|
|
@@ -252,6 +268,35 @@ export default class Params {
|
|
|
252
268
|
}
|
|
253
269
|
}
|
|
254
270
|
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
271
|
+
}
|
|
272
|
+
case 'time':
|
|
273
|
+
case 'timetz': {
|
|
274
|
+
let timeClass;
|
|
275
|
+
switch (expectedType) {
|
|
276
|
+
case 'time':
|
|
277
|
+
timeClass = ClockTime;
|
|
278
|
+
break;
|
|
279
|
+
case 'timetz':
|
|
280
|
+
timeClass = ClockTimeTz;
|
|
281
|
+
break;
|
|
282
|
+
default:
|
|
283
|
+
if (typeof expectedType === 'string')
|
|
284
|
+
throw Error(`${expectedType} must be "time" or "timetz"`);
|
|
285
|
+
else
|
|
286
|
+
throw Error(`expectedType is not a string`);
|
|
287
|
+
}
|
|
288
|
+
if (paramValue instanceof ClockTime || paramValue instanceof ClockTimeTz)
|
|
289
|
+
return paramValue;
|
|
290
|
+
if (typeof paramValue === 'string') {
|
|
291
|
+
try {
|
|
292
|
+
return timeClass.fromISO(paramValue);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
299
|
+
}
|
|
255
300
|
case 'integer':
|
|
256
301
|
if (typeof paramValue !== 'string' && typeof paramValue !== 'number')
|
|
257
302
|
throw new ParamValidationError(paramName, [typeToError(expectedType)]);
|
|
@@ -287,6 +332,8 @@ export default class Params {
|
|
|
287
332
|
case 'json[]':
|
|
288
333
|
case 'number[]':
|
|
289
334
|
case 'string[]':
|
|
335
|
+
case 'time[]':
|
|
336
|
+
case 'timetz[]':
|
|
290
337
|
case 'uuid[]':
|
|
291
338
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
292
339
|
if (!Array.isArray(paramValue))
|
|
@@ -365,6 +412,8 @@ const typeToErrorMap = {
|
|
|
365
412
|
null: 'expecting null',
|
|
366
413
|
number: 'expected number or string number',
|
|
367
414
|
string: 'expected string',
|
|
415
|
+
time: 'expected ISO time string',
|
|
416
|
+
timetz: 'expected ISO timetz string',
|
|
368
417
|
uuid: 'expected uuid',
|
|
369
418
|
'bigint[]': 'expected bigint array',
|
|
370
419
|
'boolean[]': 'expected boolean array',
|
|
@@ -375,6 +424,8 @@ const typeToErrorMap = {
|
|
|
375
424
|
'null[]': 'expecting null array',
|
|
376
425
|
'number[]': 'expected number or string number array',
|
|
377
426
|
'string[]': 'expected string array',
|
|
427
|
+
'time[]': 'expected ISO time string array',
|
|
428
|
+
'timetz[]': 'expected ISO timetz string array',
|
|
378
429
|
'uuid[]': 'expected uuid array',
|
|
379
430
|
};
|
|
380
431
|
function typeToError(param) {
|
|
@@ -393,6 +444,8 @@ const arrayTypeToNonArrayTypeMap = {
|
|
|
393
444
|
'null[]': 'null',
|
|
394
445
|
'number[]': 'number',
|
|
395
446
|
'string[]': 'string',
|
|
447
|
+
'time[]': 'time',
|
|
448
|
+
'timetz[]': 'timetz',
|
|
396
449
|
'uuid[]': 'uuid',
|
|
397
450
|
};
|
|
398
451
|
function arrayTypeToNonArrayType(param) {
|
|
@@ -14,7 +14,7 @@ export type ControllerActionMetadata = Record<string, {
|
|
|
14
14
|
serializerKey?: string;
|
|
15
15
|
}>;
|
|
16
16
|
export type PsychicParamsPrimitive = string | number | boolean | null | undefined | PsychicParamsPrimitive[];
|
|
17
|
-
export declare const PsychicParamsPrimitiveLiterals: readonly ["bigint", "bigint[]", "boolean", "boolean[]", "date", "date[]", "datetime", "datetime[]", "integer", "integer[]", "json", "json[]", "null", "null[]", "number", "number[]", "string", "string[]", "uuid", "uuid[]"];
|
|
17
|
+
export declare const PsychicParamsPrimitiveLiterals: readonly ["bigint", "bigint[]", "boolean", "boolean[]", "date", "date[]", "datetime", "datetime[]", "integer", "integer[]", "json", "json[]", "null", "null[]", "number", "number[]", "string", "string[]", "time", "time[]", "timetz", "timetz[]", "uuid", "uuid[]"];
|
|
18
18
|
export type PsychicParamsPrimitiveLiteral = (typeof PsychicParamsPrimitiveLiterals)[number];
|
|
19
19
|
export interface PsychicParamsDictionary {
|
|
20
20
|
[key: string]: PsychicParamsPrimitive | PsychicParamsDictionary | PsychicParamsDictionary[];
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export declare function launchDevServer(key: string, { port, cmd, timeout }?: LaunchDevServerOpts): Promise<void>;
|
|
1
|
+
export declare function launchDevServer(key: string, { port, cmd, timeout, onStdOut }?: LaunchDevServerOpts): Promise<void>;
|
|
2
2
|
export declare function stopDevServer(key: string): void;
|
|
3
3
|
export declare function stopDevServers(): void;
|
|
4
4
|
export interface LaunchDevServerOpts {
|
|
5
5
|
port?: number;
|
|
6
6
|
cmd?: string;
|
|
7
7
|
timeout?: number;
|
|
8
|
+
onStdOut?: (message: string) => void;
|
|
8
9
|
}
|
|
@@ -5,7 +5,7 @@ export interface VirtualAttributeStatement {
|
|
|
5
5
|
type: OpenapiShorthandPrimitiveTypes | OpenapiSchemaBodyShorthand | undefined;
|
|
6
6
|
}
|
|
7
7
|
type DreamClassColumnNames<DreamClass extends typeof Dream, DreamInstance extends InstanceType<DreamClass> = InstanceType<DreamClass>, DB = DreamInstance['DB'], TableName extends keyof DB = DreamInstance['table'] & keyof DB, Table extends DB[keyof DB] = DB[TableName]> = keyof Table & string;
|
|
8
|
-
export declare function dreamColumnOpenapiShape<DreamClass extends typeof Dream>(dreamClass: DreamClass, column: DreamClassColumnNames<DreamClass>, openapi?: OpenapiDescription | OpenapiSchemaBodyShorthand | OpenapiShorthandPrimitiveTypes | undefined, { suppressResponseEnums, allowGenericJson, }?: {
|
|
8
|
+
export declare function dreamColumnOpenapiShape<DreamClass extends typeof Dream>(source: string, dreamClass: DreamClass, column: DreamClassColumnNames<DreamClass>, openapi?: OpenapiDescription | OpenapiSchemaBodyShorthand | OpenapiShorthandPrimitiveTypes | undefined, { suppressResponseEnums, allowGenericJson, }?: {
|
|
9
9
|
suppressResponseEnums?: boolean;
|
|
10
10
|
allowGenericJson?: boolean;
|
|
11
11
|
}): OpenapiSchemaBody;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CalendarDate, DateTime, Dream } from '@rvoh/dream';
|
|
1
|
+
import { CalendarDate, ClockTime, ClockTimeTz, DateTime, Dream } from '@rvoh/dream';
|
|
2
2
|
import { OpenapiSchemaArray, OpenapiSchemaBody, OpenapiSchemaInteger, OpenapiSchemaNumber, OpenapiSchemaObjectBase, OpenapiSchemaPrimitiveGeneric, OpenapiSchemaPropertiesShorthand, OpenapiSchemaString } from '@rvoh/dream/openapi';
|
|
3
3
|
import { DreamParamSafeAttributes, DreamParamSafeColumnNames, StrictInterface } from '@rvoh/dream/types';
|
|
4
4
|
import { PsychicParamsDictionary, PsychicParamsPrimitive, PsychicParamsPrimitiveLiteral } from '../controller/index.js';
|
|
@@ -62,7 +62,7 @@ export default class Params {
|
|
|
62
62
|
}
|
|
63
63
|
export type ValidatedReturnType<ExpectedType, OptsType> = ExpectedType extends RegExp ? string : ExpectedType extends 'string' ? OptsType extends {
|
|
64
64
|
enum: infer EnumValue;
|
|
65
|
-
} ? EnumValue extends readonly string[] ? EnumValue[number] : never : string : ExpectedType extends 'number' ? number : ExpectedType extends 'datetime' ? DateTime : ExpectedType extends 'date' ? CalendarDate : ExpectedType extends 'bigint' ? string : ExpectedType extends 'integer' ? number : ExpectedType extends 'json' ? object : ExpectedType extends 'boolean' ? boolean : ExpectedType extends 'null' ? null : ExpectedType extends 'uuid' ? string : ExpectedType extends 'datetime[]' ? DateTime[] : ExpectedType extends 'date[]' ? CalendarDate[] : ExpectedType extends 'string[]' ? OptsType extends {
|
|
65
|
+
} ? EnumValue extends readonly string[] ? EnumValue[number] : never : string : ExpectedType extends 'number' ? number : ExpectedType extends 'datetime' ? DateTime : ExpectedType extends 'date' ? CalendarDate : ExpectedType extends 'time' ? ClockTime : ExpectedType extends 'timetz' ? ClockTimeTz : ExpectedType extends 'bigint' ? string : ExpectedType extends 'integer' ? number : ExpectedType extends 'json' ? object : ExpectedType extends 'boolean' ? boolean : ExpectedType extends 'null' ? null : ExpectedType extends 'uuid' ? string : ExpectedType extends 'datetime[]' ? DateTime[] : ExpectedType extends 'date[]' ? CalendarDate[] : ExpectedType extends 'time[]' ? ClockTime[] : ExpectedType extends 'timetz[]' ? ClockTimeTz[] : ExpectedType extends 'string[]' ? OptsType extends {
|
|
66
66
|
enum: infer EnumValue;
|
|
67
67
|
} ? EnumValue extends readonly string[] ? EnumValue[number][] : never : string[] : ExpectedType extends 'bigint[]' ? string[] : ExpectedType extends 'number[]' ? number[] : ExpectedType extends 'integer[]' ? number[] : ExpectedType extends 'boolean[]' ? boolean : ExpectedType extends 'null[]' ? null[] : ExpectedType extends 'uuid[]' ? string[] : OpenapiShapeToInterface<ExpectedType, 0>;
|
|
68
68
|
type OpenapiShapeToInterface<T, Depth extends number> = Depth extends 30 ? never : T extends OpenapiSchemaObjectBase ? {
|
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": "2.3.
|
|
5
|
+
"version": "2.3.9",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"yoctocolors": "^2.1.1"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@rvoh/dream": "^2.
|
|
82
|
+
"@rvoh/dream": "^2.3.0",
|
|
83
83
|
"@types/express": "^5.0.1",
|
|
84
84
|
"commander": "^12.1.0",
|
|
85
85
|
"express": "^5.2.1",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@eslint/js": "^9.39.1",
|
|
90
90
|
"@jest-mock/express": "^3.0.0",
|
|
91
|
-
"@rvoh/dream": "^2.
|
|
92
|
-
"@rvoh/dream-spec-helpers": "^2.
|
|
91
|
+
"@rvoh/dream": "^2.3.0",
|
|
92
|
+
"@rvoh/dream-spec-helpers": "^2.1.1",
|
|
93
93
|
"@rvoh/psychic-spec-helpers": "^2.0.0",
|
|
94
94
|
"@types/body-parser": "^1.19.6",
|
|
95
95
|
"@types/express": "^5.0.6",
|