@travetto/schema 2.0.4 → 2.1.1
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 +2 -2
- package/package.json +4 -4
- package/src/bind-util.ts +2 -0
- package/src/decorator/schema.ts +11 -0
- package/src/service/registry.ts +48 -16
- package/src/service/types.ts +4 -0
- package/src/validate/validator.ts +1 -1
- package/support/transform-util.ts +1 -0
- package/support/transformer.schema.ts +1 -1
package/README.md
CHANGED
|
@@ -206,7 +206,7 @@ Validation Failed {
|
|
|
206
206
|
"message": "Validation errors have occurred",
|
|
207
207
|
"category": "data",
|
|
208
208
|
"type": "ValidationResultError",
|
|
209
|
-
"at": "
|
|
209
|
+
"at": "2022-03-14T04:00:00.618Z",
|
|
210
210
|
"errors": [
|
|
211
211
|
{
|
|
212
212
|
"kind": "type",
|
|
@@ -435,7 +435,7 @@ Validation Failed {
|
|
|
435
435
|
"message": "Validation errors have occurred",
|
|
436
436
|
"category": "data",
|
|
437
437
|
"type": "ValidationResultError",
|
|
438
|
-
"at": "
|
|
438
|
+
"at": "2022-03-14T04:00:00.837Z",
|
|
439
439
|
"errors": [
|
|
440
440
|
{
|
|
441
441
|
"kind": "type",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/schema",
|
|
3
3
|
"displayName": "Schema",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.1.1",
|
|
5
5
|
"description": "Data type registry for runtime validation, reflection and binding. ",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"schema",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"directory": "module/schema"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@travetto/registry": "^2.
|
|
33
|
-
"@travetto/transformer": "^2.
|
|
32
|
+
"@travetto/registry": "^2.1.1",
|
|
33
|
+
"@travetto/transformer": "^2.1.1"
|
|
34
34
|
},
|
|
35
35
|
"optionalPeerDependencies": {
|
|
36
|
-
"@types/faker": "^5.5.
|
|
36
|
+
"@types/faker": "^5.5.9",
|
|
37
37
|
"faker": "^5.5.3"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
package/src/bind-util.ts
CHANGED
|
@@ -149,6 +149,8 @@ export class BindUtil {
|
|
|
149
149
|
return data as T;
|
|
150
150
|
} else {
|
|
151
151
|
const tgt = new (cls as ConcreteClass<T>)();
|
|
152
|
+
SchemaRegistry.ensureInstanceTypeField(cls, tgt);
|
|
153
|
+
|
|
152
154
|
for (const [k, v] of Object.entries(tgt)) { // Do not retain undefined fields
|
|
153
155
|
if (v === undefined) {
|
|
154
156
|
delete tgt[k as keyof T];
|
package/src/decorator/schema.ts
CHANGED
|
@@ -36,4 +36,15 @@ export function View<T>(name: string, fields: ViewFieldsConfig<Partial<T>>) {
|
|
|
36
36
|
return (target: Class<Partial<T>>) => {
|
|
37
37
|
SchemaRegistry.registerPendingView(target, name, fields);
|
|
38
38
|
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register a class as a subtype, with a specific discriminator
|
|
43
|
+
* @param name
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
export function SubType<T>(name: string) {
|
|
47
|
+
return (target: Class<Partial<T>>) => {
|
|
48
|
+
SchemaRegistry.registerSubTypes(target, name);
|
|
49
|
+
};
|
|
39
50
|
}
|
package/src/service/registry.ts
CHANGED
|
@@ -24,6 +24,36 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
24
24
|
super(RootRegistry);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
#computeSubTypeName(cls: Class) {
|
|
28
|
+
if (!this.#typeKeys.has(cls)) {
|
|
29
|
+
this.#typeKeys.set(cls, cls.name
|
|
30
|
+
.replace(/([A-Z])([A-Z][a-z])/g, (all, l, r) => `${l}_${r.toLowerCase()}`)
|
|
31
|
+
.replace(/([a-z]|\b)([A-Z])/g, (all, l, r) => l ? `${l}_${r.toLowerCase()}` : r.toLowerCase())
|
|
32
|
+
.toLowerCase());
|
|
33
|
+
}
|
|
34
|
+
return this.#typeKeys.get(cls);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get subtype name for a class
|
|
39
|
+
* @param cls Base class
|
|
40
|
+
*/
|
|
41
|
+
getSubTypeName(cls: Class) {
|
|
42
|
+
if (this.get(cls).subType) {
|
|
43
|
+
return this.#computeSubTypeName(cls);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Ensure type is set properl
|
|
49
|
+
*/
|
|
50
|
+
ensureInstanceTypeField<T>(cls: Class, o: T) {
|
|
51
|
+
const withType = (o as { type?: string });
|
|
52
|
+
if (this.get(cls)?.subType && 'type' in this.get(cls).views[AllViewⲐ].schema && !withType.type) { // Do we have a type field defined
|
|
53
|
+
withType.type = this.#computeSubTypeName(cls); // Assign if missing
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
27
57
|
/**
|
|
28
58
|
* Find the subtype for a given instance
|
|
29
59
|
* @param cls Class for instance
|
|
@@ -44,7 +74,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
44
74
|
if (type) {
|
|
45
75
|
return this.#subTypes.get(cls)!.get(typeId) ?? cls;
|
|
46
76
|
}
|
|
47
|
-
} else {
|
|
77
|
+
} else if (this.get(cls)?.subType) {
|
|
48
78
|
const expectedType = this.#typeKeys.get(cls);
|
|
49
79
|
if (expectedType && typeof type === 'string' && expectedType !== type) {
|
|
50
80
|
throw new AppError(`Data of type ${type} does not match expected class type ${expectedType}`, 'data');
|
|
@@ -53,23 +83,19 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
53
83
|
return cls;
|
|
54
84
|
}
|
|
55
85
|
|
|
56
|
-
/**
|
|
57
|
-
* Get subtype name for a class
|
|
58
|
-
* @param cls Base class
|
|
59
|
-
*/
|
|
60
|
-
getSubTypeName(cls: Class) {
|
|
61
|
-
return cls.name
|
|
62
|
-
.replace(/([A-Z])([A-Z][a-z])/g, (all, l, r) => `${l}_${r.toLowerCase()}`)
|
|
63
|
-
.replace(/([a-z]|\b)([A-Z])/g, (all, l, r) => l ? `${l}_${r.toLowerCase()}` : r.toLowerCase())
|
|
64
|
-
.toLowerCase();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
86
|
/**
|
|
68
87
|
* Register sub types for a class
|
|
69
88
|
* @param cls The class to register against
|
|
70
89
|
* @param type The subtype name
|
|
71
90
|
*/
|
|
72
|
-
registerSubTypes(cls: Class, type
|
|
91
|
+
registerSubTypes(cls: Class, type?: string) {
|
|
92
|
+
// Mark as subtype
|
|
93
|
+
(this.get(cls) ?? this.getOrCreatePending(cls)).subType = true;
|
|
94
|
+
|
|
95
|
+
type ??= this.#computeSubTypeName(cls)!;
|
|
96
|
+
|
|
97
|
+
this.#typeKeys.set(cls, type);
|
|
98
|
+
|
|
73
99
|
let parent = this.getParentClass(cls)!;
|
|
74
100
|
let parentConfig = this.get(parent);
|
|
75
101
|
|
|
@@ -77,12 +103,13 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
77
103
|
if (!this.#subTypes.has(parent)) {
|
|
78
104
|
this.#subTypes.set(parent, new Map());
|
|
79
105
|
}
|
|
80
|
-
this.#typeKeys.set(cls, type);
|
|
81
106
|
this.#subTypes.get(parent)!.set(type, cls);
|
|
82
107
|
this.#subTypes.get(parent)!.set(cls.ᚕid, cls);
|
|
83
108
|
parent = this.getParentClass(parent!)!;
|
|
84
109
|
parentConfig = this.get(parent);
|
|
85
110
|
}
|
|
111
|
+
|
|
112
|
+
return type;
|
|
86
113
|
}
|
|
87
114
|
|
|
88
115
|
/**
|
|
@@ -109,6 +136,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
109
136
|
return {
|
|
110
137
|
class: cls,
|
|
111
138
|
validators: [],
|
|
139
|
+
subType: false,
|
|
112
140
|
views: {
|
|
113
141
|
[AllViewⲐ]: {
|
|
114
142
|
schema: {},
|
|
@@ -259,6 +287,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
259
287
|
schema: { ...dest.views[AllViewⲐ].schema, ...src.views[AllViewⲐ].schema },
|
|
260
288
|
fields: [...dest.views[AllViewⲐ].fields, ...src.views[AllViewⲐ].fields]
|
|
261
289
|
};
|
|
290
|
+
dest.subType = src.subType || dest.subType;
|
|
262
291
|
dest.title = src.title || dest.title;
|
|
263
292
|
dest.validators = [...src.validators, ...dest.validators];
|
|
264
293
|
return dest;
|
|
@@ -305,7 +334,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
305
334
|
}
|
|
306
335
|
}
|
|
307
336
|
|
|
308
|
-
this.registerSubTypes(cls
|
|
337
|
+
this.registerSubTypes(cls);
|
|
309
338
|
|
|
310
339
|
// Merge pending, back on top, to allow child to have higher precedence
|
|
311
340
|
const pending = this.getOrCreatePending(cls);
|
|
@@ -332,10 +361,13 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
332
361
|
if (e.type === 'removing' && this.hasExpired(cls)) {
|
|
333
362
|
// Recompute subtypes
|
|
334
363
|
this.#subTypes.clear();
|
|
364
|
+
this.#typeKeys.delete(cls);
|
|
335
365
|
this.#methodSchemas.delete(cls);
|
|
366
|
+
|
|
367
|
+
// Recompute subtype mappings
|
|
336
368
|
for (const el of this.entries.keys()) {
|
|
337
369
|
const clz = this.entries.get(el)!.class;
|
|
338
|
-
this.registerSubTypes(clz
|
|
370
|
+
this.registerSubTypes(clz);
|
|
339
371
|
}
|
|
340
372
|
|
|
341
373
|
SchemaChangeListener.clearSchemaDependency(cls);
|
package/src/service/types.ts
CHANGED
|
@@ -225,7 +225,7 @@ export class SchemaValidator {
|
|
|
225
225
|
* @param view The optional view to limit the scope to
|
|
226
226
|
*/
|
|
227
227
|
static async validate<T>(cls: Class<T>, o: T, view?: string): Promise<T> {
|
|
228
|
-
if (!Util.isPlainObject(o) && !(o instanceof cls)) {
|
|
228
|
+
if (!Util.isPlainObject(o) && !(o instanceof cls || cls.ᚕid === (o as ClassInstance<T>).constructor.ᚕid)) {
|
|
229
229
|
throw new TypeMismatchError(cls.name, (o as unknown as ClassInstance).constructor.name);
|
|
230
230
|
}
|
|
231
231
|
cls = SchemaRegistry.resolveSubTypeForInstance(cls, o);
|
|
@@ -73,6 +73,7 @@ export class SchemaTransformUtil {
|
|
|
73
73
|
const attrs: ts.PropertyAssignment[] = [];
|
|
74
74
|
|
|
75
75
|
if (!ts.isGetAccessorDeclaration(node) && !ts.isSetAccessorDeclaration(node)) {
|
|
76
|
+
// eslint-disable-next-line no-bitwise
|
|
76
77
|
if ((ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Readonly) > 0) {
|
|
77
78
|
attrs.push(state.factory.createPropertyAssignment('mode', state.fromLiteral('readonly')));
|
|
78
79
|
} else if (!node.questionToken && !typeExpr.undefinable && !node.initializer) {
|
|
@@ -3,8 +3,8 @@ import * as ts from 'typescript';
|
|
|
3
3
|
import {
|
|
4
4
|
TransformerState, OnProperty, OnClass, AfterClass, DecoratorMeta, DocUtil, DeclarationUtil, TransformerId, OnGetter, OnSetter
|
|
5
5
|
} from '@travetto/transformer';
|
|
6
|
+
|
|
6
7
|
import { SchemaTransformUtil } from './transform-util';
|
|
7
|
-
import { access } from 'fs';
|
|
8
8
|
|
|
9
9
|
const inSchema = Symbol.for('@trv:schema/schema');
|
|
10
10
|
const accessors = Symbol.for('@trv:schema/schema');
|