@travetto/schema 8.0.0-alpha.20 → 8.0.0-alpha.22
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/README.md +37 -38
- package/__index__.ts +13 -12
- package/package.json +3 -3
- package/src/bind-util.ts +22 -18
- package/src/data.ts +54 -37
- package/src/decorator/common.ts +3 -3
- package/src/decorator/field.ts +15 -7
- package/src/decorator/input.ts +63 -23
- package/src/decorator/method.ts +2 -2
- package/src/decorator/schema.ts +5 -4
- package/src/internal/types.ts +3 -3
- package/src/name.ts +1 -2
- package/src/service/registry-adapter.ts +69 -55
- package/src/service/registry-index.ts +19 -13
- package/src/service/types.ts +9 -9
- package/src/type-config.ts +3 -3
- package/src/types.ts +2 -2
- package/src/validate/error.ts +2 -1
- package/src/validate/messages.ts +17 -15
- package/src/validate/regex.ts +1 -2
- package/src/validate/validator.ts +42 -44
- package/support/transformer/util.ts +133 -92
- package/support/transformer.schema.ts +20 -23
package/src/type-config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { isUint8Array, isUint16Array, isUint32Array, isArrayBuffer } from 'node:util/types';
|
|
2
1
|
import { Readable } from 'node:stream';
|
|
2
|
+
import { isArrayBuffer, isUint8Array, isUint16Array, isUint32Array } from 'node:util/types';
|
|
3
3
|
|
|
4
|
-
import { type
|
|
4
|
+
import { type BinaryArray, type BinaryStream, type BinaryType, BinaryUtil, type Class, toConcrete } from '@travetto/runtime';
|
|
5
5
|
|
|
6
6
|
type SchemaTypeConfig = {
|
|
7
7
|
/**
|
|
@@ -40,7 +40,7 @@ export class SchemaTypeUtil {
|
|
|
40
40
|
|
|
41
41
|
static register(type: Class | Function, fn: (value: unknown) => boolean): void {
|
|
42
42
|
SchemaTypeUtil.setSchemaTypeConfig(type, {
|
|
43
|
-
validate: (item: unknown) => fn(item) ? undefined : 'type'
|
|
43
|
+
validate: (item: unknown) => (fn(item) ? undefined : 'type')
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
|
package/src/types.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export class UnknownType {
|
|
1
|
+
export class UnknownType {}
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Geometric Point as [number,number] with validation and binding support
|
|
5
5
|
*
|
|
6
6
|
* @concrete ./internal/types.ts#PointContract
|
|
7
7
|
*/
|
|
8
|
-
export type Point = [number, number];
|
|
8
|
+
export type Point = [number, number];
|
package/src/validate/error.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Class, RuntimeError } from '@travetto/runtime';
|
|
2
|
+
|
|
2
3
|
import type { ValidationError } from './types.ts';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -24,4 +25,4 @@ export class TypeMismatchError extends RuntimeError {
|
|
|
24
25
|
|
|
25
26
|
export function isValidationError(error: unknown): error is ValidationError {
|
|
26
27
|
return !!error && error instanceof Error && 'path' in error;
|
|
27
|
-
}
|
|
28
|
+
}
|
package/src/validate/messages.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* List of validation messages
|
|
3
3
|
*/
|
|
4
|
-
export const Messages = new Map<string, string>(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
4
|
+
export const Messages = new Map<string, string>(
|
|
5
|
+
Object.entries({
|
|
6
|
+
default: '{path} is not valid',
|
|
7
|
+
type: '{path} is not a valid {type}',
|
|
8
|
+
required: '{path} is required',
|
|
9
|
+
minlength: '{path} is not long enough ({limit})',
|
|
10
|
+
maxlength: '{path} is too long ({limit})',
|
|
11
|
+
match: '{path} should match {regex}',
|
|
12
|
+
min: '{path} is less than ({limit})',
|
|
13
|
+
max: '{path} is greater than ({limit})',
|
|
14
|
+
'[[:telephone:]]': '{path} is not a valid phone number',
|
|
15
|
+
'[[:url:]]': '{path} is not a valid url',
|
|
16
|
+
'[[:simpleName:]]': '{path} is not a proper name',
|
|
17
|
+
'[[:postalCode:]]': '{path} is not a valid postal code',
|
|
18
|
+
'[[:email:]]': '{path} is not a valid email address'
|
|
19
|
+
})
|
|
20
|
+
);
|
package/src/validate/regex.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { TypedObject } from '@travetto/runtime';
|
|
|
3
3
|
/**
|
|
4
4
|
* List of common regular expressions for fields
|
|
5
5
|
*/
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
7
6
|
export const [CommonRegex, CommonRegexToName] = (() => {
|
|
8
7
|
const regexToName = new Map<RegExp, string>();
|
|
9
8
|
const regexes = {
|
|
@@ -19,4 +18,4 @@ export const [CommonRegex, CommonRegexToName] = (() => {
|
|
|
19
18
|
regexToName.set(regexes[key], name);
|
|
20
19
|
}
|
|
21
20
|
return [regexes, regexToName];
|
|
22
|
-
})();
|
|
21
|
+
})();
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Class, type ClassInstance, castKey, castTo, TypedObject } from '@travetto/runtime';
|
|
2
2
|
|
|
3
|
-
import type { SchemaInputConfig, SchemaFieldMap } from '../service/types.ts';
|
|
4
|
-
import type { ValidationError, ValidationKindCore, ValidationResult } from './types.ts';
|
|
5
|
-
import { Messages } from './messages.ts';
|
|
6
|
-
import { isValidationError, TypeMismatchError, ValidationResultError } from './error.ts';
|
|
7
3
|
import { DataUtil } from '../data.ts';
|
|
8
|
-
import { CommonRegexToName } from './regex.ts';
|
|
9
4
|
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
5
|
+
import type { SchemaFieldMap, SchemaInputConfig } from '../service/types.ts';
|
|
10
6
|
import { SchemaTypeUtil } from '../type-config.ts';
|
|
11
7
|
import { UnknownType } from '../types.ts';
|
|
8
|
+
import { isValidationError, TypeMismatchError, ValidationResultError } from './error.ts';
|
|
9
|
+
import { Messages } from './messages.ts';
|
|
10
|
+
import { CommonRegexToName } from './regex.ts';
|
|
11
|
+
import type { ValidationError, ValidationKindCore, ValidationResult } from './types.ts';
|
|
12
12
|
|
|
13
13
|
const PrimitiveTypes = new Set<Function>([String, Number, BigInt, Boolean]);
|
|
14
14
|
type NumericComparable = number | bigint | Date;
|
|
@@ -32,7 +32,7 @@ function isRangeValue(value: unknown): value is NumericComparable {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function isLengthValue(value: unknown): value is { length: number } {
|
|
35
|
-
return
|
|
35
|
+
return typeof value === 'string' || (typeof value === 'object' && !!value && 'length' in value && typeof value.length === 'number');
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -40,7 +40,6 @@ function isLengthValue(value: unknown): value is { length: number } {
|
|
|
40
40
|
* for errors
|
|
41
41
|
*/
|
|
42
42
|
export class SchemaValidator {
|
|
43
|
-
|
|
44
43
|
/**
|
|
45
44
|
* Validate the schema for a given object
|
|
46
45
|
* @param fields The config to validate against
|
|
@@ -51,7 +50,8 @@ export class SchemaValidator {
|
|
|
51
50
|
let errors: ValidationError[] = [];
|
|
52
51
|
|
|
53
52
|
for (const [field, fieldConfig] of TypedObject.entries(fields)) {
|
|
54
|
-
if (fieldConfig.access !== 'readonly') {
|
|
53
|
+
if (fieldConfig.access !== 'readonly') {
|
|
54
|
+
// Do not validate readonly fields
|
|
55
55
|
errors = errors.concat(this.#validateInputSchema(fieldConfig, item[castKey<T>(field)], relative));
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -66,9 +66,14 @@ export class SchemaValidator {
|
|
|
66
66
|
* @param relative The relative path of object traversal
|
|
67
67
|
*/
|
|
68
68
|
static #validateInputSchema(input: SchemaInputConfig, value: unknown, relative: string = ''): ValidationError[] {
|
|
69
|
-
const key = 'name' in input ? input.name :
|
|
69
|
+
const key = 'name' in input ? input.name : 'index' in input ? input.index : 'unknown';
|
|
70
70
|
const path = `${relative}${relative ? '.' : ''}${key}`;
|
|
71
|
-
const hasValue = !(
|
|
71
|
+
const hasValue = !(
|
|
72
|
+
value === undefined ||
|
|
73
|
+
value === null ||
|
|
74
|
+
(typeof value === 'string' && value === '') ||
|
|
75
|
+
(Array.isArray(value) && value.length === 0)
|
|
76
|
+
);
|
|
72
77
|
|
|
73
78
|
if (!hasValue) {
|
|
74
79
|
if (input.required?.active !== false) {
|
|
@@ -116,8 +121,8 @@ export class SchemaValidator {
|
|
|
116
121
|
*/
|
|
117
122
|
static #validateRange(input: SchemaInputConfig, key: 'min' | 'max', value: NumericComparable): boolean {
|
|
118
123
|
const config = input[key]!;
|
|
119
|
-
const parsed =
|
|
120
|
-
const boundary =
|
|
124
|
+
const parsed = value instanceof Date ? value.getTime() : value;
|
|
125
|
+
const boundary = config.limit instanceof Date ? config.limit.getTime() : config.limit;
|
|
121
126
|
return key === 'min' ? parsed < boundary : parsed > boundary;
|
|
122
127
|
}
|
|
123
128
|
|
|
@@ -134,9 +139,12 @@ export class SchemaValidator {
|
|
|
134
139
|
if (config?.validate) {
|
|
135
140
|
const kind = config.validate(value);
|
|
136
141
|
switch (kind) {
|
|
137
|
-
case undefined:
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
case undefined:
|
|
143
|
+
break;
|
|
144
|
+
case 'type':
|
|
145
|
+
return [{ kind, type: input.type.name }];
|
|
146
|
+
default:
|
|
147
|
+
return [{ kind, value }];
|
|
140
148
|
}
|
|
141
149
|
} else if (PrimitiveTypes.has(input.type)) {
|
|
142
150
|
if (typeof value !== input.type.name.toLowerCase()) {
|
|
@@ -145,7 +153,8 @@ export class SchemaValidator {
|
|
|
145
153
|
return [{ kind: 'type', type: 'number' }];
|
|
146
154
|
}
|
|
147
155
|
} else if (SchemaRegistryIndex.has(input.type)) {
|
|
148
|
-
if (!(value instanceof input.type)) {
|
|
156
|
+
if (!(value instanceof input.type)) {
|
|
157
|
+
// If not an instance of the type
|
|
149
158
|
return [{ kind: 'type', type: input.type.name }];
|
|
150
159
|
}
|
|
151
160
|
}
|
|
@@ -192,21 +201,16 @@ export class SchemaValidator {
|
|
|
192
201
|
message: '',
|
|
193
202
|
regex: CommonRegexToName.get(result.regex!) ?? result.regex?.source ?? '',
|
|
194
203
|
path,
|
|
195
|
-
type:
|
|
204
|
+
type: typeof result.type === 'function' ? result.type.name : result.type
|
|
196
205
|
};
|
|
197
206
|
|
|
198
207
|
if (!error.regex) {
|
|
199
208
|
delete error.regex;
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
const message = result.message ?? (
|
|
203
|
-
Messages.get(error.regex ?? '') ??
|
|
204
|
-
Messages.get(error.kind) ??
|
|
205
|
-
Messages.get('default')!
|
|
206
|
-
);
|
|
211
|
+
const message = result.message ?? Messages.get(error.regex ?? '') ?? Messages.get(error.kind) ?? Messages.get('default')!;
|
|
207
212
|
|
|
208
|
-
error.message = message
|
|
209
|
-
.replace(/\{([^}]+)\}/g, (_, key: (keyof ValidationError)) => `${error[key]}`);
|
|
213
|
+
error.message = message.replace(/\{([^}]+)\}/g, (_, key: keyof ValidationError) => `${error[key]}`);
|
|
210
214
|
|
|
211
215
|
out.push(error);
|
|
212
216
|
}
|
|
@@ -261,10 +265,7 @@ export class SchemaValidator {
|
|
|
261
265
|
const fields = SchemaRegistryIndex.get(cls).getFields(view);
|
|
262
266
|
|
|
263
267
|
// Validate using standard behaviors
|
|
264
|
-
const errors = [
|
|
265
|
-
...this.#validateFields(fields, item, ''),
|
|
266
|
-
... await this.#validateClassLevel(cls, item, view)
|
|
267
|
-
];
|
|
268
|
+
const errors = [...this.#validateFields(fields, item, ''), ...(await this.#validateClassLevel(cls, item, view))];
|
|
268
269
|
if (errors.length) {
|
|
269
270
|
throw new ValidationResultError(errors);
|
|
270
271
|
}
|
|
@@ -279,8 +280,7 @@ export class SchemaValidator {
|
|
|
279
280
|
* @param view The view to limit by
|
|
280
281
|
*/
|
|
281
282
|
static async validateAll<T>(cls: Class<T>, items: T[], view?: string): Promise<T[]> {
|
|
282
|
-
return await Promise.all<T>((items ?? [])
|
|
283
|
-
.map(item => this.validate(cls, item, view)));
|
|
283
|
+
return await Promise.all<T>((items ?? []).map(item => this.validate(cls, item, view)));
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
/**
|
|
@@ -294,7 +294,8 @@ export class SchemaValidator {
|
|
|
294
294
|
try {
|
|
295
295
|
await this.validate(cls, item, view);
|
|
296
296
|
} catch (error) {
|
|
297
|
-
if (error instanceof ValidationResultError) {
|
|
297
|
+
if (error instanceof ValidationResultError) {
|
|
298
|
+
// Don't check required fields
|
|
298
299
|
const errs = error.details.errors.filter(validationError => validationError.kind !== 'required');
|
|
299
300
|
if (errs.length) {
|
|
300
301
|
error.details.errors = errs;
|
|
@@ -318,17 +319,14 @@ export class SchemaValidator {
|
|
|
318
319
|
|
|
319
320
|
for (const param of config.parameters) {
|
|
320
321
|
const i = param.index;
|
|
321
|
-
errors.push(
|
|
322
|
-
...
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
error
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
return error;
|
|
331
|
-
}));
|
|
322
|
+
errors.push(
|
|
323
|
+
...[...this.#validateInputSchema(param, params[i]), ...(await this.#validateClassLevel(param.type, params[i]))].map(error => {
|
|
324
|
+
if (param.name && typeof param.name === 'string') {
|
|
325
|
+
error.path = !prefixes[i] ? error.path.replace(`${param.name}.`, '') : error.path.replace(param.name, prefixes[i]!);
|
|
326
|
+
}
|
|
327
|
+
return error;
|
|
328
|
+
})
|
|
329
|
+
);
|
|
332
330
|
}
|
|
333
331
|
for (const validator of config.validators) {
|
|
334
332
|
const error = await validator(...params);
|
|
@@ -344,4 +342,4 @@ export class SchemaValidator {
|
|
|
344
342
|
throw new ValidationResultError(errors);
|
|
345
343
|
}
|
|
346
344
|
}
|
|
347
|
-
}
|
|
345
|
+
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
+
|
|
2
3
|
import {
|
|
3
|
-
type AnyType,
|
|
4
|
-
|
|
4
|
+
type AnyType,
|
|
5
|
+
DeclarationUtil,
|
|
6
|
+
DecoratorUtil,
|
|
7
|
+
DocUtil,
|
|
8
|
+
LiteralUtil,
|
|
9
|
+
type ParamDocumentation,
|
|
10
|
+
type TransformerState,
|
|
11
|
+
transformCast
|
|
5
12
|
} from '@travetto/transformer';
|
|
6
13
|
|
|
7
|
-
export type ComputeConfig = { type?: AnyType
|
|
14
|
+
export type ComputeConfig = { type?: AnyType; root?: ts.Node; name?: string; index?: number };
|
|
8
15
|
|
|
9
16
|
export class SchemaTransformUtil {
|
|
10
|
-
|
|
11
17
|
static SCHEMA_IMPORT = '@travetto/schema/src/decorator/schema.ts';
|
|
12
18
|
static METHOD_IMPORT = '@travetto/schema/src/decorator/method.ts';
|
|
13
19
|
static FIELD_IMPORT = '@travetto/schema/src/decorator/field.ts';
|
|
@@ -21,12 +27,16 @@ export class SchemaTransformUtil {
|
|
|
21
27
|
*/
|
|
22
28
|
static toConcreteType(state: TransformerState, type: AnyType, node: ts.Node, root: ts.Node = node): ts.Expression {
|
|
23
29
|
switch (type.key) {
|
|
24
|
-
case 'pointer':
|
|
25
|
-
|
|
26
|
-
case '
|
|
27
|
-
|
|
30
|
+
case 'pointer':
|
|
31
|
+
return this.toConcreteType(state, type.target, node, root);
|
|
32
|
+
case 'managed':
|
|
33
|
+
return state.getOrImport(type);
|
|
34
|
+
case 'tuple':
|
|
35
|
+
return state.fromLiteral(type.subTypes.map(subType => this.toConcreteType(state, subType, node, root)!));
|
|
36
|
+
case 'template':
|
|
37
|
+
return state.createIdentifier(type.ctor.name);
|
|
28
38
|
case 'literal': {
|
|
29
|
-
if (
|
|
39
|
+
if (type.ctor === Array && type.typeArguments?.length) {
|
|
30
40
|
return state.fromLiteral([this.toConcreteType(state, type.typeArguments[0], node, root)]);
|
|
31
41
|
} else if (type.ctor) {
|
|
32
42
|
return state.createIdentifier(type.ctor.name!);
|
|
@@ -40,16 +50,20 @@ export class SchemaTransformUtil {
|
|
|
40
50
|
if (!existing) {
|
|
41
51
|
const cls = state.factory.createClassDeclaration(
|
|
42
52
|
[
|
|
43
|
-
state.createDecorator(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
state.createDecorator(
|
|
54
|
+
this.SCHEMA_IMPORT,
|
|
55
|
+
'Schema',
|
|
56
|
+
state.fromLiteral({
|
|
57
|
+
description: type.description,
|
|
58
|
+
mappedOperation: type.operation,
|
|
59
|
+
mappedFields: type.fields
|
|
60
|
+
})
|
|
61
|
+
)
|
|
49
62
|
],
|
|
50
|
-
id,
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
id,
|
|
64
|
+
[],
|
|
65
|
+
[state.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [state.factory.createExpressionWithTypeArguments(base, [])])],
|
|
66
|
+
[]
|
|
53
67
|
);
|
|
54
68
|
cls.getText = (): string => `
|
|
55
69
|
class ${uniqueId} extends ${type.mappedClassName} {
|
|
@@ -72,32 +86,43 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
72
86
|
if (!existing) {
|
|
73
87
|
const cls = state.factory.createClassDeclaration(
|
|
74
88
|
[
|
|
75
|
-
state.createDecorator(
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
state.createDecorator(
|
|
90
|
+
this.SCHEMA_IMPORT,
|
|
91
|
+
'Schema',
|
|
92
|
+
state.fromLiteral({
|
|
93
|
+
description: type.description
|
|
94
|
+
})
|
|
95
|
+
)
|
|
78
96
|
],
|
|
79
97
|
id,
|
|
80
98
|
[],
|
|
81
|
-
type.extendsFrom
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
type.extendsFrom
|
|
100
|
+
? [
|
|
101
|
+
state.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
|
|
102
|
+
state.factory.createExpressionWithTypeArguments(state.getOrImport(type.extendsFrom), [])
|
|
103
|
+
])
|
|
104
|
+
]
|
|
105
|
+
: [],
|
|
106
|
+
Object.entries(type.fieldTypes).map(([key, value]) =>
|
|
107
|
+
this.computeInput(
|
|
108
|
+
state,
|
|
109
|
+
state.factory.createPropertyDeclaration(
|
|
110
|
+
[],
|
|
111
|
+
/\W/.test(key) ? state.factory.createComputedPropertyName(state.fromLiteral(key)) : key,
|
|
90
112
|
value.undefinable || value.nullable ? state.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
|
|
91
|
-
value.key === 'unknown' ? state.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) : undefined,
|
|
92
|
-
|
|
113
|
+
value.key === 'unknown' ? state.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) : undefined,
|
|
114
|
+
undefined
|
|
115
|
+
),
|
|
116
|
+
{ type: value, root }
|
|
93
117
|
)
|
|
118
|
+
)
|
|
94
119
|
);
|
|
95
|
-
cls.getText = (): string =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.map(([key, value]) => ` ${key}${value.nullable ? '?' : ''}: ${value.name};`),
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
cls.getText = (): string =>
|
|
121
|
+
[
|
|
122
|
+
`class ${uniqueId} {`,
|
|
123
|
+
...Object.entries(type.fieldTypes).map(([key, value]) => ` ${key}${value.nullable ? '?' : ''}: ${value.name};`),
|
|
124
|
+
'}'
|
|
125
|
+
].join('\n');
|
|
101
126
|
state.addStatements([cls], root || node);
|
|
102
127
|
}
|
|
103
128
|
return id;
|
|
@@ -108,7 +133,9 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
108
133
|
}
|
|
109
134
|
break;
|
|
110
135
|
}
|
|
111
|
-
case 'foreign':
|
|
136
|
+
case 'foreign': {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
112
139
|
default: {
|
|
113
140
|
// Object
|
|
114
141
|
}
|
|
@@ -119,27 +146,25 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
119
146
|
/**
|
|
120
147
|
* Compute decorator params from property/parameter/getter/setter
|
|
121
148
|
*/
|
|
122
|
-
static computeInputDecoratorParams<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
config?: ComputeConfig
|
|
126
|
-
): ts.Expression[] {
|
|
149
|
+
static computeInputDecoratorParams<
|
|
150
|
+
T extends ts.PropertyDeclaration | ts.ParameterDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration
|
|
151
|
+
>(state: TransformerState, node: T, config?: ComputeConfig): ts.Expression[] {
|
|
127
152
|
const typeExpr = config?.type ?? state.resolveType(ts.isSetAccessor(node) ? node.parameters[0] : node);
|
|
128
153
|
const attrs: Record<string, string | boolean | object | number | ts.Expression> = {};
|
|
129
154
|
|
|
130
155
|
if (!ts.isGetAccessorDeclaration(node) && !ts.isSetAccessorDeclaration(node)) {
|
|
131
|
-
// eslint-disable-next-line no-bitwise
|
|
132
156
|
if ((ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Readonly) > 0) {
|
|
133
157
|
attrs.access = 'readonly';
|
|
134
|
-
} else if (
|
|
158
|
+
} else if (node.questionToken || typeExpr.undefinable || node.initializer) {
|
|
135
159
|
attrs.required = { active: false };
|
|
136
160
|
}
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
node.initializer
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
if (
|
|
162
|
+
node.initializer !== undefined &&
|
|
163
|
+
(ts.isLiteralExpression(node.initializer) ||
|
|
164
|
+
node.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
165
|
+
node.initializer.kind === ts.SyntaxKind.FalseKeyword ||
|
|
166
|
+
(ts.isArrayLiteralExpression(node.initializer) && node.initializer.elements.length === 0))
|
|
167
|
+
) {
|
|
143
168
|
attrs.default = node.initializer;
|
|
144
169
|
}
|
|
145
170
|
} else {
|
|
@@ -150,12 +175,12 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
150
175
|
}
|
|
151
176
|
if (!pair.getter) {
|
|
152
177
|
attrs.access = 'writeonly';
|
|
153
|
-
} else if (
|
|
178
|
+
} else if (typeExpr.undefinable) {
|
|
154
179
|
attrs.required = { active: false };
|
|
155
180
|
}
|
|
156
181
|
}
|
|
157
182
|
|
|
158
|
-
const rawName = node.getSourceFile()?.text ? node.name.getText() ?? undefined : undefined;
|
|
183
|
+
const rawName = node.getSourceFile()?.text ? (node.name.getText() ?? undefined) : undefined;
|
|
159
184
|
const providedName = config?.name ?? rawName!;
|
|
160
185
|
attrs.name = providedName;
|
|
161
186
|
|
|
@@ -169,7 +194,7 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
169
194
|
// If we have a composition type
|
|
170
195
|
if (primaryExpr.key === 'composition') {
|
|
171
196
|
const values = primaryExpr.subTypes
|
|
172
|
-
.map(subType => subType.key === 'literal' ? subType.value : undefined)
|
|
197
|
+
.map(subType => (subType.key === 'literal' ? subType.value : undefined))
|
|
173
198
|
.filter(value => value !== undefined && value !== null);
|
|
174
199
|
|
|
175
200
|
if (values.length === primaryExpr.subTypes.length) {
|
|
@@ -189,8 +214,8 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
189
214
|
|
|
190
215
|
if (ts.isParameter(node)) {
|
|
191
216
|
const parentComments = DocUtil.describeDocs(node.parent);
|
|
192
|
-
const paramComments: Partial<ParamDocumentation> =
|
|
193
|
-
.find(param => param.name === node.name.getText()) || {};
|
|
217
|
+
const paramComments: Partial<ParamDocumentation> =
|
|
218
|
+
(parentComments.params ?? []).find(param => param.name === node.name.getText()) || {};
|
|
194
219
|
|
|
195
220
|
if (paramComments.description) {
|
|
196
221
|
attrs.description = paramComments.description;
|
|
@@ -223,13 +248,19 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
223
248
|
}
|
|
224
249
|
|
|
225
250
|
const resolved = this.toConcreteType(state, typeExpr, node, config?.root ?? node);
|
|
226
|
-
const type =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
251
|
+
const type =
|
|
252
|
+
typeExpr.key === 'foreign'
|
|
253
|
+
? state.getConcreteType(node, state.factory.createIdentifier('Object'))
|
|
254
|
+
: ts.isArrayLiteralExpression(resolved)
|
|
255
|
+
? resolved.elements[0]
|
|
256
|
+
: resolved;
|
|
257
|
+
|
|
258
|
+
params.unshift(
|
|
259
|
+
LiteralUtil.fromLiteral(state.factory, {
|
|
260
|
+
array: ts.isArrayLiteralExpression(resolved),
|
|
261
|
+
type
|
|
262
|
+
})
|
|
263
|
+
);
|
|
233
264
|
|
|
234
265
|
if (existing) {
|
|
235
266
|
const args = DecoratorUtil.getArguments(existing) ?? [];
|
|
@@ -248,7 +279,9 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
248
279
|
* Compute property information from declaration
|
|
249
280
|
*/
|
|
250
281
|
static computeInput<T extends ts.PropertyDeclaration | ts.ParameterDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration>(
|
|
251
|
-
state: TransformerState,
|
|
282
|
+
state: TransformerState,
|
|
283
|
+
node: T,
|
|
284
|
+
config?: ComputeConfig
|
|
252
285
|
): T {
|
|
253
286
|
const existingField = state.findDecorator('@travetto/schema', node, 'Field', this.FIELD_IMPORT);
|
|
254
287
|
const existingInput = state.findDecorator('@travetto/schema', node, 'Input', this.INPUT_IMPORT);
|
|
@@ -265,17 +298,21 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
265
298
|
|
|
266
299
|
let result: unknown;
|
|
267
300
|
if (ts.isPropertyDeclaration(node)) {
|
|
268
|
-
result = state.factory.updatePropertyDeclaration(node,
|
|
269
|
-
modifiers, node.name, node.questionToken, node.type, node.initializer);
|
|
301
|
+
result = state.factory.updatePropertyDeclaration(node, modifiers, node.name, node.questionToken, node.type, node.initializer);
|
|
270
302
|
} else if (ts.isParameter(node)) {
|
|
271
|
-
result = state.factory.updateParameterDeclaration(
|
|
272
|
-
|
|
303
|
+
result = state.factory.updateParameterDeclaration(
|
|
304
|
+
node,
|
|
305
|
+
modifiers,
|
|
306
|
+
node.dotDotDotToken,
|
|
307
|
+
node.name,
|
|
308
|
+
node.questionToken,
|
|
309
|
+
node.type,
|
|
310
|
+
node.initializer
|
|
311
|
+
);
|
|
273
312
|
} else if (ts.isGetAccessorDeclaration(node)) {
|
|
274
|
-
result = state.factory.updateGetAccessorDeclaration(node,
|
|
275
|
-
modifiers, node.name, node.parameters, node.type, node.body);
|
|
313
|
+
result = state.factory.updateGetAccessorDeclaration(node, modifiers, node.name, node.parameters, node.type, node.body);
|
|
276
314
|
} else {
|
|
277
|
-
result = state.factory.updateSetAccessorDeclaration(node,
|
|
278
|
-
modifiers, node.name, node.parameters, node.body);
|
|
315
|
+
result = state.factory.updateSetAccessorDeclaration(node, modifiers, node.name, node.parameters, node.body);
|
|
279
316
|
}
|
|
280
317
|
return transformCast(result);
|
|
281
318
|
}
|
|
@@ -283,7 +320,7 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
283
320
|
/**
|
|
284
321
|
* Unwrap type
|
|
285
322
|
*/
|
|
286
|
-
static unwrapType(type: AnyType): { out: Record<string, unknown
|
|
323
|
+
static unwrapType(type: AnyType): { out: Record<string, unknown>; type: AnyType } {
|
|
287
324
|
const out: Record<string, unknown> = {};
|
|
288
325
|
|
|
289
326
|
while (type?.key === 'literal' && type.typeArguments?.length) {
|
|
@@ -307,15 +344,21 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
307
344
|
out.type = state.getForeignTarget(type);
|
|
308
345
|
break;
|
|
309
346
|
}
|
|
310
|
-
case 'managed':
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
case '
|
|
347
|
+
case 'managed':
|
|
348
|
+
out.type = state.typeToIdentifier(type);
|
|
349
|
+
break;
|
|
350
|
+
case 'mapped':
|
|
351
|
+
out.type = this.toConcreteType(state, type, target);
|
|
352
|
+
break;
|
|
353
|
+
case 'shape':
|
|
354
|
+
out.type = this.toConcreteType(state, type, target);
|
|
355
|
+
break;
|
|
356
|
+
case 'template':
|
|
357
|
+
out.type = state.factory.createIdentifier(type.ctor.name);
|
|
358
|
+
break;
|
|
314
359
|
case 'literal': {
|
|
315
360
|
if (type.ctor) {
|
|
316
|
-
out.type = out.array ?
|
|
317
|
-
this.toConcreteType(state, type, target) :
|
|
318
|
-
state.factory.createIdentifier(type.ctor.name);
|
|
361
|
+
out.type = out.array ? this.toConcreteType(state, type, target) : state.factory.createIdentifier(type.ctor.name);
|
|
319
362
|
}
|
|
320
363
|
}
|
|
321
364
|
}
|
|
@@ -332,14 +375,16 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
332
375
|
static findInnerReturnMethod(state: TransformerState, node: ts.MethodDeclaration, methodName: string): ts.MethodDeclaration | undefined {
|
|
333
376
|
// Process returnType
|
|
334
377
|
const { type } = this.unwrapType(state.resolveReturnType(node));
|
|
335
|
-
let cls;
|
|
378
|
+
let cls: ts.Type | ts.ClassDeclaration | undefined;
|
|
336
379
|
switch (type?.key) {
|
|
337
380
|
case 'managed': {
|
|
338
381
|
const [decorator] = DeclarationUtil.getDeclarations(type.original!);
|
|
339
382
|
cls = decorator && ts.isClassDeclaration(decorator) ? decorator : undefined;
|
|
340
383
|
break;
|
|
341
384
|
}
|
|
342
|
-
case 'shape':
|
|
385
|
+
case 'shape':
|
|
386
|
+
cls = type.original;
|
|
387
|
+
break;
|
|
343
388
|
}
|
|
344
389
|
if (cls) {
|
|
345
390
|
return state.findMethodByName(cls, methodName);
|
|
@@ -373,14 +418,10 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
373
418
|
static createAccessorDefineProperty(state: TransformerState, accessorName: string): ts.Statement {
|
|
374
419
|
const bindUtilImport = state.importFile(this.BIND_UTIL_IMPORT);
|
|
375
420
|
return state.factory.createExpressionStatement(
|
|
376
|
-
state.factory.createCallExpression(
|
|
377
|
-
state.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
state.factory.createThis(),
|
|
381
|
-
state.factory.createStringLiteral(accessorName)
|
|
382
|
-
]
|
|
383
|
-
)
|
|
421
|
+
state.factory.createCallExpression(state.createAccess(bindUtilImport.identifier, 'BindUtil', 'registerAccessor'), undefined, [
|
|
422
|
+
state.factory.createThis(),
|
|
423
|
+
state.factory.createStringLiteral(accessorName)
|
|
424
|
+
])
|
|
384
425
|
);
|
|
385
426
|
}
|
|
386
|
-
}
|
|
427
|
+
}
|