@travetto/schema 5.0.12 → 5.0.14
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/LICENSE +1 -1
- package/README.md +23 -23
- package/package.json +3 -3
- package/src/bind-util.ts +1 -1
- package/src/data.ts +3 -0
- package/src/decorator/field.ts +3 -8
- package/src/decorator/schema.ts +15 -4
- package/src/service/registry.ts +24 -5
- package/src/service/types.ts +10 -2
- package/src/validate/regexp.ts +1 -1
- package/src/validate/types.ts +8 -1
- package/src/validate/validator.ts +15 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -63,28 +63,28 @@ User:
|
|
|
63
63
|
|
|
64
64
|
### Fields
|
|
65
65
|
This schema provides a powerful base for data binding and validation at runtime. Additionally there may be types that cannot be detected, or some information that the programmer would like to override. Below are the supported field decorators:
|
|
66
|
-
* [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
67
|
-
* [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
68
|
-
* [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
69
|
-
* [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
70
|
-
* [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
71
|
-
* [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
72
|
-
* [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
73
|
-
* [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
74
|
-
* [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
75
|
-
* [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
76
|
-
* [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
77
|
-
* [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
78
|
-
* [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
79
|
-
* [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
80
|
-
* [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
81
|
-
* [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
82
|
-
* [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
83
|
-
* [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
84
|
-
* [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
85
|
-
* [@Secret](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
86
|
-
* [@Specifier](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
87
|
-
* [@SubTypeField](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
66
|
+
* [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L25) defines a field that will be serialized.
|
|
67
|
+
* [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L65) defines a that field should be required
|
|
68
|
+
* [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L72) defines the allowable values that a field can have
|
|
69
|
+
* [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L93) defines a regular expression that the field value should match
|
|
70
|
+
* [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L101) enforces min length of a string
|
|
71
|
+
* [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L111) enforces max length of a string
|
|
72
|
+
* [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L101) enforces min value for a date or a number
|
|
73
|
+
* [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L111) enforces max value for a date or a number
|
|
74
|
+
* [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L138) ensures string field matches basic email regex
|
|
75
|
+
* [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L145) ensures string field matches basic telephone regex
|
|
76
|
+
* [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L152) ensures string field matches basic url regex
|
|
77
|
+
* [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L191) exclude from auto schema registration
|
|
78
|
+
* [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L166) ensures number passed in is only a whole number
|
|
79
|
+
* [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L172) ensures number passed in allows fractional values
|
|
80
|
+
* [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L184) provides support for standard currency
|
|
81
|
+
* [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L80) indicates that a field is expecting natural language input, not just discrete values
|
|
82
|
+
* [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L85) same as text, but expects longer form content
|
|
83
|
+
* [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L52) defines a that field should not be bindable external to the class
|
|
84
|
+
* [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L46) defines a that field should not be exported in serialization, but that it can be bound to
|
|
85
|
+
* [@Secret](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L58) marks a field as being sensitive. This is used by certain logging activities to ensure sensitive information is not logged out.
|
|
86
|
+
* [@Specifier](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L200) attributes additional specifiers to a field, allowing for more specification beyond just the field's type.
|
|
87
|
+
* [@SubTypeField](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L206) allows for promoting a given field as the owner of the sub type discriminator (defaults to `type`).
|
|
88
88
|
Additionally, schemas can be nested to form more complex data structures that are able to bound and validated.
|
|
89
89
|
|
|
90
90
|
Just like the class, all fields can be defined with
|
|
@@ -102,7 +102,7 @@ import { Match, Min } from '@travetto/schema';
|
|
|
102
102
|
const NAME_REGEX = /[A-Z][a-z]+(\s+[A-Z][a-z]+)*/;
|
|
103
103
|
|
|
104
104
|
export class ParamUsage {
|
|
105
|
-
main(@Match(NAME_REGEX)
|
|
105
|
+
main(@Match(NAME_REGEX) name: string, @Min(20) age?: number) {
|
|
106
106
|
console.log('Valid name and age!', { name, age });
|
|
107
107
|
}
|
|
108
108
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/schema",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.14",
|
|
4
4
|
"description": "Data type registry for runtime validation, reflection and binding.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"schema",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"directory": "module/schema"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/registry": "^5.0.
|
|
30
|
+
"@travetto/registry": "^5.0.14"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/transformer": "^5.0.
|
|
33
|
+
"@travetto/transformer": "^5.0.11"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@travetto/transformer": {
|
package/src/bind-util.ts
CHANGED
package/src/data.ts
CHANGED
|
@@ -91,6 +91,9 @@ export class DataUtil {
|
|
|
91
91
|
const retObj: Record<string, unknown> = castTo(ret);
|
|
92
92
|
|
|
93
93
|
for (const key of Object.keys(bObj)) {
|
|
94
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
94
97
|
retObj[key] = this.#deepAssignRaw(retObj[key], bObj[key], mode);
|
|
95
98
|
}
|
|
96
99
|
}
|
package/src/decorator/field.ts
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import { Any,
|
|
1
|
+
import { Any, ClassInstance } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { SchemaRegistry } from '../service/registry';
|
|
4
4
|
import { CommonRegExp } from '../validate/regexp';
|
|
5
5
|
import { ClassList, FieldConfig } from '../service/types';
|
|
6
6
|
|
|
7
|
-
type PropType<V> = (<T extends Partial<Record<K, V>>, K extends string>(t: T, k: K, idx?: TypedPropertyDescriptor<Any> | number) => void)
|
|
8
|
-
Param: (t: unknown, k: string, idx: number) => void;
|
|
9
|
-
};
|
|
7
|
+
type PropType<V> = (<T extends Partial<Record<K, V | Function>>, K extends string>(t: T, k: K, idx?: TypedPropertyDescriptor<Any> | number) => void);
|
|
10
8
|
|
|
11
9
|
function prop<V>(obj: Partial<FieldConfig>): PropType<V> {
|
|
12
|
-
|
|
10
|
+
return (t: ClassInstance, k: string, idx?: number | TypedPropertyDescriptor<Any>): void => {
|
|
13
11
|
if (idx !== undefined && typeof idx === 'number') {
|
|
14
12
|
SchemaRegistry.registerPendingParamFacet(t.constructor, k, idx, obj);
|
|
15
13
|
} else {
|
|
16
14
|
SchemaRegistry.registerPendingFieldFacet(t.constructor, k, obj);
|
|
17
15
|
}
|
|
18
16
|
};
|
|
19
|
-
fn.Param = fn;
|
|
20
|
-
|
|
21
|
-
return castTo(fn);
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
/**
|
package/src/decorator/schema.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { castTo, Class, DeepPartial } from '@travetto/runtime';
|
|
1
|
+
import { Any, castTo, Class, ClassInstance, DeepPartial } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { BindUtil } from '../bind-util';
|
|
4
4
|
import { SchemaRegistry } from '../service/registry';
|
|
5
5
|
import { ClassConfig, ViewFieldsConfig } from '../service/types';
|
|
6
|
-
import { ValidatorFn } from '../validate/types';
|
|
6
|
+
import { MethodValidatorFn, ValidatorFn } from '../validate/types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Register a class as a Schema
|
|
@@ -26,12 +26,23 @@ export function Schema(cfg?: Partial<Pick<ClassConfig, 'subTypeName' | 'subTypeF
|
|
|
26
26
|
*
|
|
27
27
|
* @param fn The validator function
|
|
28
28
|
*/
|
|
29
|
-
export
|
|
30
|
-
|
|
29
|
+
export const Validator = <T>(fn: ValidatorFn<T, string>) =>
|
|
30
|
+
(target: Class<T>, k?: string): void => {
|
|
31
31
|
SchemaRegistry.getOrCreatePending(target).validators!.push(castTo(fn));
|
|
32
32
|
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Add a custom validator for a given method
|
|
36
|
+
*
|
|
37
|
+
* @param fn The validator function
|
|
38
|
+
*/
|
|
39
|
+
export function MethodValidator<T extends (...args: Any[]) => Any>(fn: MethodValidatorFn<Parameters<T>>) {
|
|
40
|
+
return (target: ClassInstance, k: string, prop: TypedPropertyDescriptor<T>): void => {
|
|
41
|
+
SchemaRegistry.registerPendingMethod(target.constructor, k).validators!.push(castTo(fn));
|
|
42
|
+
};
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
|
|
35
46
|
/**
|
|
36
47
|
* Register a specific view for a class
|
|
37
48
|
* @param name The name of the view
|
package/src/service/registry.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Class, AppError, describeFunction, castTo, classConstruct, asFull, castKey } from '@travetto/runtime';
|
|
1
|
+
import { Class, AppError, describeFunction, castTo, classConstruct, asFull, castKey, Any } from '@travetto/runtime';
|
|
2
2
|
import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
|
|
3
3
|
|
|
4
|
-
import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig, ViewConfig } from './types';
|
|
4
|
+
import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig, ViewConfig, SchemaMethodConfig } from './types';
|
|
5
5
|
import { SchemaChangeListener } from './changes';
|
|
6
6
|
import { AllViewⲐ } from '../internal/types';
|
|
7
|
+
import { MethodValidatorFn } from '../validate/types';
|
|
7
8
|
|
|
8
9
|
const classToSubTypeName = (cls: Class): string => cls.name
|
|
9
10
|
.replace(/([A-Z])([A-Z][a-z])/g, (all, l, r) => `${l}_${r.toLowerCase()}`)
|
|
@@ -218,7 +219,16 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
218
219
|
* @param method
|
|
219
220
|
*/
|
|
220
221
|
getMethodSchema<T>(cls: Class<T>, method: string): FieldConfig[] {
|
|
221
|
-
return (this.get(cls)?.methods?.[method] ??
|
|
222
|
+
return (this.get(cls)?.methods?.[method] ?? {}).fields?.filter(x => !!x).sort((a, b) => a.index! - b.index!) ?? [];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get method validators
|
|
227
|
+
* @param cls
|
|
228
|
+
* @param method
|
|
229
|
+
*/
|
|
230
|
+
getMethodValidators<T>(cls: Class<T>, method: string): MethodValidatorFn<unknown[]>[] {
|
|
231
|
+
return (this.get(cls)?.methods?.[method] ?? {}).validators ?? [];
|
|
222
232
|
}
|
|
223
233
|
|
|
224
234
|
/**
|
|
@@ -235,6 +245,16 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
235
245
|
this.#pendingViews.get(target)!.set(view, generalConfig);
|
|
236
246
|
}
|
|
237
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Register pending method, and establish a method config
|
|
250
|
+
* @param target
|
|
251
|
+
* @param method
|
|
252
|
+
*/
|
|
253
|
+
registerPendingMethod(target: Class, method: string): SchemaMethodConfig {
|
|
254
|
+
const methods = this.getOrCreatePending(target)!.methods!;
|
|
255
|
+
return (methods[method] ??= { fields: [], validators: [] });
|
|
256
|
+
}
|
|
257
|
+
|
|
238
258
|
/**
|
|
239
259
|
* Register a partial config for a pending method param
|
|
240
260
|
* @param target The class to target
|
|
@@ -243,8 +263,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
243
263
|
* @param config The config to register
|
|
244
264
|
*/
|
|
245
265
|
registerPendingParamFacet(target: Class, method: string, idx: number, config: Partial<FieldConfig>): Class {
|
|
246
|
-
const
|
|
247
|
-
const params = (methods[method] ??= []);
|
|
266
|
+
const params = this.registerPendingMethod(target, method).fields;
|
|
248
267
|
if (config.name === '') {
|
|
249
268
|
delete config.name;
|
|
250
269
|
}
|
package/src/service/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Any, Class, Primitive } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { AllViewⲐ } from '../internal/types';
|
|
4
|
-
import { ValidatorFn } from '../validate/types';
|
|
4
|
+
import { MethodValidatorFn, ValidatorFn } from '../validate/types';
|
|
5
5
|
|
|
6
6
|
export type ClassList = Class | [Class];
|
|
7
7
|
|
|
@@ -26,6 +26,14 @@ export interface DescribableConfig {
|
|
|
26
26
|
examples?: string[];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Basic structure for a method configuration
|
|
31
|
+
*/
|
|
32
|
+
export interface SchemaMethodConfig {
|
|
33
|
+
fields: FieldConfig[];
|
|
34
|
+
validators: MethodValidatorFn<unknown[]>[];
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
/**
|
|
30
38
|
* Schema configuration
|
|
31
39
|
*/
|
|
@@ -85,7 +93,7 @@ export interface ClassConfig extends DescribableConfig {
|
|
|
85
93
|
/**
|
|
86
94
|
* Method parameter configs
|
|
87
95
|
*/
|
|
88
|
-
methods: Record<string,
|
|
96
|
+
methods: Record<string, SchemaMethodConfig>;
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
/**
|
package/src/validate/regexp.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const CommonRegExp = {
|
|
|
14
14
|
email: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
|
|
15
15
|
telephone: /^(\+?\d{1,3}(\s*-?\s*|\s+))?((\(\d{3}\))|\d{3})(\s*|-|[.])(\d{3})(\s*|-|[.])(\d{4})(\s+(x|ext[.]?)\s*\d+)?$/,
|
|
16
16
|
url: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/,
|
|
17
|
-
simpleName: /^([a-zA-Z\u0080-\u024F]
|
|
17
|
+
simpleName: /^([a-zA-Z\u0080-\u024F]{0,100}(?:. |-| |')){0,10}[a-zA-Z\u0080-\u024F]+$/,
|
|
18
18
|
postalCode: /^\d{5}(?:[-\s]\d{4})?$/
|
|
19
19
|
};
|
|
20
20
|
|
package/src/validate/types.ts
CHANGED
|
@@ -65,7 +65,14 @@ export interface ValidationResult {
|
|
|
65
65
|
n?: number | Date;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
type OrPromise<T> = T | Promise<T>;
|
|
69
|
+
|
|
68
70
|
/**
|
|
69
71
|
* Validator function
|
|
70
72
|
*/
|
|
71
|
-
export type ValidatorFn<T, U> = (value: T, parent?: U) => ValidationError |
|
|
73
|
+
export type ValidatorFn<T, U> = (value: T, parent?: U) => OrPromise<ValidationError | ValidationError[] | undefined>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Method Validator function
|
|
77
|
+
*/
|
|
78
|
+
export type MethodValidatorFn<T extends unknown[]> = (...value: T) => OrPromise<ValidationError | ValidationError[] | undefined>;
|
|
@@ -233,7 +233,11 @@ export class SchemaValidator {
|
|
|
233
233
|
try {
|
|
234
234
|
const res = await fn(o, view);
|
|
235
235
|
if (res) {
|
|
236
|
-
|
|
236
|
+
if (Array.isArray(res)) {
|
|
237
|
+
errors.push(...res);
|
|
238
|
+
} else {
|
|
239
|
+
errors.push(res);
|
|
240
|
+
}
|
|
237
241
|
}
|
|
238
242
|
} catch (err: unknown) {
|
|
239
243
|
if (isValidationError(err)) {
|
|
@@ -324,6 +328,16 @@ export class SchemaValidator {
|
|
|
324
328
|
return x;
|
|
325
329
|
}));
|
|
326
330
|
}
|
|
331
|
+
for (const validator of SchemaRegistry.getMethodValidators(cls, method)) {
|
|
332
|
+
const res = await validator(...params);
|
|
333
|
+
if (res) {
|
|
334
|
+
if (Array.isArray(res)) {
|
|
335
|
+
errors.push(...res);
|
|
336
|
+
} else {
|
|
337
|
+
errors.push(res);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
327
341
|
if (errors.length) {
|
|
328
342
|
throw new ValidationResultError(errors);
|
|
329
343
|
}
|