@travetto/schema 3.1.0-rc.0 → 3.1.0-rc.2
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 +35 -19
- package/package.json +2 -2
- package/src/decorator/common.ts +3 -2
- package/src/decorator/field.ts +36 -43
- package/src/service/registry.ts +20 -25
- package/src/service/types.ts +4 -4
- package/src/validate/validator.ts +8 -4
package/README.md
CHANGED
|
@@ -62,25 +62,25 @@ User:
|
|
|
62
62
|
|
|
63
63
|
### Fields
|
|
64
64
|
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:
|
|
65
|
-
* [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
66
|
-
* [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
67
|
-
* [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
68
|
-
* [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
69
|
-
* [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
70
|
-
* [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
71
|
-
* [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
72
|
-
* [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
73
|
-
* [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
74
|
-
* [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
75
|
-
* [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
76
|
-
* [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
77
|
-
* [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
78
|
-
* [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
79
|
-
* [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
80
|
-
* [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
81
|
-
* [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
82
|
-
* [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
83
|
-
* [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#
|
|
65
|
+
* [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L31) defines a field that will be serialized.
|
|
66
|
+
* [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L71) defines a that field should be required
|
|
67
|
+
* [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L78) defines the allowable values that a field can have
|
|
68
|
+
* [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L99) defines a regular expression that the field value should match
|
|
69
|
+
* [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L107) enforces min length of a string
|
|
70
|
+
* [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L117) enforces max length of a string
|
|
71
|
+
* [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L107) enforces min value for a date or a number
|
|
72
|
+
* [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L117) enforces max value for a date or a number
|
|
73
|
+
* [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L144) ensures string field matches basic email regex
|
|
74
|
+
* [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L151) ensures string field matches basic telephone regex
|
|
75
|
+
* [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L158) ensures string field matches basic url regex
|
|
76
|
+
* [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L197) exclude from auto schema registration
|
|
77
|
+
* [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L172) ensures number passed in is only a whole number
|
|
78
|
+
* [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L178) ensures number passed in allows fractional values
|
|
79
|
+
* [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L190) provides support for standard currency
|
|
80
|
+
* [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L86) indicates that a field is expecting natural language input, not just discrete values
|
|
81
|
+
* [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L91) same as text, but expects longer form content
|
|
82
|
+
* [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L58) defines a that field should not be bindable external to the class
|
|
83
|
+
* [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L52) defines a that field should not be exported in serialization, but that it can be bound to
|
|
84
84
|
Additionally, schemas can be nested to form more complex data structures that are able to bound and validated.
|
|
85
85
|
|
|
86
86
|
Just like the class, all fields can be defined with
|
|
@@ -88,6 +88,22 @@ Just like the class, all fields can be defined with
|
|
|
88
88
|
* `examples` - A set of examples as [JSON](https://www.json.org) or [YAML](https://en.wikipedia.org/wiki/YAML)
|
|
89
89
|
And similarly, the `description` will be picked up from the [JSDoc](http://usejsdoc.org/about-getting-started.html) comments, and additionally all fields can be set using the [@Describe](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/common.ts#L15) decorator.
|
|
90
90
|
|
|
91
|
+
### Parameters
|
|
92
|
+
Parameters are available in certain scenarios (e.g. [RESTful API](https://github.com/travetto/travetto/tree/main/module/rest#readme "Declarative api for RESTful APIs with support for the dependency injection module.") endpoints and [Command Line Interface](https://github.com/travetto/travetto/tree/main/module/cli#readme "CLI infrastructure for Travetto framework") main methods). In these scenarios, all of the field decorators are valid, but need to be called slightly differently to pass the typechecker. The simple solution is to use the `Arg` field of the decorator to convince Typescript its the correct type.
|
|
93
|
+
|
|
94
|
+
**Code: Sample Parameter Usage**
|
|
95
|
+
```typescript
|
|
96
|
+
import { Match, Min } from '../__index__';
|
|
97
|
+
|
|
98
|
+
const NAME_REGEX = /[A-Z][a-z]+(\s+[A-Z][a-z]+)*/;
|
|
99
|
+
|
|
100
|
+
export class ParamUsage {
|
|
101
|
+
main(@Match(NAME_REGEX).Param name: string, @Min(20).Param age?: number) {
|
|
102
|
+
console.log('Valid name and age!', { name, age });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
91
107
|
## Binding/Validation
|
|
92
108
|
At runtime, once a schema is registered, a programmer can utilize this structure to perform specific operations. Specifically binding and validation.
|
|
93
109
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/schema",
|
|
3
|
-
"version": "3.1.0-rc.
|
|
3
|
+
"version": "3.1.0-rc.2",
|
|
4
4
|
"description": "Data type registry for runtime validation, reflection and binding.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"schema",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@travetto/registry": "^3.1.0-rc.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/transformer": "^3.1.0-rc.
|
|
33
|
+
"@travetto/transformer": "^3.1.0-rc.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@travetto/transformer": {
|
package/src/decorator/common.ts
CHANGED
|
@@ -16,9 +16,10 @@ export function Describe(config: Partial<DescribableConfig>) {
|
|
|
16
16
|
return (target: Class | ClassInstance, property?: string, descOrIdx?: PropertyDescriptor | number): void => {
|
|
17
17
|
if (isClassInstance(target, property)) {
|
|
18
18
|
if (descOrIdx !== undefined && typeof descOrIdx === 'number') {
|
|
19
|
-
property
|
|
19
|
+
SchemaRegistry.registerPendingParamFacet(target.constructor, property!, descOrIdx, config);
|
|
20
|
+
} else {
|
|
21
|
+
SchemaRegistry.registerPendingFieldFacet(target.constructor, property!, config);
|
|
20
22
|
}
|
|
21
|
-
SchemaRegistry.registerPendingFieldFacet(target.constructor, property!, config);
|
|
22
23
|
} else {
|
|
23
24
|
SchemaRegistry.register(target, config);
|
|
24
25
|
}
|
package/src/decorator/field.ts
CHANGED
|
@@ -4,30 +4,23 @@ import { SchemaRegistry } from '../service/registry';
|
|
|
4
4
|
import { CommonRegExp } from '../validate/regexp';
|
|
5
5
|
import { ClassList, FieldConfig } from '../service/types';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
type PropType<V> = (<T extends Partial<Record<K, V>>, K extends string>(t: T, k: K, idx?: number) => void) & {
|
|
8
|
+
Param: (t: unknown, k: string, idx: number) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function prop<V>(obj: Partial<FieldConfig>): PropType<V> {
|
|
12
|
+
const fn = (t: ClassInstance, k: string, idx?: number): void => {
|
|
9
13
|
if (idx !== undefined && typeof idx === 'number') {
|
|
10
14
|
SchemaRegistry.registerPendingParamFacet(t.constructor, k, idx, obj);
|
|
11
15
|
} else {
|
|
12
16
|
SchemaRegistry.registerPendingFieldFacet(t.constructor, k, obj);
|
|
13
17
|
}
|
|
14
18
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// eslint-disable-next-line max-len
|
|
18
|
-
const stringArrProp: (obj: Partial<FieldConfig>) => <T extends Partial<Record<K, string | unknown[]>>, K extends string>(t: T, k: K, idx?: number | PropertyDescriptor) => void = prop;
|
|
19
|
-
|
|
20
|
-
// eslint-disable-next-line max-len
|
|
21
|
-
const stringArrStringProp: (obj: Partial<FieldConfig>) => <T extends Partial<Record<K, string | string[]>>, K extends string>(t: T, k: K, idx?: number | PropertyDescriptor) => void = prop;
|
|
19
|
+
fn.Param = fn; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
22
20
|
|
|
23
|
-
// eslint-disable-next-line
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// eslint-disable-next-line max-len
|
|
27
|
-
const stringNumberProp: (obj: Partial<FieldConfig>) => <T extends Partial<Record<K, string | number>>, K extends string>(t: T, k: K, idx?: number | PropertyDescriptor) => void = prop;
|
|
28
|
-
|
|
29
|
-
// eslint-disable-next-line max-len
|
|
30
|
-
const dateNumberProp: (obj: Partial<FieldConfig>) => <T extends Partial<Record<K, Date | number>>, K extends string>(t: T, k: K, idx?: number | PropertyDescriptor) => void = prop;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
22
|
+
return fn as PropType<V>;
|
|
23
|
+
}
|
|
31
24
|
|
|
32
25
|
/**
|
|
33
26
|
* Registering a field
|
|
@@ -36,7 +29,7 @@ const dateNumberProp: (obj: Partial<FieldConfig>) => <T extends Partial<Record<K
|
|
|
36
29
|
* @augments `@travetto/schema:Field`
|
|
37
30
|
*/
|
|
38
31
|
export function Field(type: ClassList, config?: Partial<FieldConfig>) {
|
|
39
|
-
return (f: ClassInstance, k: string, idx?: number
|
|
32
|
+
return (f: ClassInstance, k: string, idx?: number): void => {
|
|
40
33
|
if (idx !== undefined && typeof idx === 'number') {
|
|
41
34
|
SchemaRegistry.registerPendingParamConfig(f.constructor, k, idx, type, config);
|
|
42
35
|
} else {
|
|
@@ -50,52 +43,52 @@ export function Field(type: ClassList, config?: Partial<FieldConfig>) {
|
|
|
50
43
|
* @param aliases List of all aliases for a field
|
|
51
44
|
* @augments `@travetto/schema:Field`
|
|
52
45
|
*/
|
|
53
|
-
export function Alias(...aliases: string[]):
|
|
46
|
+
export function Alias(...aliases: string[]): PropType<unknown> { return prop({ aliases }); }
|
|
54
47
|
/**
|
|
55
48
|
* Mark a field as writeonly
|
|
56
49
|
* @param active This determines if this field is readonly or not.
|
|
57
50
|
* @augments `@travetto/schema:Field`
|
|
58
51
|
*/
|
|
59
|
-
export function Writeonly(active = true):
|
|
52
|
+
export function Writeonly(active = true): PropType<unknown> { return prop({ access: 'writeonly' }); }
|
|
60
53
|
/**
|
|
61
54
|
* Mark a field as readonly
|
|
62
55
|
* @param active This determines if this field is readonly or not.
|
|
63
56
|
* @augments `@travetto/schema:Field`
|
|
64
57
|
*/
|
|
65
|
-
export function Readonly(active = true):
|
|
58
|
+
export function Readonly(active = true): PropType<unknown> { return prop({ access: 'readonly' }); }
|
|
66
59
|
/**
|
|
67
60
|
* Mark a field as sensitive
|
|
68
61
|
* @param active This determines if this field is sensitive or not.
|
|
69
62
|
* @augments `@travetto/schema:Field`
|
|
70
63
|
*/
|
|
71
|
-
export function Secret(active = true):
|
|
64
|
+
export function Secret(active = true): PropType<unknown> { return prop({ secret: active }); }
|
|
72
65
|
/**
|
|
73
66
|
* Mark a field as required
|
|
74
67
|
* @param active This determines if this field is required or not.
|
|
75
68
|
* @param message The error message when a the constraint fails.
|
|
76
69
|
* @augments `@travetto/schema:Field`
|
|
77
70
|
*/
|
|
78
|
-
export function Required(active = true, message?: string):
|
|
71
|
+
export function Required(active = true, message?: string): PropType<unknown> { return prop({ required: { active, message } }); }
|
|
79
72
|
/**
|
|
80
73
|
* Define a field as a set of enumerated values
|
|
81
74
|
* @param values The list of values allowed for the enumeration
|
|
82
75
|
* @param message The error message to show when the constraint fails
|
|
83
76
|
* @augments `@travetto/schema:Field`
|
|
84
77
|
*/
|
|
85
|
-
export function Enum(values: string[], message?: string):
|
|
78
|
+
export function Enum(values: string[], message?: string): PropType<string | number> {
|
|
86
79
|
message = message || `{path} is only allowed to be "${values.join('" or "')}"`;
|
|
87
|
-
return
|
|
80
|
+
return prop({ enum: { values, message } });
|
|
88
81
|
}
|
|
89
82
|
/**
|
|
90
83
|
* Mark the field as indicating it's storing textual data
|
|
91
84
|
* @augments `@travetto/schema:Field`
|
|
92
85
|
*/
|
|
93
|
-
export function Text():
|
|
86
|
+
export function Text(): PropType<string | string[]> { return prop({ specifier: 'text' }); }
|
|
94
87
|
/**
|
|
95
88
|
* Mark the field to indicate it's for long form text
|
|
96
89
|
* @augments `@travetto/schema:Field`
|
|
97
90
|
*/
|
|
98
|
-
export function LongText():
|
|
91
|
+
export function LongText(): PropType<string | string[]> { return prop({ specifier: 'text-long' }); }
|
|
99
92
|
|
|
100
93
|
/**
|
|
101
94
|
* Require the field to match a specific RegExp
|
|
@@ -103,7 +96,7 @@ export function LongText(): ReturnType<typeof stringArrStringProp> { return stri
|
|
|
103
96
|
* @param message The message to show when the constraint fails
|
|
104
97
|
* @augments `@travetto/schema:Field`
|
|
105
98
|
*/
|
|
106
|
-
export function Match(re: RegExp, message?: string):
|
|
99
|
+
export function Match(re: RegExp, message?: string): PropType<string | string[]> { return prop({ match: { re, message } }); }
|
|
107
100
|
|
|
108
101
|
/**
|
|
109
102
|
* The minimum length for the string or array
|
|
@@ -111,8 +104,8 @@ export function Match(re: RegExp, message?: string): ReturnType<typeof stringArr
|
|
|
111
104
|
* @param message The message to show when the constraint fails
|
|
112
105
|
* @augments `@travetto/schema:Field`
|
|
113
106
|
*/
|
|
114
|
-
export function MinLength(n: number, message?: string):
|
|
115
|
-
return
|
|
107
|
+
export function MinLength(n: number, message?: string): PropType<string | unknown[]> {
|
|
108
|
+
return prop({ minlength: { n, message }, ...(n === 0 ? { required: { active: false } } : {}) });
|
|
116
109
|
}
|
|
117
110
|
|
|
118
111
|
/**
|
|
@@ -121,7 +114,7 @@ export function MinLength(n: number, message?: string): ReturnType<typeof string
|
|
|
121
114
|
* @param message The message to show when the constraint fails
|
|
122
115
|
* @augments `@travetto/schema:Field`
|
|
123
116
|
*/
|
|
124
|
-
export function MaxLength(n: number, message?: string):
|
|
117
|
+
export function MaxLength(n: number, message?: string): PropType<string | unknown[]> { return prop({ maxlength: { n, message } }); }
|
|
125
118
|
|
|
126
119
|
/**
|
|
127
120
|
* The minimum value
|
|
@@ -129,8 +122,8 @@ export function MaxLength(n: number, message?: string): ReturnType<typeof string
|
|
|
129
122
|
* @param message The message to show when the constraint fails
|
|
130
123
|
* @augments `@travetto/schema:Field`
|
|
131
124
|
*/
|
|
132
|
-
export function Min<T extends number | Date>(n: T, message?: string):
|
|
133
|
-
return
|
|
125
|
+
export function Min<T extends number | Date>(n: T, message?: string): PropType<Date | number> {
|
|
126
|
+
return prop({ min: { n, message } });
|
|
134
127
|
}
|
|
135
128
|
|
|
136
129
|
/**
|
|
@@ -139,8 +132,8 @@ export function Min<T extends number | Date>(n: T, message?: string): ReturnType
|
|
|
139
132
|
* @param message The message to show when the constraint fails
|
|
140
133
|
* @augments `@travetto/schema:Field`
|
|
141
134
|
*/
|
|
142
|
-
export function Max<T extends number | Date>(n: T, message?: string):
|
|
143
|
-
return
|
|
135
|
+
export function Max<T extends number | Date>(n: T, message?: string): PropType<Date | number> {
|
|
136
|
+
return prop({ max: { n, message } });
|
|
144
137
|
}
|
|
145
138
|
|
|
146
139
|
/**
|
|
@@ -148,21 +141,21 @@ export function Max<T extends number | Date>(n: T, message?: string): ReturnType
|
|
|
148
141
|
* @param message The message to show when the constraint fails
|
|
149
142
|
* @augments `@travetto/schema:Field`
|
|
150
143
|
*/
|
|
151
|
-
export function Email(message?: string):
|
|
144
|
+
export function Email(message?: string): PropType<string | string[]> { return Match(CommonRegExp.email, message); }
|
|
152
145
|
|
|
153
146
|
/**
|
|
154
147
|
* Mark a field as an telephone number
|
|
155
148
|
* @param message The message to show when the constraint fails
|
|
156
149
|
* @augments `@travetto/schema:Field`
|
|
157
150
|
*/
|
|
158
|
-
export function Telephone(message?: string):
|
|
151
|
+
export function Telephone(message?: string): PropType<string | string[]> { return Match(CommonRegExp.telephone, message); }
|
|
159
152
|
|
|
160
153
|
/**
|
|
161
154
|
* Mark a field as a url
|
|
162
155
|
* @param message The message to show when the constraint fails
|
|
163
156
|
* @augments `@travetto/schema:Field`
|
|
164
157
|
*/
|
|
165
|
-
export function Url(message?: string):
|
|
158
|
+
export function Url(message?: string): PropType<string | string[]> { return Match(CommonRegExp.url, message); }
|
|
166
159
|
|
|
167
160
|
/**
|
|
168
161
|
* Determine the numeric precision of the value
|
|
@@ -170,31 +163,31 @@ export function Url(message?: string): ReturnType<typeof Match> { return Match(C
|
|
|
170
163
|
* @param decimals The number of decimal digits to support
|
|
171
164
|
* @augments `@travetto/schema:Field`
|
|
172
165
|
*/
|
|
173
|
-
export function Precision(digits: number, decimals?: number):
|
|
166
|
+
export function Precision(digits: number, decimals?: number): PropType<number> { return prop({ precision: [digits, decimals] }); }
|
|
174
167
|
|
|
175
168
|
/**
|
|
176
169
|
* Mark a number as an integer
|
|
177
170
|
* @augments `@travetto/schema:Field`
|
|
178
171
|
*/
|
|
179
|
-
export function Integer():
|
|
172
|
+
export function Integer(): PropType<number> { return Precision(0); }
|
|
180
173
|
|
|
181
174
|
/**
|
|
182
175
|
* Mark a number as a float
|
|
183
176
|
* @augments `@travetto/schema:Field`
|
|
184
177
|
*/
|
|
185
|
-
export function Float():
|
|
178
|
+
export function Float(): PropType<number> { return Precision(10, 7); }
|
|
186
179
|
|
|
187
180
|
/**
|
|
188
181
|
* Mark a number as a long value
|
|
189
182
|
* @augments `@travetto/schema:Field`
|
|
190
183
|
*/
|
|
191
|
-
export function Long():
|
|
184
|
+
export function Long(): PropType<number> { return Precision(19, 0); }
|
|
192
185
|
|
|
193
186
|
/**
|
|
194
187
|
* Mark a number as a currency
|
|
195
188
|
* @augments `@travetto/schema:Field`
|
|
196
189
|
*/
|
|
197
|
-
export function Currency():
|
|
190
|
+
export function Currency(): PropType<number> { return Precision(13, 2); }
|
|
198
191
|
|
|
199
192
|
/**
|
|
200
193
|
* Mark a field as ignored
|
package/src/service/registry.ts
CHANGED
|
@@ -28,7 +28,6 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
28
28
|
#subTypes = new Map<Class, Map<string, Class>>();
|
|
29
29
|
#typeKeys = new Map<Class, string>();
|
|
30
30
|
#pendingViews = new Map<Class, Map<string, ViewFieldsConfig<unknown>>>();
|
|
31
|
-
#methodSchemas = new Map<Class, Map<string, FieldConfig[]>>();
|
|
32
31
|
|
|
33
32
|
constructor() {
|
|
34
33
|
super(RootRegistry);
|
|
@@ -191,6 +190,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
191
190
|
validators: [],
|
|
192
191
|
subType: false,
|
|
193
192
|
metadata: {},
|
|
193
|
+
methods: {},
|
|
194
194
|
views: {
|
|
195
195
|
[AllViewⲐ]: {
|
|
196
196
|
schema: {},
|
|
@@ -225,22 +225,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
225
225
|
* @param method
|
|
226
226
|
*/
|
|
227
227
|
getMethodSchema<T>(cls: Class<T>, method: string): FieldConfig[] {
|
|
228
|
-
|
|
229
|
-
this.#methodSchemas.set(cls, new Map());
|
|
230
|
-
}
|
|
231
|
-
const cache = this.#methodSchemas.get(cls)!;
|
|
232
|
-
if (!cache.has(method) && this.has(cls)) {
|
|
233
|
-
const { fields, schema } = this.getViewSchema(cls);
|
|
234
|
-
const out = [];
|
|
235
|
-
for (const el of fields) {
|
|
236
|
-
if (el.startsWith(`${method}.`) && schema[el].forMethod) {
|
|
237
|
-
out.push(schema[el]);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
out.sort((a, b) => a.index! - b.index!);
|
|
241
|
-
cache.set(method, out);
|
|
242
|
-
}
|
|
243
|
-
return cache.get(method)! ?? [];
|
|
228
|
+
return (this.get(cls)?.methods?.[method] ?? []).filter(x => !!x).sort((a, b) => a.index! - b.index!);
|
|
244
229
|
}
|
|
245
230
|
|
|
246
231
|
/**
|
|
@@ -265,9 +250,18 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
265
250
|
* @param idx The param index
|
|
266
251
|
* @param config The config to register
|
|
267
252
|
*/
|
|
268
|
-
registerPendingParamFacet(target: Class,
|
|
269
|
-
|
|
270
|
-
|
|
253
|
+
registerPendingParamFacet(target: Class, method: string, idx: number, config: Partial<FieldConfig>): Class {
|
|
254
|
+
const methods = this.getOrCreatePending(target)!.methods!;
|
|
255
|
+
const params = (methods[method] ??= []);
|
|
256
|
+
params[idx] = {
|
|
257
|
+
// @ts-expect-error
|
|
258
|
+
name: `${method}.${idx}`,
|
|
259
|
+
...params[idx] ?? {},
|
|
260
|
+
owner: target,
|
|
261
|
+
index: idx,
|
|
262
|
+
...config,
|
|
263
|
+
};
|
|
264
|
+
return target;
|
|
271
265
|
}
|
|
272
266
|
|
|
273
267
|
/**
|
|
@@ -300,10 +294,11 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
300
294
|
* @param conf Extra config
|
|
301
295
|
*/
|
|
302
296
|
registerPendingParamConfig(target: Class, method: string, idx: number, type: ClassList, conf?: Partial<FieldConfig>): Class {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
297
|
+
return this.registerPendingParamFacet(target, method, idx, {
|
|
298
|
+
...conf,
|
|
299
|
+
array: Array.isArray(type),
|
|
300
|
+
type: Array.isArray(type) ? type[0] : type,
|
|
301
|
+
});
|
|
307
302
|
}
|
|
308
303
|
|
|
309
304
|
/**
|
|
@@ -335,6 +330,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
335
330
|
schema: { ...dest.views[AllViewⲐ].schema, ...src.views?.[AllViewⲐ].schema },
|
|
336
331
|
fields: [...dest.views[AllViewⲐ].fields, ...src.views?.[AllViewⲐ].fields ?? []]
|
|
337
332
|
};
|
|
333
|
+
dest.methods = { ...src.methods ?? {}, ...dest.methods ?? {} };
|
|
338
334
|
dest.metadata = { ...src.metadata ?? {}, ...dest.metadata ?? {} };
|
|
339
335
|
dest.subType = src.subType || dest.subType;
|
|
340
336
|
dest.title = src.title || dest.title;
|
|
@@ -411,7 +407,6 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
411
407
|
// Recompute subtypes
|
|
412
408
|
this.#subTypes.clear();
|
|
413
409
|
this.#typeKeys.delete(cls);
|
|
414
|
-
this.#methodSchemas.delete(cls);
|
|
415
410
|
this.#accessorDescriptors.delete(cls);
|
|
416
411
|
|
|
417
412
|
// Recompute subtype mappings
|
package/src/service/types.ts
CHANGED
|
@@ -71,6 +71,10 @@ export interface ClassConfig extends DescribableConfig {
|
|
|
71
71
|
* Metadata that is related to the schema structure
|
|
72
72
|
*/
|
|
73
73
|
metadata?: Record<symbol, unknown>;
|
|
74
|
+
/**
|
|
75
|
+
* Method parameter configs
|
|
76
|
+
*/
|
|
77
|
+
methods: Record<string, FieldConfig[]>;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
/**
|
|
@@ -160,10 +164,6 @@ export interface FieldConfig extends DescribableConfig {
|
|
|
160
164
|
* Is this field a getter or setter
|
|
161
165
|
*/
|
|
162
166
|
accessor?: string;
|
|
163
|
-
/**
|
|
164
|
-
* Is the field for a method
|
|
165
|
-
*/
|
|
166
|
-
forMethod?: boolean;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
export type ViewFieldsConfig<T> = { with: Extract<(keyof T), string>[] } | { without: Extract<(keyof T), string>[] };
|
|
@@ -40,7 +40,7 @@ export class SchemaValidator {
|
|
|
40
40
|
|
|
41
41
|
const fields = TypedObject.keys<SchemaConfig>(schema);
|
|
42
42
|
for (const field of fields) {
|
|
43
|
-
if (schema[field].access !== 'readonly'
|
|
43
|
+
if (schema[field].access !== 'readonly') { // Do not validate readonly fields
|
|
44
44
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
45
45
|
errors = errors.concat(this.#validateFieldSchema(schema[field], o[field as keyof T], relative));
|
|
46
46
|
}
|
|
@@ -107,20 +107,24 @@ export class SchemaValidator {
|
|
|
107
107
|
static #validateRange(field: FieldConfig, key: 'min' | 'max', value: string | number | Date): boolean {
|
|
108
108
|
|
|
109
109
|
const f = field[key]!;
|
|
110
|
-
|
|
110
|
+
const fn = f.n;
|
|
111
|
+
if (typeof fn === 'number') {
|
|
111
112
|
if (typeof value === 'string') {
|
|
112
113
|
value = parseInt(value, 10);
|
|
113
114
|
}
|
|
114
115
|
if (field.type === Date) {
|
|
115
116
|
value = new Date(value);
|
|
116
117
|
}
|
|
117
|
-
|
|
118
|
+
const valN = typeof value === 'number' ? value : value.getTime();
|
|
119
|
+
if (key === 'min' && valN < fn || key === 'max' && valN > fn) {
|
|
118
120
|
return true;
|
|
119
121
|
}
|
|
120
122
|
} else {
|
|
121
|
-
const date =
|
|
123
|
+
const date = fn.getTime();
|
|
122
124
|
if (typeof value === 'string') {
|
|
123
125
|
value = Date.parse(value);
|
|
126
|
+
} else if (value instanceof Date) {
|
|
127
|
+
value = value.getTime();
|
|
124
128
|
}
|
|
125
129
|
if (key === 'min' && value < date || key === 'max' && value > date) {
|
|
126
130
|
return true;
|