@travetto/schema 8.0.0-alpha.21 → 8.0.0-alpha.23
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 +23 -19
- package/src/data.ts +55 -38
- 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 +44 -46
- package/support/transformer/util.ts +133 -92
- package/support/transformer.schema.ts +20 -23
package/src/decorator/input.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Any,
|
|
3
|
+
type Class,
|
|
4
|
+
type ClassInstance,
|
|
5
|
+
getClass,
|
|
6
|
+
type NumericLikeIntrinsic,
|
|
7
|
+
type NumericPrimitive,
|
|
8
|
+
type Primitive
|
|
9
|
+
} from '@travetto/runtime';
|
|
2
10
|
|
|
3
|
-
import { CommonRegex } from '../validate/regex.ts';
|
|
4
|
-
import { CONSTRUCTOR_PROPERTY, type SchemaInputConfig } from '../service/types.ts';
|
|
5
11
|
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
12
|
+
import { CONSTRUCTOR_PROPERTY, type SchemaInputConfig } from '../service/types.ts';
|
|
13
|
+
import { CommonRegex } from '../validate/regex.ts';
|
|
6
14
|
|
|
7
15
|
type StringType = string | string[];
|
|
8
16
|
type LengthType = string | unknown[] | Uint8Array | Uint16Array | Uint32Array;
|
|
@@ -10,9 +18,11 @@ type NumberType = NumericPrimitive | NumericPrimitive[];
|
|
|
10
18
|
type NumberLikeType = NumericLikeIntrinsic | NumericLikeIntrinsic[];
|
|
11
19
|
type EnumType = Exclude<Primitive, 'boolean'> | Exclude<Primitive, 'boolean'>[];
|
|
12
20
|
|
|
13
|
-
type PropType<V> =
|
|
14
|
-
instance: T,
|
|
15
|
-
|
|
21
|
+
type PropType<V> = <T extends Partial<Record<K, V | Function>>, K extends string>(
|
|
22
|
+
instance: T,
|
|
23
|
+
property: K,
|
|
24
|
+
idx?: TypedPropertyDescriptor<Any> | number
|
|
25
|
+
) => void;
|
|
16
26
|
|
|
17
27
|
function input<V>(...configs: Partial<SchemaInputConfig>[]): PropType<V> {
|
|
18
28
|
return (instanceOrCls: ClassInstance | Class, property: string, idx?: number | TypedPropertyDescriptor<Any>): void => {
|
|
@@ -43,7 +53,9 @@ export function Input(type: Pick<SchemaInputConfig, 'type' | 'array'>, ...config
|
|
|
43
53
|
* @augments `@travetto/schema:Input`
|
|
44
54
|
* @kind decorator
|
|
45
55
|
*/
|
|
46
|
-
export function Alias(...aliases: string[]): PropType<unknown> {
|
|
56
|
+
export function Alias(...aliases: string[]): PropType<unknown> {
|
|
57
|
+
return input({ aliases });
|
|
58
|
+
}
|
|
47
59
|
|
|
48
60
|
/**
|
|
49
61
|
* Mark an input as required
|
|
@@ -52,7 +64,9 @@ export function Alias(...aliases: string[]): PropType<unknown> { return input({
|
|
|
52
64
|
* @augments `@travetto/schema:Input`
|
|
53
65
|
* @kind decorator
|
|
54
66
|
*/
|
|
55
|
-
export function Required(active = true, message?: string): PropType<unknown> {
|
|
67
|
+
export function Required(active = true, message?: string): PropType<unknown> {
|
|
68
|
+
return input({ required: { active, message } });
|
|
69
|
+
}
|
|
56
70
|
|
|
57
71
|
/**
|
|
58
72
|
* Define an input as a set of enumerated values
|
|
@@ -71,14 +85,18 @@ export function Enum(values: string[], message?: string): PropType<EnumType> {
|
|
|
71
85
|
* @augments `@travetto/schema:Input`
|
|
72
86
|
* @kind decorator
|
|
73
87
|
*/
|
|
74
|
-
export function Text(): PropType<StringType> {
|
|
88
|
+
export function Text(): PropType<StringType> {
|
|
89
|
+
return input({ specifiers: ['text'] });
|
|
90
|
+
}
|
|
75
91
|
|
|
76
92
|
/**
|
|
77
93
|
* Mark the input to indicate it's for long form text
|
|
78
94
|
* @augments `@travetto/schema:Input`
|
|
79
95
|
* @kind decorator
|
|
80
96
|
*/
|
|
81
|
-
export function LongText(): PropType<StringType> {
|
|
97
|
+
export function LongText(): PropType<StringType> {
|
|
98
|
+
return input({ specifiers: ['text', 'long'] });
|
|
99
|
+
}
|
|
82
100
|
|
|
83
101
|
/**
|
|
84
102
|
* Require the input to match a specific RegExp
|
|
@@ -87,7 +105,9 @@ export function LongText(): PropType<StringType> { return input({ specifiers: ['
|
|
|
87
105
|
* @augments `@travetto/schema:Input`
|
|
88
106
|
* @kind decorator
|
|
89
107
|
*/
|
|
90
|
-
export function Match(regex: RegExp, message?: string): PropType<StringType> {
|
|
108
|
+
export function Match(regex: RegExp, message?: string): PropType<StringType> {
|
|
109
|
+
return input({ match: { regex, message } });
|
|
110
|
+
}
|
|
91
111
|
|
|
92
112
|
/**
|
|
93
113
|
* The minimum length for the string or array
|
|
@@ -107,7 +127,9 @@ export function MinLength(limit: number, message?: string): PropType<LengthType>
|
|
|
107
127
|
* @augments `@travetto/schema:Input`
|
|
108
128
|
* @kind decorator
|
|
109
129
|
*/
|
|
110
|
-
export function MaxLength(limit: number, message?: string): PropType<LengthType> {
|
|
130
|
+
export function MaxLength(limit: number, message?: string): PropType<LengthType> {
|
|
131
|
+
return input({ maxlength: { limit, message } });
|
|
132
|
+
}
|
|
111
133
|
|
|
112
134
|
/**
|
|
113
135
|
* The minimum value
|
|
@@ -137,7 +159,9 @@ export function Max(limit: NumericLikeIntrinsic, message?: string): PropType<Num
|
|
|
137
159
|
* @augments `@travetto/schema:Input`
|
|
138
160
|
* @kind decorator
|
|
139
161
|
*/
|
|
140
|
-
export function Email(message?: string): PropType<StringType> {
|
|
162
|
+
export function Email(message?: string): PropType<StringType> {
|
|
163
|
+
return Match(CommonRegex.email, message);
|
|
164
|
+
}
|
|
141
165
|
|
|
142
166
|
/**
|
|
143
167
|
* Mark an input as an telephone number
|
|
@@ -145,7 +169,9 @@ export function Email(message?: string): PropType<StringType> { return Match(Com
|
|
|
145
169
|
* @augments `@travetto/schema:Input`
|
|
146
170
|
* @kind decorator
|
|
147
171
|
*/
|
|
148
|
-
export function Telephone(message?: string): PropType<StringType> {
|
|
172
|
+
export function Telephone(message?: string): PropType<StringType> {
|
|
173
|
+
return Match(CommonRegex.telephone, message);
|
|
174
|
+
}
|
|
149
175
|
|
|
150
176
|
/**
|
|
151
177
|
* Mark an input as a url
|
|
@@ -153,7 +179,9 @@ export function Telephone(message?: string): PropType<StringType> { return Match
|
|
|
153
179
|
* @augments `@travetto/schema:Input`
|
|
154
180
|
* @kind decorator
|
|
155
181
|
*/
|
|
156
|
-
export function Url(message?: string): PropType<StringType> {
|
|
182
|
+
export function Url(message?: string): PropType<StringType> {
|
|
183
|
+
return Match(CommonRegex.url, message);
|
|
184
|
+
}
|
|
157
185
|
|
|
158
186
|
/**
|
|
159
187
|
* Determine the numeric precision of the value
|
|
@@ -162,35 +190,45 @@ export function Url(message?: string): PropType<StringType> { return Match(Commo
|
|
|
162
190
|
* @augments `@travetto/schema:Input`
|
|
163
191
|
* @kind decorator
|
|
164
192
|
*/
|
|
165
|
-
export function Precision(digits: number, decimals?: number): PropType<number> {
|
|
193
|
+
export function Precision(digits: number, decimals?: number): PropType<number> {
|
|
194
|
+
return input({ precision: [digits, decimals] });
|
|
195
|
+
}
|
|
166
196
|
|
|
167
197
|
/**
|
|
168
198
|
* Mark a number as an integer
|
|
169
199
|
* @augments `@travetto/schema:Input`
|
|
170
200
|
* @kind decorator
|
|
171
201
|
*/
|
|
172
|
-
export function Integer(): PropType<NumberType> {
|
|
202
|
+
export function Integer(): PropType<NumberType> {
|
|
203
|
+
return Precision(0);
|
|
204
|
+
}
|
|
173
205
|
|
|
174
206
|
/**
|
|
175
207
|
* Mark a number as a float
|
|
176
208
|
* @augments `@travetto/schema:Input`
|
|
177
209
|
* @kind decorator
|
|
178
210
|
*/
|
|
179
|
-
export function Float(): PropType<number> {
|
|
211
|
+
export function Float(): PropType<number> {
|
|
212
|
+
return Precision(10, 7);
|
|
213
|
+
}
|
|
180
214
|
|
|
181
215
|
/**
|
|
182
216
|
* Mark a number as a long value
|
|
183
217
|
* @augments `@travetto/schema:Input`
|
|
184
218
|
* @kind decorator
|
|
185
219
|
*/
|
|
186
|
-
export function Long(): PropType<NumberType> {
|
|
220
|
+
export function Long(): PropType<NumberType> {
|
|
221
|
+
return Precision(19, 0);
|
|
222
|
+
}
|
|
187
223
|
|
|
188
224
|
/**
|
|
189
225
|
* Mark a number as a currency
|
|
190
226
|
* @augments `@travetto/schema:Input`
|
|
191
227
|
* @kind decorator
|
|
192
228
|
*/
|
|
193
|
-
export function Currency(): PropType<NumberType> {
|
|
229
|
+
export function Currency(): PropType<NumberType> {
|
|
230
|
+
return Precision(13, 2);
|
|
231
|
+
}
|
|
194
232
|
|
|
195
233
|
/**
|
|
196
234
|
* Specifier for the input
|
|
@@ -198,18 +236,20 @@ export function Currency(): PropType<NumberType> { return Precision(13, 2); }
|
|
|
198
236
|
* @augments `@travetto/schema:Input`
|
|
199
237
|
* @kind decorator
|
|
200
238
|
*/
|
|
201
|
-
export function Specifier(...specifiers: string[]): PropType<unknown> {
|
|
239
|
+
export function Specifier(...specifiers: string[]): PropType<unknown> {
|
|
240
|
+
return input({ specifiers });
|
|
241
|
+
}
|
|
202
242
|
|
|
203
243
|
/**
|
|
204
244
|
* Sets the discriminator field via a property decorator
|
|
205
245
|
* @augments `@travetto/schema:Input`
|
|
206
246
|
* @kind decorator
|
|
207
247
|
*/
|
|
208
|
-
export function DiscriminatorField(): (
|
|
248
|
+
export function DiscriminatorField(): (instance: ClassInstance, property: string) => void {
|
|
209
249
|
return (instance: ClassInstance, property: string): void => {
|
|
210
250
|
SchemaRegistryIndex.getForRegister(getClass(instance)).register({
|
|
211
251
|
discriminatedBase: true,
|
|
212
252
|
discriminatedField: property
|
|
213
253
|
});
|
|
214
254
|
};
|
|
215
|
-
}
|
|
255
|
+
}
|
package/src/decorator/method.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type Any,
|
|
1
|
+
import { type Any, type ClassInstance, castTo, getClass } from '@travetto/runtime';
|
|
2
2
|
|
|
3
|
-
import type { SchemaMethodConfig } from '../service/types.ts';
|
|
4
3
|
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
4
|
+
import type { SchemaMethodConfig } from '../service/types.ts';
|
|
5
5
|
import type { MethodValidatorFn } from '../validate/types.ts';
|
|
6
6
|
|
|
7
7
|
type MethodDecorator = (instance: ClassInstance, property: string, descriptor: PropertyDescriptor) => PropertyDescriptor | void;
|
package/src/decorator/schema.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Class, castTo, type DeepPartial } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { BindUtil } from '../bind-util.ts';
|
|
4
|
+
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
4
5
|
import type { SchemaClassConfig, ViewFieldsConfig } from '../service/types.ts';
|
|
5
6
|
import type { ValidatorFn } from '../validate/types.ts';
|
|
6
|
-
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Provides all the valid string type fields from a given type T
|
|
@@ -31,7 +31,8 @@ export function Schema(config?: Partial<Pick<SchemaClassConfig, 'validators' | '
|
|
|
31
31
|
* @param fn The validator function
|
|
32
32
|
* @kind decorator
|
|
33
33
|
*/
|
|
34
|
-
export const Validator =
|
|
34
|
+
export const Validator =
|
|
35
|
+
<T>(fn: ValidatorFn<T, string>) =>
|
|
35
36
|
(cls: Class<T>): void => {
|
|
36
37
|
SchemaRegistryIndex.getForRegister(cls).register({ validators: [castTo(fn)] });
|
|
37
38
|
};
|
|
@@ -68,4 +69,4 @@ export function Discriminated<T>(field: ValidStringField<T>) {
|
|
|
68
69
|
return (cls: Class<Partial<T>>): void => {
|
|
69
70
|
SchemaRegistryIndex.getForRegister(cls).register({ discriminatedField: field, discriminatedBase: true });
|
|
70
71
|
};
|
|
71
|
-
}
|
|
72
|
+
}
|
package/src/internal/types.ts
CHANGED
|
@@ -20,16 +20,16 @@ function bindPoint(input: unknown): [number, number] | typeof InvalidSymbol | un
|
|
|
20
20
|
*/
|
|
21
21
|
function validatePoint(input: unknown): 'type' | undefined {
|
|
22
22
|
const bound = bindPoint(input);
|
|
23
|
-
return bound !== InvalidSymbol && bound && !isNaN(bound[0]) && !isNaN(bound[1]) ? undefined : 'type';
|
|
23
|
+
return bound !== InvalidSymbol && bound && !Number.isNaN(bound[0]) && !Number.isNaN(bound[1]) ? undefined : 'type';
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Point Contract
|
|
28
28
|
*/
|
|
29
|
-
export class PointContract {
|
|
29
|
+
export class PointContract {}
|
|
30
30
|
|
|
31
31
|
SchemaTypeUtil.setSchemaTypeConfig(PointContract, {
|
|
32
32
|
validate: validatePoint,
|
|
33
|
-
bind: bindPoint
|
|
33
|
+
bind: bindPoint
|
|
34
34
|
});
|
|
35
35
|
Object.defineProperty(PointContract, 'name', { value: 'Point' });
|
package/src/name.ts
CHANGED
|
@@ -6,7 +6,6 @@ const ID_REGEX = /(\d{1,100})Δ$/;
|
|
|
6
6
|
* Name resolver, specifically for synthetic types
|
|
7
7
|
*/
|
|
8
8
|
export class SchemaNameResolver {
|
|
9
|
-
|
|
10
9
|
#schemaIdToName = new Map<string, string>();
|
|
11
10
|
#digits: number;
|
|
12
11
|
|
|
@@ -27,4 +26,4 @@ export class SchemaNameResolver {
|
|
|
27
26
|
return cls.name;
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
|
-
}
|
|
29
|
+
}
|
|
@@ -1,34 +1,43 @@
|
|
|
1
1
|
import type { RegistryAdapter } from '@travetto/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { BinaryUtil, type Class, castKey, castTo, describeFunction, RuntimeError, safeAssign } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
type
|
|
7
|
-
|
|
5
|
+
CONSTRUCTOR_PROPERTY,
|
|
6
|
+
type SchemaClassConfig,
|
|
7
|
+
type SchemaCoreConfig,
|
|
8
|
+
type SchemaFieldConfig,
|
|
9
|
+
type SchemaFieldMap,
|
|
10
|
+
type SchemaInputConfig,
|
|
11
|
+
type SchemaMethodConfig,
|
|
12
|
+
type SchemaParameterConfig
|
|
8
13
|
} from './types.ts';
|
|
9
14
|
|
|
10
15
|
export type SchemaDiscriminatedInfo = Required<Pick<SchemaClassConfig, 'discriminatedType' | 'discriminatedField' | 'discriminatedBase'>>;
|
|
11
16
|
|
|
12
|
-
const classToDiscriminatedType = (cls: Class): string =>
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
const classToDiscriminatedType = (cls: Class): string =>
|
|
18
|
+
cls.name
|
|
19
|
+
.replace(/([A-Z])([A-Z][a-z])/g, (all, left, right) => `${left}_${right.toLowerCase()}`)
|
|
20
|
+
.replace(/([a-z]|\b)([A-Z])/g, (all, left, right) => (left ? `${left}_${right.toLowerCase()}` : right.toLowerCase()))
|
|
21
|
+
.toLowerCase();
|
|
16
22
|
|
|
17
23
|
function assignMetadata<T>(key: symbol, base: SchemaCoreConfig, data: Partial<T>[]): T {
|
|
18
|
-
const metadata = base.metadata ??= {};
|
|
19
|
-
const out = metadata[key] ??= {};
|
|
24
|
+
const metadata = (base.metadata ??= {});
|
|
25
|
+
const out = (metadata[key] ??= {});
|
|
20
26
|
for (const d of data) {
|
|
21
27
|
safeAssign(out, d);
|
|
22
28
|
}
|
|
23
29
|
return castTo(out);
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
function combineCore<T extends SchemaCoreConfig>(
|
|
32
|
+
function combineCore<T extends SchemaCoreConfig>(
|
|
33
|
+
base: T,
|
|
34
|
+
config: Partial<T>
|
|
35
|
+
): Pick<Partial<T>, 'metadata' | 'private' | 'description' | 'examples'> {
|
|
27
36
|
return {
|
|
28
|
-
...config.metadata ? { metadata: { ...base.metadata, ...config.metadata } } : {},
|
|
29
|
-
...config.private ? { private: config.private ?? base.private } : {},
|
|
30
|
-
...config.description ? { description: config.description || base.description } : {},
|
|
31
|
-
...config.examples ? { examples: [...(base.examples ?? []), ...(config.examples ?? [])] } : {}
|
|
37
|
+
...(config.metadata ? { metadata: { ...base.metadata, ...config.metadata } } : {}),
|
|
38
|
+
...(config.private ? { private: config.private ?? base.private } : {}),
|
|
39
|
+
...(config.description ? { description: config.description || base.description } : {}),
|
|
40
|
+
...(config.examples ? { examples: [...(base.examples ?? []), ...(config.examples ?? [])] } : {})
|
|
32
41
|
};
|
|
33
42
|
}
|
|
34
43
|
|
|
@@ -37,14 +46,16 @@ function combineInputs<T extends SchemaInputConfig>(base: T, configs: Partial<T>
|
|
|
37
46
|
if (config) {
|
|
38
47
|
safeAssign(base, {
|
|
39
48
|
...config,
|
|
40
|
-
...config.aliases ? { aliases: [...base.aliases ?? [], ...config.aliases ?? []] } : {},
|
|
41
|
-
...config.specifiers ? { specifiers: [...base.specifiers ?? [], ...config.specifiers ?? []] } : {},
|
|
42
|
-
...config.enum
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
...(config.aliases ? { aliases: [...(base.aliases ?? []), ...(config.aliases ?? [])] } : {}),
|
|
50
|
+
...(config.specifiers ? { specifiers: [...(base.specifiers ?? []), ...(config.specifiers ?? [])] } : {}),
|
|
51
|
+
...(config.enum
|
|
52
|
+
? {
|
|
53
|
+
enum: {
|
|
54
|
+
message: config.enum?.message ?? base.enum?.message,
|
|
55
|
+
values: (config.enum?.values ?? base.enum?.values ?? []).toSorted()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
: {}),
|
|
48
59
|
...combineCore(base, config)
|
|
49
60
|
});
|
|
50
61
|
}
|
|
@@ -58,7 +69,7 @@ function combineMethods<T extends SchemaMethodConfig>(base: T, configs: Partial<
|
|
|
58
69
|
...config,
|
|
59
70
|
...combineCore(base, config),
|
|
60
71
|
parameters: config.parameters ?? base.parameters,
|
|
61
|
-
validators: [...base.validators, ...(config.validators ?? [])]
|
|
72
|
+
validators: [...base.validators, ...(config.validators ?? [])]
|
|
62
73
|
});
|
|
63
74
|
if (config.parameters) {
|
|
64
75
|
for (const param of config.parameters) {
|
|
@@ -84,25 +95,28 @@ function getConstructorConfig<T extends SchemaClassConfig>(base: Partial<T>, par
|
|
|
84
95
|
|
|
85
96
|
function combineClassWithParent<T extends SchemaClassConfig>(base: T, parent: T): T {
|
|
86
97
|
safeAssign(base, {
|
|
87
|
-
...base.views ? { views: { ...parent.views, ...base.views } } : {},
|
|
88
|
-
...base.validators ? { validators: [...parent.validators, ...base.validators] } : {},
|
|
89
|
-
...base.metadata ? { metadata: { ...parent.metadata, ...base.metadata } } : {},
|
|
98
|
+
...(base.views ? { views: { ...parent.views, ...base.views } } : {}),
|
|
99
|
+
...(base.validators ? { validators: [...parent.validators, ...base.validators] } : {}),
|
|
100
|
+
...(base.metadata ? { metadata: { ...parent.metadata, ...base.metadata } } : {}),
|
|
90
101
|
interfaces: [...parent.interfaces, ...base.interfaces],
|
|
91
102
|
methods: { ...parent.methods, ...base.methods },
|
|
92
103
|
description: base.description || parent.description,
|
|
93
104
|
examples: [...(parent.examples ?? []), ...(base.examples ?? [])],
|
|
94
|
-
discriminatedField: base.discriminatedField ?? parent.discriminatedField
|
|
105
|
+
discriminatedField: base.discriminatedField ?? parent.discriminatedField
|
|
95
106
|
});
|
|
96
107
|
switch (base.mappedOperation) {
|
|
97
108
|
case 'Required':
|
|
98
109
|
case 'Partial': {
|
|
99
110
|
base.fields = Object.fromEntries(
|
|
100
|
-
Object.entries(parent.fields).map(([key, value]) => [
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
Object.entries(parent.fields).map(([key, value]) => [
|
|
112
|
+
key,
|
|
113
|
+
{
|
|
114
|
+
...value,
|
|
115
|
+
required: {
|
|
116
|
+
active: base.mappedOperation === 'Required'
|
|
117
|
+
}
|
|
104
118
|
}
|
|
105
|
-
|
|
119
|
+
])
|
|
106
120
|
);
|
|
107
121
|
break;
|
|
108
122
|
}
|
|
@@ -110,9 +124,7 @@ function combineClassWithParent<T extends SchemaClassConfig>(base: T, parent: T)
|
|
|
110
124
|
case 'Omit': {
|
|
111
125
|
const keys = new Set<string>(base.mappedFields ?? []);
|
|
112
126
|
base.fields = Object.fromEntries(
|
|
113
|
-
Object.entries(parent.fields).filter(([key]) =>
|
|
114
|
-
base.mappedOperation === 'Pick' ? keys.has(key) : !keys.has(key)
|
|
115
|
-
)
|
|
127
|
+
Object.entries(parent.fields).filter(([key]) => (base.mappedOperation === 'Pick' ? keys.has(key) : !keys.has(key)))
|
|
116
128
|
);
|
|
117
129
|
break;
|
|
118
130
|
}
|
|
@@ -127,19 +139,18 @@ function combineClasses<T extends SchemaClassConfig>(base: T, configs: Partial<T
|
|
|
127
139
|
for (const config of configs) {
|
|
128
140
|
Object.assign(base, {
|
|
129
141
|
...config,
|
|
130
|
-
...config.views ? { views: { ...base.views, ...config.views } } : {},
|
|
131
|
-
...config.validators ? { validators: [...base.validators, ...config.validators] } : {},
|
|
142
|
+
...(config.views ? { views: { ...base.views, ...config.views } } : {}),
|
|
143
|
+
...(config.validators ? { validators: [...base.validators, ...config.validators] } : {}),
|
|
132
144
|
...combineCore(base, config),
|
|
133
145
|
interfaces: [...base.interfaces, ...(config.interfaces ?? [])],
|
|
134
146
|
methods: { ...base.methods, ...config.methods },
|
|
135
|
-
fields: { ...base.fields, ...config.fields }
|
|
147
|
+
fields: { ...base.fields, ...config.fields }
|
|
136
148
|
});
|
|
137
149
|
}
|
|
138
150
|
return base;
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig> {
|
|
142
|
-
|
|
143
154
|
#cls: Class;
|
|
144
155
|
#config: SchemaClassConfig;
|
|
145
156
|
#views: Map<string, SchemaFieldMap> = new Map();
|
|
@@ -150,14 +161,14 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
150
161
|
}
|
|
151
162
|
|
|
152
163
|
register(...data: Partial<SchemaClassConfig>[]): SchemaClassConfig {
|
|
153
|
-
const config = this.#config ??= {
|
|
164
|
+
const config = (this.#config ??= {
|
|
154
165
|
methods: {},
|
|
155
166
|
class: this.#cls,
|
|
156
167
|
views: {},
|
|
157
168
|
validators: [],
|
|
158
169
|
interfaces: [],
|
|
159
|
-
fields: {}
|
|
160
|
-
};
|
|
170
|
+
fields: {}
|
|
171
|
+
});
|
|
161
172
|
return combineClasses(config, data);
|
|
162
173
|
}
|
|
163
174
|
|
|
@@ -173,7 +184,7 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
173
184
|
|
|
174
185
|
registerField(field: string, ...data: Partial<SchemaFieldConfig>[]): SchemaFieldConfig {
|
|
175
186
|
const classConfig = this.register({});
|
|
176
|
-
const config = classConfig.fields[field] ??= { name: field, class: this.#cls, type: null! };
|
|
187
|
+
const config = (classConfig.fields[field] ??= { name: field, class: this.#cls, type: null! });
|
|
177
188
|
const combined = combineInputs(config, data);
|
|
178
189
|
return combined;
|
|
179
190
|
}
|
|
@@ -202,7 +213,7 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
202
213
|
|
|
203
214
|
registerMethod(method: string, ...data: Partial<SchemaMethodConfig>[]): SchemaMethodConfig {
|
|
204
215
|
const classConfig = this.register();
|
|
205
|
-
const config = classConfig.methods[method] ??= { class: this.#cls, parameters: [], validators: [] };
|
|
216
|
+
const config = (classConfig.methods[method] ??= { class: this.#cls, parameters: [], validators: [] });
|
|
206
217
|
return combineMethods(config, data);
|
|
207
218
|
}
|
|
208
219
|
|
|
@@ -218,7 +229,7 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
218
229
|
|
|
219
230
|
registerParameter(method: string, idx: number, ...data: Partial<SchemaParameterConfig>[]): SchemaParameterConfig {
|
|
220
231
|
const params = this.registerMethod(method, {}).parameters;
|
|
221
|
-
const config = params[idx] ??= { method, index: idx, class: this.#cls, array: false, type: null! };
|
|
232
|
+
const config = (params[idx] ??= { method, index: idx, class: this.#cls, array: false, type: null! });
|
|
222
233
|
return combineInputs(config, data);
|
|
223
234
|
}
|
|
224
235
|
|
|
@@ -251,8 +262,8 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
251
262
|
},
|
|
252
263
|
enum: {
|
|
253
264
|
values: [config.discriminatedType],
|
|
254
|
-
message: `${config.discriminatedField} can only be '${config.discriminatedType}'
|
|
255
|
-
}
|
|
265
|
+
message: `${config.discriminatedField} can only be '${config.discriminatedType}'`
|
|
266
|
+
}
|
|
256
267
|
};
|
|
257
268
|
}
|
|
258
269
|
|
|
@@ -260,11 +271,14 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
260
271
|
for (const view of Object.keys(config.views)) {
|
|
261
272
|
const fields = config.views[view];
|
|
262
273
|
const withoutSet = 'without' in fields ? new Set<string>(fields.without) : undefined;
|
|
263
|
-
const fieldList = withoutSet
|
|
264
|
-
Object.keys(config.fields).filter(field => !withoutSet.has(field))
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
274
|
+
const fieldList = withoutSet
|
|
275
|
+
? Object.keys(config.fields).filter(field => !withoutSet.has(field))
|
|
276
|
+
: 'with' in fields
|
|
277
|
+
? fields.with
|
|
278
|
+
: [];
|
|
279
|
+
|
|
280
|
+
this.#views.set(
|
|
281
|
+
view,
|
|
268
282
|
fieldList.reduce<SchemaFieldMap>((map, value) => {
|
|
269
283
|
map[value] = config.fields[value];
|
|
270
284
|
return map;
|
|
@@ -275,7 +289,7 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
275
289
|
config.methods[CONSTRUCTOR_PROPERTY] = getConstructorConfig(config, parent);
|
|
276
290
|
|
|
277
291
|
for (const method of Object.values(config.methods)) {
|
|
278
|
-
method.parameters = method.parameters.toSorted((a, b) =>
|
|
292
|
+
method.parameters = method.parameters.toSorted((a, b) => a.index! - b.index!);
|
|
279
293
|
if (method.returnType?.type) {
|
|
280
294
|
method.returnType.binary = BinaryUtil.isBinaryTypeReference(method.returnType.type);
|
|
281
295
|
}
|
|
@@ -373,4 +387,4 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
373
387
|
}
|
|
374
388
|
return undefined;
|
|
375
389
|
}
|
|
376
|
-
}
|
|
390
|
+
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { type RegistrationMethods, type RegistryIndex, RegistryIndexStore
|
|
2
|
-
import {
|
|
1
|
+
import { type RegistrationMethods, Registry, type RegistryIndex, RegistryIndexStore } from '@travetto/registry';
|
|
2
|
+
import { type Class, castKey, castTo, classConstruct, getParentClass, RuntimeError } from '@travetto/runtime';
|
|
3
3
|
|
|
4
|
-
import type { SchemaFieldConfig, SchemaClassConfig } from './types.ts';
|
|
5
4
|
import { type SchemaDiscriminatedInfo, SchemaRegistryAdapter } from './registry-adapter.ts';
|
|
5
|
+
import type { SchemaClassConfig, SchemaFieldConfig } from './types.ts';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Schema registry index for managing schema configurations across classes
|
|
9
9
|
*/
|
|
10
10
|
export class SchemaRegistryIndex implements RegistryIndex {
|
|
11
|
-
|
|
12
11
|
static #instance = Registry.registerIndex(SchemaRegistryIndex);
|
|
13
12
|
|
|
14
13
|
static getForRegister(cls: Class): SchemaRegistryAdapter {
|
|
@@ -36,7 +35,7 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
static visitFields<T>(cls: Class<T>, onField: (field: SchemaFieldConfig, path: SchemaFieldConfig[]) => void): void {
|
|
39
|
-
|
|
38
|
+
this.#instance.visitFields(cls, onField);
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
static getDiscriminatedClasses(cls: Class): Class[] {
|
|
@@ -63,7 +62,9 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
63
62
|
#baseSchema = new Map<Class, Class>();
|
|
64
63
|
#byDiscriminatedTypes = new Map<Class, Map<string, Class>>();
|
|
65
64
|
|
|
66
|
-
/** @private */ constructor(source: unknown) {
|
|
65
|
+
/** @private */ constructor(source: unknown) {
|
|
66
|
+
Registry.validateConstructor(source);
|
|
67
|
+
}
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* Register discriminated types for a class
|
|
@@ -128,7 +129,9 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
128
129
|
}
|
|
129
130
|
const requested = map.get(type)!;
|
|
130
131
|
if (!(classConstruct(requested) instanceof targetClass)) {
|
|
131
|
-
throw new RuntimeError(
|
|
132
|
+
throw new RuntimeError(
|
|
133
|
+
`Resolved discriminated type '${type}' for class ${base.name} is not an instance of requested type ${targetClass.name}`
|
|
134
|
+
);
|
|
132
135
|
}
|
|
133
136
|
return requested;
|
|
134
137
|
}
|
|
@@ -137,10 +140,13 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
137
140
|
/**
|
|
138
141
|
* Visit fields recursively
|
|
139
142
|
*/
|
|
140
|
-
visitFields<T>(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
visitFields<T>(
|
|
144
|
+
cls: Class<T>,
|
|
145
|
+
onField: (field: SchemaFieldConfig, path: SchemaFieldConfig[]) => void,
|
|
146
|
+
_path: SchemaFieldConfig[] = [],
|
|
147
|
+
root = cls
|
|
148
|
+
): void {
|
|
149
|
+
const fields = SchemaRegistryIndex.has(cls) ? Object.values(this.getClassConfig(cls).fields) : [];
|
|
144
150
|
for (const field of fields) {
|
|
145
151
|
if (SchemaRegistryIndex.has(field.type)) {
|
|
146
152
|
this.visitFields(field.type, onField, [..._path, field], root);
|
|
@@ -155,7 +161,7 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
155
161
|
* @param cls The base class to resolve from
|
|
156
162
|
*/
|
|
157
163
|
getDiscriminatedClasses(cls: Class): Class[] {
|
|
158
|
-
return [...this.#byDiscriminatedTypes.get(cls)?.values() ?? []];
|
|
164
|
+
return [...(this.#byDiscriminatedTypes.get(cls)?.values() ?? [])];
|
|
159
165
|
}
|
|
160
166
|
|
|
161
167
|
/**
|
|
@@ -168,4 +174,4 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
168
174
|
}
|
|
169
175
|
return undefined;
|
|
170
176
|
}
|
|
171
|
-
}
|
|
177
|
+
}
|
package/src/service/types.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Any, Class, IntrinsicType, NumericLikeIntrinsic, Primitive } from
|
|
|
3
3
|
import type { MethodValidatorFn, ValidatorFn } from '../validate/types.ts';
|
|
4
4
|
|
|
5
5
|
type TemplateLiteralPart = string | NumberConstructor | StringConstructor | BooleanConstructor;
|
|
6
|
-
export type TemplateLiteral = { operation: 'and' | 'or'
|
|
6
|
+
export type TemplateLiteral = { operation: 'and' | 'or'; values: (TemplateLiteralPart | TemplateLiteral)[] };
|
|
7
7
|
|
|
8
8
|
export const CONSTRUCTOR_PROPERTY = 'CONSTRUCTOR';
|
|
9
9
|
|
|
@@ -160,31 +160,31 @@ export interface SchemaInputConfig extends SchemaCoreConfig, SchemaBasicType {
|
|
|
160
160
|
/**
|
|
161
161
|
* Is the field required
|
|
162
162
|
*/
|
|
163
|
-
required?: { active: boolean
|
|
163
|
+
required?: { active: boolean; message?: string };
|
|
164
164
|
/**
|
|
165
165
|
* Does the field expect a match
|
|
166
166
|
*/
|
|
167
|
-
match?: { regex: RegExp
|
|
167
|
+
match?: { regex: RegExp; message?: string; template?: TemplateLiteral };
|
|
168
168
|
/**
|
|
169
169
|
* Minimum value configuration
|
|
170
170
|
*/
|
|
171
|
-
min?: { limit: NumericLikeIntrinsic
|
|
171
|
+
min?: { limit: NumericLikeIntrinsic; message?: string };
|
|
172
172
|
/**
|
|
173
173
|
* Maximum value configuration
|
|
174
174
|
*/
|
|
175
|
-
max?: { limit: NumericLikeIntrinsic
|
|
175
|
+
max?: { limit: NumericLikeIntrinsic; message?: string };
|
|
176
176
|
/**
|
|
177
177
|
* Minimum length configuration
|
|
178
178
|
*/
|
|
179
|
-
minlength?: { limit: number
|
|
179
|
+
minlength?: { limit: number; message?: string };
|
|
180
180
|
/**
|
|
181
181
|
* Maximum length configuration
|
|
182
182
|
*/
|
|
183
|
-
maxlength?: { limit: number
|
|
183
|
+
maxlength?: { limit: number; message?: string };
|
|
184
184
|
/**
|
|
185
185
|
* Enumerated values
|
|
186
186
|
*/
|
|
187
|
-
enum?: { values: Primitive[]
|
|
187
|
+
enum?: { values: Primitive[]; message: string };
|
|
188
188
|
/**
|
|
189
189
|
* Default value
|
|
190
190
|
*/
|
|
@@ -235,4 +235,4 @@ export interface SchemaFieldConfig extends SchemaInputConfig {
|
|
|
235
235
|
accessor?: boolean;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
export type ViewFieldsConfig<T> = { with: Extract<
|
|
238
|
+
export type ViewFieldsConfig<T> = { with: Extract<keyof T, string>[] } | { without: Extract<keyof T, string>[] };
|