@travetto/schema 6.0.1 → 7.0.0-rc.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 +30 -26
- package/__index__.ts +4 -2
- package/package.json +3 -3
- package/src/bind-util.ts +47 -44
- package/src/decorator/common.ts +32 -16
- package/src/decorator/field.ts +21 -184
- package/src/decorator/input.ts +207 -0
- package/src/decorator/method.ts +28 -0
- package/src/decorator/schema.ts +36 -29
- package/src/name.ts +2 -2
- package/src/service/changes.ts +27 -30
- package/src/service/registry-adapter.ts +340 -0
- package/src/service/registry-index.ts +230 -0
- package/src/service/types.ts +113 -63
- package/src/validate/types.ts +4 -0
- package/src/validate/validator.ts +70 -64
- package/support/transformer/util.ts +147 -61
- package/support/transformer.schema.ts +137 -49
- package/src/service/registry.ts +0 -501
package/src/service/registry.ts
DELETED
|
@@ -1,501 +0,0 @@
|
|
|
1
|
-
import { Class, AppError, describeFunction, castTo, classConstruct, asFull, castKey } from '@travetto/runtime';
|
|
2
|
-
import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
|
|
3
|
-
|
|
4
|
-
import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig, ViewConfig, SchemaMethodConfig } from './types.ts';
|
|
5
|
-
import { SchemaChangeListener } from './changes.ts';
|
|
6
|
-
import { MethodValidatorFn } from '../validate/types.ts';
|
|
7
|
-
|
|
8
|
-
const classToSubTypeName = (cls: Class): string => cls.name
|
|
9
|
-
.replace(/([A-Z])([A-Z][a-z])/g, (all, l, r) => `${l}_${r.toLowerCase()}`)
|
|
10
|
-
.replace(/([a-z]|\b)([A-Z])/g, (all, l, r) => l ? `${l}_${r.toLowerCase()}` : r.toLowerCase())
|
|
11
|
-
.toLowerCase();
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Schema registry for listening to changes
|
|
15
|
-
*/
|
|
16
|
-
class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
17
|
-
|
|
18
|
-
#accessorDescriptors = new Map<Class, Map<string, PropertyDescriptor>>();
|
|
19
|
-
#subTypes = new Map<Class, Map<string, Class>>();
|
|
20
|
-
#pendingViews = new Map<Class, Map<string, ViewFieldsConfig<unknown>>>();
|
|
21
|
-
#baseSchema = new Map<Class, Class>();
|
|
22
|
-
|
|
23
|
-
constructor() {
|
|
24
|
-
super(RootRegistry);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Find base schema class for a given class
|
|
29
|
-
*/
|
|
30
|
-
getBaseSchema(cls: Class): Class {
|
|
31
|
-
if (!this.#baseSchema.has(cls)) {
|
|
32
|
-
let conf = this.get(cls) ?? this.getOrCreatePending(cls);
|
|
33
|
-
let parent = cls;
|
|
34
|
-
|
|
35
|
-
while (conf && !conf.baseType) {
|
|
36
|
-
parent = this.getParentClass(parent)!;
|
|
37
|
-
conf = this.get(parent) ?? this.pending.get(MetadataRegistry.id(parent));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
this.#baseSchema.set(cls, conf ? parent : cls);
|
|
41
|
-
}
|
|
42
|
-
return this.#baseSchema.get(cls)!;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Retrieve class level metadata
|
|
47
|
-
* @param cls
|
|
48
|
-
* @param prop
|
|
49
|
-
* @param key
|
|
50
|
-
* @returns
|
|
51
|
-
*/
|
|
52
|
-
getMetadata<K>(cls: Class, key: symbol): K | undefined {
|
|
53
|
-
const cfg = this.get(cls);
|
|
54
|
-
return castTo(cfg.metadata?.[key]);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Retrieve pending class level metadata, or create if needed
|
|
59
|
-
* @param cls
|
|
60
|
-
* @param prop
|
|
61
|
-
* @param key
|
|
62
|
-
* @returns
|
|
63
|
-
*/
|
|
64
|
-
getOrCreatePendingMetadata<K>(cls: Class, key: symbol, value: K): K {
|
|
65
|
-
const cfg = this.getOrCreatePending(cls);
|
|
66
|
-
return castTo((cfg.metadata ??= {})[key] ??= value);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Ensure type is set properly
|
|
71
|
-
*/
|
|
72
|
-
ensureInstanceTypeField<T>(cls: Class, o: T): void {
|
|
73
|
-
const schema = this.get(cls);
|
|
74
|
-
const typeField = castKey<T>(schema.subTypeField);
|
|
75
|
-
if (schema.subTypeName && typeField in schema.totalView.schema && !o[typeField]) { // Do we have a type field defined
|
|
76
|
-
o[typeField] = castTo(schema.subTypeName); // Assign if missing
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Provides the prototype-derived descriptor for a property
|
|
82
|
-
*/
|
|
83
|
-
getAccessorDescriptor(cls: Class, field: string): PropertyDescriptor {
|
|
84
|
-
if (!this.#accessorDescriptors.has(cls)) {
|
|
85
|
-
this.#accessorDescriptors.set(cls, new Map());
|
|
86
|
-
}
|
|
87
|
-
const map = this.#accessorDescriptors.get(cls)!;
|
|
88
|
-
if (!map.has(field)) {
|
|
89
|
-
let proto = cls.prototype;
|
|
90
|
-
while (proto && !Object.hasOwn(proto, field)) {
|
|
91
|
-
proto = proto.prototype;
|
|
92
|
-
}
|
|
93
|
-
map.set(field, Object.getOwnPropertyDescriptor(proto, field)!);
|
|
94
|
-
}
|
|
95
|
-
return map.get(field)!;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Find the resolved type for a given instance
|
|
100
|
-
* @param cls Class for instance
|
|
101
|
-
* @param o Actual instance
|
|
102
|
-
*/
|
|
103
|
-
resolveInstanceType<T>(cls: Class<T>, o: T): Class {
|
|
104
|
-
cls = this.get(cls.Ⲑid).class; // Resolve by id to handle any stale references
|
|
105
|
-
|
|
106
|
-
const base = this.getBaseSchema(cls);
|
|
107
|
-
const clsSchema = this.get(cls);
|
|
108
|
-
const baseSchema = this.get(base);
|
|
109
|
-
|
|
110
|
-
if (clsSchema.subTypeName || baseSchema.baseType) { // We have a sub type
|
|
111
|
-
const type = castTo<string>(o[castKey<T>(baseSchema.subTypeField)]) ?? clsSchema.subTypeName ?? baseSchema.subTypeName;
|
|
112
|
-
const subType = this.#subTypes.get(base)!.get(type)!;
|
|
113
|
-
if (subType && !(classConstruct(subType) instanceof cls)) {
|
|
114
|
-
throw new AppError(`Resolved class ${subType.name} is not assignable to ${cls.name}`);
|
|
115
|
-
}
|
|
116
|
-
return subType;
|
|
117
|
-
} else {
|
|
118
|
-
return cls;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Return all subtypes by discriminator for a given class
|
|
124
|
-
* @param cls The base class to resolve from
|
|
125
|
-
*/
|
|
126
|
-
getSubTypesForClass(cls: Class): Class[] | undefined {
|
|
127
|
-
const res = this.#subTypes.get(cls)?.values();
|
|
128
|
-
return res ? [...res] : undefined;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Register sub types for a class
|
|
133
|
-
* @param cls The class to register against
|
|
134
|
-
* @param name The subtype name
|
|
135
|
-
*/
|
|
136
|
-
registerSubTypes(cls: Class, name?: string): void {
|
|
137
|
-
// Mark as subtype
|
|
138
|
-
const config = (this.get(cls) ?? this.getOrCreatePending(cls));
|
|
139
|
-
let base: Class | undefined = this.getBaseSchema(cls);
|
|
140
|
-
|
|
141
|
-
if (!this.#subTypes.has(base)) {
|
|
142
|
-
this.#subTypes.set(base, new Map());
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (base !== cls || config.baseType) {
|
|
146
|
-
config.subTypeField = (this.get(base) ?? this.getOrCreatePending(base)).subTypeField;
|
|
147
|
-
config.subTypeName = name ?? config.subTypeName ?? classToSubTypeName(cls);
|
|
148
|
-
this.#subTypes.get(base)!.set(config.subTypeName!, cls);
|
|
149
|
-
}
|
|
150
|
-
if (base !== cls) {
|
|
151
|
-
while (base && base.Ⲑid) {
|
|
152
|
-
this.#subTypes.get(base)!.set(config.subTypeName!, cls);
|
|
153
|
-
const parent = this.getParentClass(base);
|
|
154
|
-
base = parent ? this.getBaseSchema(parent) : undefined;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Track changes to schemas, and track the dependent changes
|
|
161
|
-
* @param cls The root class of the hierarchy
|
|
162
|
-
* @param curr The new class
|
|
163
|
-
* @param path The path within the object hierarchy
|
|
164
|
-
*/
|
|
165
|
-
trackSchemaDependencies(cls: Class, curr: Class = cls, path: FieldConfig[] = []): void {
|
|
166
|
-
const config = this.get(curr);
|
|
167
|
-
|
|
168
|
-
SchemaChangeListener.trackSchemaDependency(curr, cls, path, this.get(cls));
|
|
169
|
-
|
|
170
|
-
// Read children
|
|
171
|
-
const view = config.totalView;
|
|
172
|
-
for (const k of view.fields) {
|
|
173
|
-
if (this.has(view.schema[k].type) && view.schema[k].type !== cls) {
|
|
174
|
-
this.trackSchemaDependencies(cls, view.schema[k].type, [...path, view.schema[k]]);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
createPending(cls: Class): ClassConfig {
|
|
180
|
-
return {
|
|
181
|
-
class: cls,
|
|
182
|
-
validators: [],
|
|
183
|
-
subTypeField: 'type',
|
|
184
|
-
baseType: describeFunction(cls)?.abstract,
|
|
185
|
-
metadata: {},
|
|
186
|
-
methods: {},
|
|
187
|
-
totalView: {
|
|
188
|
-
schema: {},
|
|
189
|
-
fields: [],
|
|
190
|
-
},
|
|
191
|
-
views: {}
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Get schema for a given view
|
|
197
|
-
* @param cls The class to retrieve the schema for
|
|
198
|
-
* @param view The view name
|
|
199
|
-
*/
|
|
200
|
-
getViewSchema<T>(cls: Class<T>, view?: string): ViewConfig {
|
|
201
|
-
const schema = this.get(cls)!;
|
|
202
|
-
if (!schema) {
|
|
203
|
-
throw new Error(`Unknown schema class ${cls.name}`);
|
|
204
|
-
}
|
|
205
|
-
let res = schema.totalView;
|
|
206
|
-
if (view) {
|
|
207
|
-
res = schema.views[view];
|
|
208
|
-
if (!res) {
|
|
209
|
-
throw new Error(`Unknown view ${view.toString()} for ${cls.name}`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return res;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Get schema for a method invocation
|
|
217
|
-
* @param cls
|
|
218
|
-
* @param method
|
|
219
|
-
*/
|
|
220
|
-
getMethodSchema<T>(cls: Class<T>, method: string): FieldConfig[] {
|
|
221
|
-
return (this.get(cls)?.methods?.[method] ?? {}).fields?.filter(x => !!x).toSorted((a, b) => a.index! - b.index!) ?? [];
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Get method validators
|
|
226
|
-
* @param cls
|
|
227
|
-
* @param method
|
|
228
|
-
*/
|
|
229
|
-
getMethodValidators<T>(cls: Class<T>, method: string): MethodValidatorFn<unknown[]>[] {
|
|
230
|
-
return (this.get(cls)?.methods?.[method] ?? {}).validators ?? [];
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Register a view
|
|
235
|
-
* @param target The target class
|
|
236
|
-
* @param view View name
|
|
237
|
-
* @param fields Fields to register
|
|
238
|
-
*/
|
|
239
|
-
registerPendingView<T>(target: Class<T>, view: string, fields: ViewFieldsConfig<T>): void {
|
|
240
|
-
if (!this.#pendingViews.has(target)) {
|
|
241
|
-
this.#pendingViews.set(target, new Map());
|
|
242
|
-
}
|
|
243
|
-
const generalConfig: ViewFieldsConfig<unknown> = castTo(fields);
|
|
244
|
-
this.#pendingViews.get(target)!.set(view, generalConfig);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Register pending method, and establish a method config
|
|
249
|
-
* @param target
|
|
250
|
-
* @param method
|
|
251
|
-
*/
|
|
252
|
-
registerPendingMethod(target: Class, method: string): SchemaMethodConfig {
|
|
253
|
-
const methods = this.getOrCreatePending(target)!.methods!;
|
|
254
|
-
return (methods[method] ??= { fields: [], validators: [] });
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Register a partial config for a pending method param
|
|
259
|
-
* @param target The class to target
|
|
260
|
-
* @param prop The method name
|
|
261
|
-
* @param idx The param index
|
|
262
|
-
* @param config The config to register
|
|
263
|
-
*/
|
|
264
|
-
registerPendingParamFacet(target: Class, method: string, idx: number, config: Partial<FieldConfig>): Class {
|
|
265
|
-
const params = this.registerPendingMethod(target, method).fields;
|
|
266
|
-
if (config.name === '') {
|
|
267
|
-
delete config.name;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (config.aliases) {
|
|
271
|
-
config.aliases = [...params[idx]?.aliases ?? [], ...config.aliases];
|
|
272
|
-
}
|
|
273
|
-
if (config.specifiers) {
|
|
274
|
-
config.specifiers = [...params[idx]?.specifiers ?? [], ...config.specifiers];
|
|
275
|
-
}
|
|
276
|
-
if (config.enum?.values) {
|
|
277
|
-
config.enum.values = config.enum.values.toSorted();
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
params[idx] = {
|
|
281
|
-
// @ts-expect-error
|
|
282
|
-
name: `${method}.${idx}`,
|
|
283
|
-
...params[idx] ?? {},
|
|
284
|
-
owner: target,
|
|
285
|
-
index: idx,
|
|
286
|
-
...config,
|
|
287
|
-
};
|
|
288
|
-
return target;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Register a partial config for a pending field
|
|
293
|
-
* @param target The class to target
|
|
294
|
-
* @param prop The property name
|
|
295
|
-
* @param config The config to register
|
|
296
|
-
*/
|
|
297
|
-
registerPendingFieldFacet(target: Class, prop: string, config: Partial<FieldConfig>): Class {
|
|
298
|
-
if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') {
|
|
299
|
-
throw new AppError('Invalid property name');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const totalViewConf = this.getOrCreatePending(target).totalView!;
|
|
303
|
-
|
|
304
|
-
if (!totalViewConf.schema[prop]) {
|
|
305
|
-
totalViewConf.fields.push(prop);
|
|
306
|
-
// Partial config while building
|
|
307
|
-
totalViewConf.schema[prop] = asFull<FieldConfig>({});
|
|
308
|
-
}
|
|
309
|
-
if (config.aliases) {
|
|
310
|
-
config.aliases = [...totalViewConf.schema[prop].aliases ?? [], ...config.aliases];
|
|
311
|
-
}
|
|
312
|
-
if (config.specifiers) {
|
|
313
|
-
config.specifiers = [...totalViewConf.schema[prop].specifiers ?? [], ...config.specifiers];
|
|
314
|
-
}
|
|
315
|
-
if (config.enum?.values) {
|
|
316
|
-
config.enum.values = config.enum.values.toSorted();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
Object.assign(totalViewConf.schema[prop], config);
|
|
320
|
-
|
|
321
|
-
return target;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Register pending field configuration
|
|
326
|
-
* @param target Target class
|
|
327
|
-
* @param method Method name
|
|
328
|
-
* @param idx Param index
|
|
329
|
-
* @param type List of types
|
|
330
|
-
* @param conf Extra config
|
|
331
|
-
*/
|
|
332
|
-
registerPendingParamConfig(target: Class, method: string, idx: number, type: ClassList, conf?: Partial<FieldConfig>): Class {
|
|
333
|
-
return this.registerPendingParamFacet(target, method, idx, {
|
|
334
|
-
...conf,
|
|
335
|
-
array: Array.isArray(type),
|
|
336
|
-
type: Array.isArray(type) ? type[0] : type,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Register pending field configuration
|
|
342
|
-
* @param target Target class
|
|
343
|
-
* @param prop Property name
|
|
344
|
-
* @param type List of types
|
|
345
|
-
* @param conf Extra config
|
|
346
|
-
*/
|
|
347
|
-
registerPendingFieldConfig(target: Class, prop: string, type: ClassList, conf?: Partial<FieldConfig>): Class {
|
|
348
|
-
const fieldConf: FieldConfig = {
|
|
349
|
-
owner: target,
|
|
350
|
-
name: prop,
|
|
351
|
-
array: Array.isArray(type),
|
|
352
|
-
type: Array.isArray(type) ? type[0] : type,
|
|
353
|
-
...(conf ?? {})
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
return this.registerPendingFieldFacet(target, prop, fieldConf);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Merge two class configs
|
|
361
|
-
* @param dest Target config
|
|
362
|
-
* @param src Source config
|
|
363
|
-
*/
|
|
364
|
-
mergeConfigs(dest: ClassConfig, src: Partial<ClassConfig>, inherited = false): ClassConfig {
|
|
365
|
-
dest.totalView = {
|
|
366
|
-
schema: { ...dest.totalView.schema, ...src.totalView?.schema },
|
|
367
|
-
fields: [...dest.totalView.fields, ...src.totalView?.fields ?? []]
|
|
368
|
-
};
|
|
369
|
-
if (!inherited) {
|
|
370
|
-
dest.baseType = src.baseType ?? dest.baseType;
|
|
371
|
-
dest.subTypeName = src.subTypeName ?? dest.subTypeName;
|
|
372
|
-
}
|
|
373
|
-
dest.methods = { ...src.methods ?? {}, ...dest.methods ?? {} };
|
|
374
|
-
dest.metadata = { ...src.metadata ?? {}, ...dest.metadata ?? {} };
|
|
375
|
-
dest.subTypeField = src.subTypeField ?? dest.subTypeField;
|
|
376
|
-
dest.title = src.title || dest.title;
|
|
377
|
-
dest.validators = [...src.validators ?? [], ...dest.validators];
|
|
378
|
-
return dest;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Project all pending views into a final state
|
|
383
|
-
* @param target The target class
|
|
384
|
-
* @param conf The class config
|
|
385
|
-
*/
|
|
386
|
-
finalizeViews<T>(target: Class<T>, conf: ClassConfig): ClassConfig {
|
|
387
|
-
const totalViewConf = conf.totalView;
|
|
388
|
-
const pending = this.#pendingViews.get(target) ?? new Map<string, ViewFieldsConfig<string>>();
|
|
389
|
-
this.#pendingViews.delete(target);
|
|
390
|
-
|
|
391
|
-
for (const [view, fields] of pending.entries()) {
|
|
392
|
-
const withoutSet = 'without' in fields ? new Set<string>(fields.without) : undefined;
|
|
393
|
-
const fieldList = withoutSet ?
|
|
394
|
-
totalViewConf.fields.filter(x => !withoutSet.has(x)) :
|
|
395
|
-
('with' in fields ? fields.with : []);
|
|
396
|
-
|
|
397
|
-
conf.views![view] = {
|
|
398
|
-
fields: fieldList,
|
|
399
|
-
schema: fieldList.reduce<SchemaConfig>((acc, v) => {
|
|
400
|
-
acc[v] = totalViewConf.schema[v];
|
|
401
|
-
return acc;
|
|
402
|
-
}, {})
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return conf;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
onInstallFinalize(cls: Class): ClassConfig {
|
|
410
|
-
|
|
411
|
-
let config: ClassConfig = this.createPending(cls);
|
|
412
|
-
|
|
413
|
-
// Merge parent
|
|
414
|
-
const parent = this.getParentClass(cls);
|
|
415
|
-
if (parent) {
|
|
416
|
-
const parentConfig = this.get(parent);
|
|
417
|
-
if (parentConfig) {
|
|
418
|
-
config = this.mergeConfigs(config, parentConfig, true);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
this.registerSubTypes(cls);
|
|
423
|
-
|
|
424
|
-
// Merge pending, back on top, to allow child to have higher precedence
|
|
425
|
-
const pending = this.getOrCreatePending(cls);
|
|
426
|
-
if (pending) {
|
|
427
|
-
config = this.mergeConfigs(config, pending);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Write views out
|
|
431
|
-
config = this.finalizeViews(cls, config);
|
|
432
|
-
|
|
433
|
-
if (config.subTypeName && config.subTypeField in config.totalView.schema) {
|
|
434
|
-
const field = config.totalView.schema[config.subTypeField];
|
|
435
|
-
config.totalView.schema[config.subTypeField] = {
|
|
436
|
-
...field,
|
|
437
|
-
enum: {
|
|
438
|
-
values: [config.subTypeName],
|
|
439
|
-
message: `${config.subTypeField} can only be '${config.subTypeName}'`,
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return config;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
override onInstall(cls: Class, e: ChangeEvent<Class>): void {
|
|
448
|
-
super.onInstall(cls, e);
|
|
449
|
-
|
|
450
|
-
if (this.has(cls)) { // Track dependencies of schemas
|
|
451
|
-
this.trackSchemaDependencies(cls);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
override onUninstall<T>(cls: Class<T>, e: ChangeEvent<Class>): void {
|
|
456
|
-
super.onUninstall(cls, e);
|
|
457
|
-
if (e.type === 'removing' && this.hasExpired(cls)) {
|
|
458
|
-
// Recompute subtypes
|
|
459
|
-
this.#subTypes.clear();
|
|
460
|
-
this.#baseSchema.delete(cls);
|
|
461
|
-
this.#accessorDescriptors.delete(cls);
|
|
462
|
-
|
|
463
|
-
// Recompute subtype mappings
|
|
464
|
-
for (const el of this.entries.keys()) {
|
|
465
|
-
const clz = this.entries.get(el)!.class;
|
|
466
|
-
this.registerSubTypes(clz);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
SchemaChangeListener.clearSchemaDependency(cls);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
override emit(ev: ChangeEvent<Class>): void {
|
|
474
|
-
super.emit(ev);
|
|
475
|
-
if (ev.type === 'changed') {
|
|
476
|
-
SchemaChangeListener.emitFieldChanges({
|
|
477
|
-
type: 'changed',
|
|
478
|
-
curr: this.get(ev.curr!),
|
|
479
|
-
prev: this.getExpired(ev.curr!)
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Visit fields recursively
|
|
486
|
-
*/
|
|
487
|
-
visitFields<T>(cls: Class<T>, onField: (field: FieldConfig, path: FieldConfig[]) => void, _path: FieldConfig[] = [], root = cls): void {
|
|
488
|
-
const fields = this.has(cls) ?
|
|
489
|
-
Object.values(this.getViewSchema(cls).schema) :
|
|
490
|
-
[];
|
|
491
|
-
for (const field of fields) {
|
|
492
|
-
if (this.has(field.type)) {
|
|
493
|
-
this.visitFields(field.type, onField, [..._path, field], root);
|
|
494
|
-
} else {
|
|
495
|
-
onField(field, _path);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
export const SchemaRegistry = new $SchemaRegistry();
|