@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 CHANGED
@@ -206,7 +206,7 @@ Validation Failed {
206
206
  "message": "Validation errors have occurred",
207
207
  "category": "data",
208
208
  "type": "ValidationResultError",
209
- "at": "2021-03-14T05:00:00.618Z",
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": "2021-03-14T05:00:00.837Z",
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.0.4",
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.0.2",
33
- "@travetto/transformer": "^2.0.3"
32
+ "@travetto/registry": "^2.1.1",
33
+ "@travetto/transformer": "^2.1.1"
34
34
  },
35
35
  "optionalPeerDependencies": {
36
- "@types/faker": "^5.5.6",
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];
@@ -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
  }
@@ -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: string) {
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, this.getSubTypeName(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, this.getSubTypeName(clz));
370
+ this.registerSubTypes(clz);
339
371
  }
340
372
 
341
373
  SchemaChangeListener.clearSchemaDependency(cls);
@@ -64,6 +64,10 @@ export interface ClassConfig extends DescribableConfig {
64
64
  * Global validators
65
65
  */
66
66
  validators: ValidatorFn<unknown, unknown>[];
67
+ /**
68
+ * Is the class a sub type
69
+ */
70
+ subType?: boolean;
67
71
  }
68
72
 
69
73
  /**
@@ -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');