@travetto/schema 7.0.0-rc.1 → 7.0.0-rc.3
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 +34 -34
- package/__index__.ts +3 -3
- package/package.json +3 -3
- package/src/bind-util.ts +90 -90
- package/src/data.ts +40 -39
- package/src/decorator/common.ts +5 -5
- package/src/decorator/field.ts +9 -7
- package/src/decorator/input.ts +28 -26
- package/src/decorator/method.ts +3 -1
- package/src/decorator/schema.ts +2 -2
- package/src/internal/types.ts +4 -4
- package/src/name.ts +3 -3
- package/src/service/registry-adapter.ts +64 -61
- package/src/service/registry-index.ts +18 -73
- package/src/service/types.ts +14 -18
- package/src/types.ts +1 -1
- package/src/validate/error.ts +2 -2
- package/src/validate/messages.ts +10 -10
- package/src/validate/regex.ts +22 -0
- package/src/validate/types.ts +4 -4
- package/src/validate/validator.ts +80 -80
- package/support/transformer/util.ts +29 -26
- package/support/transformer.schema.ts +7 -7
- package/src/service/changes.ts +0 -152
- package/src/validate/regexp.ts +0 -22
|
@@ -7,14 +7,16 @@ import {
|
|
|
7
7
|
CONSTRUCTOR_PROPERTY
|
|
8
8
|
} from './types';
|
|
9
9
|
|
|
10
|
+
export type SchemaDiscriminatedInfo = Required<Pick<SchemaClassConfig, 'discriminatedType' | 'discriminatedField' | 'discriminatedBase'>>;
|
|
11
|
+
|
|
10
12
|
const classToDiscriminatedType = (cls: Class): string => cls.name
|
|
11
|
-
.replace(/([A-Z])([A-Z][a-z])/g, (all,
|
|
12
|
-
.replace(/([a-z]|\b)([A-Z])/g, (all,
|
|
13
|
+
.replace(/([A-Z])([A-Z][a-z])/g, (all, left, right) => `${left}_${right.toLowerCase()}`)
|
|
14
|
+
.replace(/([a-z]|\b)([A-Z])/g, (all, left, right) => left ? `${left}_${right.toLowerCase()}` : right.toLowerCase())
|
|
13
15
|
.toLowerCase();
|
|
14
16
|
|
|
15
17
|
function assignMetadata<T>(key: symbol, base: SchemaCoreConfig, data: Partial<T>[]): T {
|
|
16
|
-
const
|
|
17
|
-
const out =
|
|
18
|
+
const metadata = base.metadata ??= {};
|
|
19
|
+
const out = metadata[key] ??= {};
|
|
18
20
|
for (const d of data) {
|
|
19
21
|
safeAssign(out, d);
|
|
20
22
|
}
|
|
@@ -69,6 +71,7 @@ function getConstructorConfig<T extends SchemaClassConfig>(base: Partial<T>, par
|
|
|
69
71
|
const parentCons = parent?.methods?.[CONSTRUCTOR_PROPERTY];
|
|
70
72
|
const baseCons = base.methods?.[CONSTRUCTOR_PROPERTY];
|
|
71
73
|
return {
|
|
74
|
+
class: base.class!,
|
|
72
75
|
parameters: [],
|
|
73
76
|
validators: [],
|
|
74
77
|
...parentCons,
|
|
@@ -92,8 +95,8 @@ function combineClassWithParent<T extends SchemaClassConfig>(base: T, parent: T)
|
|
|
92
95
|
case 'Required':
|
|
93
96
|
case 'Partial': {
|
|
94
97
|
base.fields = Object.fromEntries(
|
|
95
|
-
Object.entries(parent.fields).map(([
|
|
96
|
-
...
|
|
98
|
+
Object.entries(parent.fields).map(([key, value]) => [key, {
|
|
99
|
+
...value,
|
|
97
100
|
required: {
|
|
98
101
|
active: base.mappedOperation === 'Required'
|
|
99
102
|
}
|
|
@@ -105,8 +108,8 @@ function combineClassWithParent<T extends SchemaClassConfig>(base: T, parent: T)
|
|
|
105
108
|
case 'Omit': {
|
|
106
109
|
const keys = new Set<string>(base.mappedFields ?? []);
|
|
107
110
|
base.fields = Object.fromEntries(
|
|
108
|
-
Object.entries(parent.fields).filter(([
|
|
109
|
-
base.mappedOperation === 'Pick' ? keys.has(
|
|
111
|
+
Object.entries(parent.fields).filter(([key]) =>
|
|
112
|
+
base.mappedOperation === 'Pick' ? keys.has(key) : !keys.has(key)
|
|
110
113
|
)
|
|
111
114
|
);
|
|
112
115
|
break;
|
|
@@ -145,7 +148,7 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
register(...data: Partial<SchemaClassConfig>[]): SchemaClassConfig {
|
|
148
|
-
const
|
|
151
|
+
const config = this.#config ??= {
|
|
149
152
|
methods: {},
|
|
150
153
|
class: this.#cls,
|
|
151
154
|
views: {},
|
|
@@ -153,38 +156,38 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
153
156
|
interfaces: [],
|
|
154
157
|
fields: {},
|
|
155
158
|
};
|
|
156
|
-
return combineClasses(
|
|
159
|
+
return combineClasses(config, data);
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
registerMetadata<T>(key: symbol, ...data: Partial<T>[]): T {
|
|
160
|
-
const
|
|
161
|
-
return assignMetadata(key,
|
|
163
|
+
const config = this.register({});
|
|
164
|
+
return assignMetadata(key, config, data);
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
getMetadata<T>(key: symbol): T | undefined {
|
|
165
|
-
const
|
|
166
|
-
return castTo<T>(
|
|
168
|
+
const metadata = this.#config?.metadata;
|
|
169
|
+
return castTo<T>(metadata?.[key]);
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
registerField(field: string
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const combined = combineInputs(
|
|
172
|
+
registerField(field: string, ...data: Partial<SchemaFieldConfig>[]): SchemaFieldConfig {
|
|
173
|
+
const classConfig = this.register({});
|
|
174
|
+
const config = classConfig.fields[field] ??= { name: field, class: this.#cls, type: null! };
|
|
175
|
+
const combined = combineInputs(config, data);
|
|
173
176
|
return combined;
|
|
174
177
|
}
|
|
175
178
|
|
|
176
|
-
registerFieldMetadata<T>(field: string
|
|
177
|
-
const
|
|
178
|
-
return assignMetadata(key,
|
|
179
|
+
registerFieldMetadata<T>(field: string, key: symbol, ...data: Partial<T>[]): T {
|
|
180
|
+
const config = this.registerField(field);
|
|
181
|
+
return assignMetadata(key, config, data);
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
getFieldMetadata<T>(field: string
|
|
182
|
-
const
|
|
183
|
-
return castTo<T>(
|
|
184
|
+
getFieldMetadata<T>(field: string, key: symbol): T | undefined {
|
|
185
|
+
const metadata = this.#config?.fields[field]?.metadata;
|
|
186
|
+
return castTo<T>(metadata?.[key]);
|
|
184
187
|
}
|
|
185
188
|
|
|
186
|
-
registerClass({ methods, ...
|
|
187
|
-
this.register({ ...
|
|
189
|
+
registerClass({ methods, ...config }: Partial<SchemaClassConfig> = {}): SchemaClassConfig {
|
|
190
|
+
this.register({ ...config });
|
|
188
191
|
if (methods?.[CONSTRUCTOR_PROPERTY]) {
|
|
189
192
|
const { parameters, ...rest } = methods[CONSTRUCTOR_PROPERTY];
|
|
190
193
|
this.registerMethod(CONSTRUCTOR_PROPERTY, rest);
|
|
@@ -195,36 +198,36 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
195
198
|
return this.#config;
|
|
196
199
|
}
|
|
197
200
|
|
|
198
|
-
registerMethod(method: string
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
return combineMethods(
|
|
201
|
+
registerMethod(method: string, ...data: Partial<SchemaMethodConfig>[]): SchemaMethodConfig {
|
|
202
|
+
const classConfig = this.register();
|
|
203
|
+
const config = classConfig.methods[method] ??= { class: this.#cls, parameters: [], validators: [] };
|
|
204
|
+
return combineMethods(config, data);
|
|
202
205
|
}
|
|
203
206
|
|
|
204
|
-
registerMethodMetadata<T>(method: string
|
|
205
|
-
const
|
|
206
|
-
return assignMetadata(key,
|
|
207
|
+
registerMethodMetadata<T>(method: string, key: symbol, ...data: Partial<T>[]): T {
|
|
208
|
+
const config = this.registerMethod(method);
|
|
209
|
+
return assignMetadata(key, config, data);
|
|
207
210
|
}
|
|
208
211
|
|
|
209
|
-
getMethodMetadata<T>(method: string
|
|
210
|
-
const
|
|
211
|
-
return castTo<T>(
|
|
212
|
+
getMethodMetadata<T>(method: string, key: symbol): T | undefined {
|
|
213
|
+
const metadata = this.#config?.methods[method]?.metadata;
|
|
214
|
+
return castTo<T>(metadata?.[key]);
|
|
212
215
|
}
|
|
213
216
|
|
|
214
|
-
registerParameter(method: string
|
|
217
|
+
registerParameter(method: string, idx: number, ...data: Partial<SchemaParameterConfig>[]): SchemaParameterConfig {
|
|
215
218
|
const params = this.registerMethod(method, {}).parameters;
|
|
216
|
-
const
|
|
217
|
-
return combineInputs(
|
|
219
|
+
const config = params[idx] ??= { method, index: idx, class: this.#cls, array: false, type: null! };
|
|
220
|
+
return combineInputs(config, data);
|
|
218
221
|
}
|
|
219
222
|
|
|
220
|
-
registerParameterMetadata<T>(method: string
|
|
221
|
-
const
|
|
222
|
-
return assignMetadata(key,
|
|
223
|
+
registerParameterMetadata<T>(method: string, idx: number, key: symbol, ...data: Partial<T>[]): T {
|
|
224
|
+
const config = this.registerParameter(method, idx);
|
|
225
|
+
return assignMetadata(key, config, data);
|
|
223
226
|
}
|
|
224
227
|
|
|
225
|
-
getParameterMetadata<T>(method: string
|
|
226
|
-
const
|
|
227
|
-
return castTo<T>(
|
|
228
|
+
getParameterMetadata<T>(method: string, idx: number, key: symbol): T | undefined {
|
|
229
|
+
const metadata = this.#config?.methods[method]?.parameters[idx]?.metadata;
|
|
230
|
+
return castTo<T>(metadata?.[key]);
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
finalize(parent?: SchemaClassConfig): void {
|
|
@@ -256,13 +259,13 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
256
259
|
const fields = config.views[view];
|
|
257
260
|
const withoutSet = 'without' in fields ? new Set<string>(fields.without) : undefined;
|
|
258
261
|
const fieldList = withoutSet ?
|
|
259
|
-
Object.keys(config.fields).filter(
|
|
262
|
+
Object.keys(config.fields).filter(field => !withoutSet.has(field)) :
|
|
260
263
|
('with' in fields ? fields.with : []);
|
|
261
264
|
|
|
262
265
|
this.#views.set(view,
|
|
263
|
-
fieldList.reduce<SchemaFieldMap>((
|
|
264
|
-
|
|
265
|
-
return
|
|
266
|
+
fieldList.reduce<SchemaFieldMap>((map, value) => {
|
|
267
|
+
map[value] = config.fields[value];
|
|
268
|
+
return map;
|
|
266
269
|
}, {})
|
|
267
270
|
);
|
|
268
271
|
}
|
|
@@ -278,19 +281,19 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
278
281
|
return this.#config;
|
|
279
282
|
}
|
|
280
283
|
|
|
281
|
-
getField(field: string
|
|
284
|
+
getField(field: string): SchemaFieldConfig {
|
|
282
285
|
return this.#config.fields[field];
|
|
283
286
|
}
|
|
284
287
|
|
|
285
|
-
getMethod(method: string
|
|
286
|
-
const
|
|
287
|
-
if (!
|
|
288
|
+
getMethod(method: string): SchemaMethodConfig {
|
|
289
|
+
const methodConfig = this.#config.methods[method];
|
|
290
|
+
if (!methodConfig) {
|
|
288
291
|
throw new AppError(`Unknown method ${String(method)} on class ${this.#cls.Ⲑid}`);
|
|
289
292
|
}
|
|
290
|
-
return
|
|
293
|
+
return methodConfig;
|
|
291
294
|
}
|
|
292
295
|
|
|
293
|
-
getMethodReturnType(method: string
|
|
296
|
+
getMethodReturnType(method: string): Class {
|
|
294
297
|
return this.getMethod(method).returnType!.type;
|
|
295
298
|
}
|
|
296
299
|
|
|
@@ -305,8 +308,8 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
305
308
|
}
|
|
306
309
|
|
|
307
310
|
/**
|
|
308
|
-
|
|
309
|
-
|
|
311
|
+
* Provides the prototype-derived descriptor for a property
|
|
312
|
+
*/
|
|
310
313
|
getAccessorDescriptor(field: string): PropertyDescriptor {
|
|
311
314
|
if (!this.#accessorDescriptors.has(field)) {
|
|
312
315
|
let proto = this.#cls.prototype;
|
|
@@ -321,16 +324,16 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
321
324
|
/**
|
|
322
325
|
* Ensure type is set properly
|
|
323
326
|
*/
|
|
324
|
-
ensureInstanceTypeField<T>(
|
|
327
|
+
ensureInstanceTypeField<T>(value: T): T {
|
|
325
328
|
const config = this.getDiscriminatedConfig();
|
|
326
329
|
if (config) {
|
|
327
330
|
const typeField = castKey<T>(config.discriminatedField);
|
|
328
|
-
|
|
331
|
+
value[typeField] ??= castTo(config.discriminatedType); // Assign if missing
|
|
329
332
|
}
|
|
330
|
-
return
|
|
333
|
+
return value;
|
|
331
334
|
}
|
|
332
335
|
|
|
333
|
-
getDiscriminatedConfig():
|
|
336
|
+
getDiscriminatedConfig(): SchemaDiscriminatedInfo | undefined {
|
|
334
337
|
const { discriminatedField, discriminatedType, discriminatedBase } = this.#config;
|
|
335
338
|
if (discriminatedType && discriminatedField) {
|
|
336
339
|
return { discriminatedType, discriminatedField, discriminatedBase: !!discriminatedBase };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AppError, castKey, castTo, Class, classConstruct, getParentClass
|
|
1
|
+
import { RegistrationMethods, RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
|
|
2
|
+
import { AppError, castKey, castTo, Class, classConstruct, getParentClass } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import { SchemaFieldConfig, SchemaClassConfig } from './types.ts';
|
|
5
|
-
import { SchemaRegistryAdapter } from './registry-adapter.ts';
|
|
6
|
-
import { SchemaChangeListener } from './changes.ts';
|
|
5
|
+
import { SchemaDiscriminatedInfo, SchemaRegistryAdapter } from './registry-adapter.ts';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Schema registry index for managing schema configurations across classes
|
|
@@ -20,7 +19,7 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
20
19
|
return this.#instance.store.get(cls).get();
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
static getDiscriminatedConfig<T>(cls: Class<T>):
|
|
22
|
+
static getDiscriminatedConfig<T>(cls: Class<T>): SchemaDiscriminatedInfo | undefined {
|
|
24
23
|
return this.#instance.store.get(cls).getDiscriminatedConfig();
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -28,16 +27,12 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
28
27
|
return this.#instance.store.has(cls);
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
static getClassById(classId: string): Class {
|
|
32
|
-
return this.#instance.store.getClassById(classId);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
30
|
static getDiscriminatedTypes(cls: Class): string[] | undefined {
|
|
36
31
|
return this.#instance.getDiscriminatedTypes(cls);
|
|
37
32
|
}
|
|
38
33
|
|
|
39
|
-
static resolveInstanceType<T>(cls: Class<T>,
|
|
40
|
-
return this.#instance.resolveInstanceType(cls,
|
|
34
|
+
static resolveInstanceType<T>(cls: Class<T>, item: T): Class {
|
|
35
|
+
return this.#instance.resolveInstanceType(cls, item);
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
static visitFields<T>(cls: Class<T>, onField: (field: SchemaFieldConfig, path: SchemaFieldConfig[]) => void): void {
|
|
@@ -68,6 +63,8 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
68
63
|
#baseSchema = new Map<Class, Class>();
|
|
69
64
|
#byDiscriminatedTypes = new Map<Class, Map<string, Class>>();
|
|
70
65
|
|
|
66
|
+
/** @private */ constructor(source: unknown) { Registry.validateConstructor(source); }
|
|
67
|
+
|
|
71
68
|
/**
|
|
72
69
|
* Register discriminated types for a class
|
|
73
70
|
*/
|
|
@@ -84,44 +81,11 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
84
81
|
this.#byDiscriminatedTypes.get(base)!.set(config.discriminatedType, cls);
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
Util.queueMacroTask().then(() => {
|
|
89
|
-
SchemaChangeListener.emitFieldChanges({
|
|
90
|
-
type: 'changed',
|
|
91
|
-
curr: this.getClassConfig(event.curr),
|
|
92
|
-
prev: this.getClassConfig(event.prev)
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#onRemoving(event: ChangeEvent<Class> & { type: 'removing' }): void {
|
|
98
|
-
SchemaChangeListener.clearSchemaDependency(event.prev);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#onAdded(event: ChangeEvent<Class> & { type: 'added' }): void {
|
|
102
|
-
Util.queueMacroTask().then(() => {
|
|
103
|
-
SchemaChangeListener.emitFieldChanges({
|
|
104
|
-
type: 'added',
|
|
105
|
-
curr: this.getClassConfig(event.curr)
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
process(events: ChangeEvent<Class>[]): void {
|
|
111
|
-
for (const event of events) {
|
|
112
|
-
if (event.type === 'changed') {
|
|
113
|
-
this.#onChanged(event);
|
|
114
|
-
} else if (event.type === 'removing') {
|
|
115
|
-
this.#onRemoving(event);
|
|
116
|
-
} else if (event.type === 'added') {
|
|
117
|
-
this.#onAdded(event);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
84
|
+
beforeChangeSetComplete(): void {
|
|
121
85
|
// Rebuild indices after every "process" batch
|
|
122
86
|
this.#byDiscriminatedTypes.clear();
|
|
123
|
-
for (const
|
|
124
|
-
this.#registerDiscriminatedTypes(
|
|
87
|
+
for (const cls of this.store.getClasses()) {
|
|
88
|
+
this.#registerDiscriminatedTypes(cls);
|
|
125
89
|
}
|
|
126
90
|
}
|
|
127
91
|
|
|
@@ -134,15 +98,15 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
134
98
|
*/
|
|
135
99
|
getBaseClass(cls: Class): Class {
|
|
136
100
|
if (!this.#baseSchema.has(cls)) {
|
|
137
|
-
let
|
|
101
|
+
let config = this.getClassConfig(cls);
|
|
138
102
|
let parent: Class | undefined = cls;
|
|
139
|
-
while (parent &&
|
|
103
|
+
while (parent && config.discriminatedType && !config.discriminatedBase) {
|
|
140
104
|
parent = getParentClass(parent);
|
|
141
105
|
if (parent) {
|
|
142
|
-
|
|
106
|
+
config = this.store.getOptional(parent)?.get() ?? config;
|
|
143
107
|
}
|
|
144
108
|
}
|
|
145
|
-
this.#baseSchema.set(cls,
|
|
109
|
+
this.#baseSchema.set(cls, config.class);
|
|
146
110
|
}
|
|
147
111
|
return this.#baseSchema.get(cls)!;
|
|
148
112
|
}
|
|
@@ -150,16 +114,16 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
150
114
|
/**
|
|
151
115
|
* Find the resolved type for a given instance
|
|
152
116
|
* @param cls Class for instance
|
|
153
|
-
* @param
|
|
117
|
+
* @param item Actual instance
|
|
154
118
|
*/
|
|
155
|
-
resolveInstanceType<T>(cls: Class<T>,
|
|
119
|
+
resolveInstanceType<T>(cls: Class<T>, item: T): Class {
|
|
156
120
|
const { discriminatedField, discriminatedType, class: targetClass } = this.store.get(cls).get();
|
|
157
121
|
if (!discriminatedField) {
|
|
158
122
|
return targetClass;
|
|
159
123
|
} else {
|
|
160
124
|
const base = this.getBaseClass(targetClass);
|
|
161
125
|
const map = this.#byDiscriminatedTypes.get(base);
|
|
162
|
-
const type = castTo<string>(
|
|
126
|
+
const type = castTo<string>(item[castKey<T>(discriminatedField)]) ?? discriminatedType;
|
|
163
127
|
if (!type) {
|
|
164
128
|
throw new AppError(`Unable to resolve discriminated type for class ${base.name} without a type`);
|
|
165
129
|
}
|
|
@@ -174,25 +138,6 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
174
138
|
}
|
|
175
139
|
}
|
|
176
140
|
|
|
177
|
-
/**
|
|
178
|
-
* Track changes to schemas, and track the dependent changes
|
|
179
|
-
* @param cls The root class of the hierarchy
|
|
180
|
-
* @param curr The new class
|
|
181
|
-
* @param path The path within the object hierarchy
|
|
182
|
-
*/
|
|
183
|
-
trackSchemaDependencies(cls: Class, curr: Class = cls, path: SchemaFieldConfig[] = []): void {
|
|
184
|
-
const config = this.getClassConfig(curr);
|
|
185
|
-
|
|
186
|
-
SchemaChangeListener.trackSchemaDependency(curr, cls, path, this.getClassConfig(cls));
|
|
187
|
-
|
|
188
|
-
// Read children
|
|
189
|
-
for (const field of Object.values(config.fields)) {
|
|
190
|
-
if (SchemaRegistryIndex.has(field.type) && field.type !== cls) {
|
|
191
|
-
this.trackSchemaDependencies(cls, field.type, [...path, field]);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
141
|
/**
|
|
197
142
|
* Visit fields recursively
|
|
198
143
|
*/
|
package/src/service/types.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Any, Class, Primitive } from '@travetto/runtime';
|
|
|
3
3
|
import { MethodValidatorFn, ValidatorFn } from '../validate/types.ts';
|
|
4
4
|
|
|
5
5
|
type TemplateLiteralPart = string | NumberConstructor | StringConstructor | BooleanConstructor;
|
|
6
|
-
export type TemplateLiteral = {
|
|
6
|
+
export type TemplateLiteral = { operation: 'and' | 'or', values: (TemplateLiteralPart | TemplateLiteral)[] };
|
|
7
7
|
|
|
8
8
|
export const CONSTRUCTOR_PROPERTY = 'CONSTRUCTOR';
|
|
9
9
|
|
|
@@ -36,6 +36,10 @@ export type SchemaBasicType = {
|
|
|
36
36
|
* Basic schema configuration
|
|
37
37
|
*/
|
|
38
38
|
export interface SchemaCoreConfig {
|
|
39
|
+
/**
|
|
40
|
+
* Schema class
|
|
41
|
+
*/
|
|
42
|
+
class: Class;
|
|
39
43
|
/**
|
|
40
44
|
* Description
|
|
41
45
|
*/
|
|
@@ -83,17 +87,13 @@ export interface SchemaFieldMap {
|
|
|
83
87
|
/**
|
|
84
88
|
* List of all fields
|
|
85
89
|
*/
|
|
86
|
-
[key: string
|
|
90
|
+
[key: string]: SchemaFieldConfig;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
/**
|
|
90
94
|
* Class configuration
|
|
91
95
|
*/
|
|
92
96
|
export interface SchemaClassConfig extends SchemaCoreConfig {
|
|
93
|
-
/**
|
|
94
|
-
* Target class
|
|
95
|
-
*/
|
|
96
|
-
class: Class;
|
|
97
97
|
/**
|
|
98
98
|
* List of all views
|
|
99
99
|
*/
|
|
@@ -121,7 +121,7 @@ export interface SchemaClassConfig extends SchemaCoreConfig {
|
|
|
121
121
|
/**
|
|
122
122
|
* Method configs
|
|
123
123
|
*/
|
|
124
|
-
methods: Record<string
|
|
124
|
+
methods: Record<string, SchemaMethodConfig>;
|
|
125
125
|
/**
|
|
126
126
|
* Interfaces that the class implements
|
|
127
127
|
*/
|
|
@@ -144,10 +144,6 @@ export interface SchemaInputConfig extends SchemaCoreConfig, SchemaBasicType {
|
|
|
144
144
|
* Key name for validation when dealing with complex types
|
|
145
145
|
*/
|
|
146
146
|
view?: string;
|
|
147
|
-
/**
|
|
148
|
-
* Owner class
|
|
149
|
-
*/
|
|
150
|
-
owner: Class;
|
|
151
147
|
/**
|
|
152
148
|
* List of aliases
|
|
153
149
|
*/
|
|
@@ -167,23 +163,23 @@ export interface SchemaInputConfig extends SchemaCoreConfig, SchemaBasicType {
|
|
|
167
163
|
/**
|
|
168
164
|
* Does the field expect a match
|
|
169
165
|
*/
|
|
170
|
-
match?: {
|
|
166
|
+
match?: { regex: RegExp, message?: string, template?: TemplateLiteral };
|
|
171
167
|
/**
|
|
172
168
|
* Minimum value configuration
|
|
173
169
|
*/
|
|
174
|
-
min?: {
|
|
170
|
+
min?: { limit: number | Date, message?: string };
|
|
175
171
|
/**
|
|
176
172
|
* Maximum value configuration
|
|
177
173
|
*/
|
|
178
|
-
max?: {
|
|
174
|
+
max?: { limit: number | Date, message?: string };
|
|
179
175
|
/**
|
|
180
176
|
* Minimum length configuration
|
|
181
177
|
*/
|
|
182
|
-
minlength?: {
|
|
178
|
+
minlength?: { limit: number, message?: string };
|
|
183
179
|
/**
|
|
184
180
|
* Maximum length configuration
|
|
185
181
|
*/
|
|
186
|
-
maxlength?: {
|
|
182
|
+
maxlength?: { limit: number, message?: string };
|
|
187
183
|
/**
|
|
188
184
|
* Enumerated values
|
|
189
185
|
*/
|
|
@@ -209,7 +205,7 @@ export interface SchemaParameterConfig extends SchemaInputConfig {
|
|
|
209
205
|
/**
|
|
210
206
|
* Method the parameter belongs to
|
|
211
207
|
*/
|
|
212
|
-
method: string
|
|
208
|
+
method: string;
|
|
213
209
|
/**
|
|
214
210
|
* Source text for the parameter
|
|
215
211
|
*/
|
|
@@ -223,7 +219,7 @@ export interface SchemaFieldConfig extends SchemaInputConfig {
|
|
|
223
219
|
/**
|
|
224
220
|
* Field name
|
|
225
221
|
*/
|
|
226
|
-
name: string
|
|
222
|
+
name: string;
|
|
227
223
|
/**
|
|
228
224
|
* Is the field readonly, or write only?, defaults to no restrictions
|
|
229
225
|
*/
|
package/src/types.ts
CHANGED
package/src/validate/error.ts
CHANGED
|
@@ -22,6 +22,6 @@ export class TypeMismatchError extends AppError {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export function isValidationError(
|
|
26
|
-
return !!
|
|
25
|
+
export function isValidationError(error: unknown): error is ValidationError {
|
|
26
|
+
return !!error && error instanceof Error && 'path' in error;
|
|
27
27
|
}
|
package/src/validate/messages.ts
CHANGED
|
@@ -5,14 +5,14 @@ export const Messages = new Map<string, string>(Object.entries({
|
|
|
5
5
|
default: '{path} is not valid',
|
|
6
6
|
type: '{path} is not a valid {type}',
|
|
7
7
|
required: '{path} is required',
|
|
8
|
-
minlength: '{path} is not long enough ({
|
|
9
|
-
maxlength: '{path} is too long ({
|
|
10
|
-
match: '{path} should match {
|
|
11
|
-
min: '{path} is less than ({
|
|
12
|
-
max: '{path} is greater than ({
|
|
13
|
-
telephone: '{path} is not a valid phone number',
|
|
14
|
-
url: '{path} is not a valid url',
|
|
15
|
-
simpleName: '{path} is not a proper name',
|
|
16
|
-
postalCode: '{path} is not a valid postal code',
|
|
17
|
-
email: '{path} is not a valid email address'
|
|
8
|
+
minlength: '{path} is not long enough ({limit})',
|
|
9
|
+
maxlength: '{path} is too long ({limit})',
|
|
10
|
+
match: '{path} should match {regex}',
|
|
11
|
+
min: '{path} is less than ({limit})',
|
|
12
|
+
max: '{path} is greater than ({limit})',
|
|
13
|
+
'[[:telephone:]]': '{path} is not a valid phone number',
|
|
14
|
+
'[[:url:]]': '{path} is not a valid url',
|
|
15
|
+
'[[:simpleName:]]': '{path} is not a proper name',
|
|
16
|
+
'[[:postalCode:]]': '{path} is not a valid postal code',
|
|
17
|
+
'[[:email:]]': '{path} is not a valid email address'
|
|
18
18
|
}));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TypedObject } from '@travetto/runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* List of common regular expressions for fields
|
|
5
|
+
*/
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
7
|
+
export const [CommonRegex, CommonRegexToName] = (() => {
|
|
8
|
+
const regexToName = new Map<RegExp, string>();
|
|
9
|
+
const regexes = {
|
|
10
|
+
email: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
|
|
11
|
+
telephone: /^(\+?\d{1,3}(\s*-?\s*|\s+))?((\(\d{3}\))|\d{3})(\s*|-|[.])(\d{3})(\s*|-|[.])(\d{4})(\s+(x|ext[.]?)\s*\d+)?$/,
|
|
12
|
+
url: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/,
|
|
13
|
+
simpleName: /^([a-zA-Z\u0080-\u024F]{0,100}(?:. |-| |')){0,10}[a-zA-Z\u0080-\u024F]+$/,
|
|
14
|
+
postalCode: /^\d{5}(?:[-\s]\d{4})?$/
|
|
15
|
+
};
|
|
16
|
+
// Rebind regexes
|
|
17
|
+
for (const key of TypedObject.keys(regexes)) {
|
|
18
|
+
const name = `[[:${key}:]]`;
|
|
19
|
+
regexToName.set(regexes[key], name);
|
|
20
|
+
}
|
|
21
|
+
return [regexes, regexToName];
|
|
22
|
+
})();
|
package/src/validate/types.ts
CHANGED
|
@@ -25,11 +25,11 @@ export interface ValidationError {
|
|
|
25
25
|
/**
|
|
26
26
|
* Regular expression to match
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
regex?: string;
|
|
29
29
|
/**
|
|
30
30
|
* Number to compare against
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
limit?: number | Date;
|
|
33
33
|
/**
|
|
34
34
|
* The type of the field
|
|
35
35
|
*/
|
|
@@ -63,11 +63,11 @@ export interface ValidationResult {
|
|
|
63
63
|
/**
|
|
64
64
|
* Potential regular expression for the result
|
|
65
65
|
*/
|
|
66
|
-
|
|
66
|
+
regex?: RegExp;
|
|
67
67
|
/**
|
|
68
68
|
* Number to compare against
|
|
69
69
|
*/
|
|
70
|
-
|
|
70
|
+
limit?: number | Date;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
type OrPromise<T> = T | Promise<T>;
|