@travetto/schema 7.0.0-rc.1 → 7.0.0-rc.2
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 +31 -31
- 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 +11 -9
- package/src/decorator/method.ts +3 -1
- package/src/decorator/schema.ts +2 -2
- package/src/internal/types.ts +3 -3
- package/src/name.ts +3 -3
- package/src/service/changes.ts +33 -33
- package/src/service/registry-adapter.ts +58 -58
- package/src/service/registry-index.ts +19 -19
- package/src/service/types.ts +5 -5
- package/src/types.ts +1 -1
- package/src/validate/error.ts +2 -2
- package/src/validate/regexp.ts +4 -4
- package/src/validate/validator.ts +75 -75
- package/support/transformer/util.ts +26 -23
- package/support/transformer.schema.ts +7 -7
package/src/decorator/common.ts
CHANGED
|
@@ -10,14 +10,14 @@ import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
|
10
10
|
* @kind decorator
|
|
11
11
|
*/
|
|
12
12
|
export function Describe(config: Partial<Omit<SchemaCoreConfig, 'metadata'>>) {
|
|
13
|
-
return (instanceOrCls: Class | ClassInstance, property?: string
|
|
13
|
+
return (instanceOrCls: Class | ClassInstance, property?: string, descriptorOrIdx?: PropertyDescriptor | number): void => {
|
|
14
14
|
const adapter = SchemaRegistryIndex.getForRegister(getClass(instanceOrCls));
|
|
15
15
|
if (!property) {
|
|
16
16
|
adapter.register(config);
|
|
17
17
|
} else {
|
|
18
|
-
if (
|
|
19
|
-
adapter.registerParameter(property,
|
|
20
|
-
} else if (typeof
|
|
18
|
+
if (descriptorOrIdx !== undefined && typeof descriptorOrIdx === 'number') {
|
|
19
|
+
adapter.registerParameter(property, descriptorOrIdx, config);
|
|
20
|
+
} else if (typeof descriptorOrIdx === 'object' && typeof descriptorOrIdx.value === 'function') {
|
|
21
21
|
adapter.registerMethod(property, config);
|
|
22
22
|
} else {
|
|
23
23
|
adapter.registerField(property, config);
|
|
@@ -31,7 +31,7 @@ export function Describe(config: Partial<Omit<SchemaCoreConfig, 'metadata'>>) {
|
|
|
31
31
|
* @augments `@travetto/schema:Input`
|
|
32
32
|
* @kind decorator
|
|
33
33
|
*/
|
|
34
|
-
export const IsPrivate = (): (instanceOrCls: Class | ClassInstance, property?: string
|
|
34
|
+
export const IsPrivate = (): (instanceOrCls: Class | ClassInstance, property?: string) => void => Describe({ private: true });
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Mark a field/method as ignored
|
package/src/decorator/field.ts
CHANGED
|
@@ -3,23 +3,25 @@ import { Any, ClassInstance, getClass } from '@travetto/runtime';
|
|
|
3
3
|
import { SchemaFieldConfig } from '../service/types.ts';
|
|
4
4
|
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
5
5
|
|
|
6
|
-
type PropType<V> = (<T extends Partial<Record<K, V | Function>>, K extends string>(
|
|
6
|
+
type PropType<V> = (<T extends Partial<Record<K, V | Function>>, K extends string>(
|
|
7
|
+
instance: T, property: K, idx?: TypedPropertyDescriptor<Any> | number
|
|
8
|
+
) => void);
|
|
7
9
|
|
|
8
|
-
function field<V>(...
|
|
9
|
-
return (instance: ClassInstance, property: string
|
|
10
|
-
SchemaRegistryIndex.getForRegister(getClass(instance)).registerField(property, ...
|
|
10
|
+
function field<V>(...configs: Partial<SchemaFieldConfig>[]): PropType<V> {
|
|
11
|
+
return (instance: ClassInstance, property: string): void => {
|
|
12
|
+
SchemaRegistryIndex.getForRegister(getClass(instance)).registerField(property, ...configs);
|
|
11
13
|
};
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Registering a field
|
|
16
18
|
* @param type The type for the field
|
|
17
|
-
* @param
|
|
19
|
+
* @param configs The field configuration
|
|
18
20
|
* @augments `@travetto/schema:Input`
|
|
19
21
|
* @kind decorator
|
|
20
22
|
*/
|
|
21
|
-
export function Field(type: Pick<SchemaFieldConfig, 'type' | 'array'>, ...
|
|
22
|
-
return field(type, ...
|
|
23
|
+
export function Field(type: Pick<SchemaFieldConfig, 'type' | 'array'>, ...configs: Partial<SchemaFieldConfig>[]): PropType<unknown> {
|
|
24
|
+
return field(type, ...configs);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
package/src/decorator/input.ts
CHANGED
|
@@ -4,16 +4,18 @@ import { CommonRegExp } from '../validate/regexp.ts';
|
|
|
4
4
|
import { CONSTRUCTOR_PROPERTY, SchemaInputConfig } from '../service/types.ts';
|
|
5
5
|
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
6
6
|
|
|
7
|
-
type PropType<V> = (<T extends Partial<Record<K, V | Function>>, K extends string>(
|
|
7
|
+
type PropType<V> = (<T extends Partial<Record<K, V | Function>>, K extends string>(
|
|
8
|
+
instance: T, property: K, idx?: TypedPropertyDescriptor<Any> | number
|
|
9
|
+
) => void);
|
|
8
10
|
|
|
9
|
-
function input<V>(...
|
|
10
|
-
return (instanceOrCls: ClassInstance | Class, property: string
|
|
11
|
+
function input<V>(...configs: Partial<SchemaInputConfig>[]): PropType<V> {
|
|
12
|
+
return (instanceOrCls: ClassInstance | Class, property: string, idx?: number | TypedPropertyDescriptor<Any>): void => {
|
|
11
13
|
const adapter = SchemaRegistryIndex.getForRegister(getClass(instanceOrCls));
|
|
12
14
|
const propertyKey = property ?? CONSTRUCTOR_PROPERTY;
|
|
13
15
|
if (typeof idx === 'number') {
|
|
14
|
-
adapter.registerParameter(propertyKey, idx, ...
|
|
16
|
+
adapter.registerParameter(propertyKey, idx, ...configs);
|
|
15
17
|
} else {
|
|
16
|
-
adapter.registerField(propertyKey, ...
|
|
18
|
+
adapter.registerField(propertyKey, ...configs);
|
|
17
19
|
}
|
|
18
20
|
};
|
|
19
21
|
}
|
|
@@ -21,12 +23,12 @@ function input<V>(...obj: Partial<SchemaInputConfig>[]): PropType<V> {
|
|
|
21
23
|
/**
|
|
22
24
|
* Registering an input
|
|
23
25
|
* @param type The type for the input
|
|
24
|
-
* @param
|
|
26
|
+
* @param configs The input configuration
|
|
25
27
|
* @augments `@travetto/schema:Input`
|
|
26
28
|
* @kind decorator
|
|
27
29
|
*/
|
|
28
|
-
export function Input(type: Pick<SchemaInputConfig, 'type' | 'array'>, ...
|
|
29
|
-
return input(type, ...
|
|
30
|
+
export function Input(type: Pick<SchemaInputConfig, 'type' | 'array'>, ...configs: Partial<SchemaInputConfig>[]): PropType<unknown> {
|
|
31
|
+
return input(type, ...configs);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -197,7 +199,7 @@ export function Specifier(...specifiers: string[]): PropType<unknown> { return i
|
|
|
197
199
|
* @augments `@travetto/schema:Input`
|
|
198
200
|
* @kind decorator
|
|
199
201
|
*/
|
|
200
|
-
export function DiscriminatorField(): ((
|
|
202
|
+
export function DiscriminatorField(): ((instance: ClassInstance, property: string) => void) {
|
|
201
203
|
return (instance: ClassInstance, property: string): void => {
|
|
202
204
|
SchemaRegistryIndex.getForRegister(getClass(instance)).register({
|
|
203
205
|
discriminatedBase: true,
|
package/src/decorator/method.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { SchemaMethodConfig } from '../service/types';
|
|
|
4
4
|
import { SchemaRegistryIndex } from '../service/registry-index';
|
|
5
5
|
import { MethodValidatorFn } from '../validate/types';
|
|
6
6
|
|
|
7
|
+
type MethodDecorator = (instance: ClassInstance, property: string, descriptor: PropertyDescriptor) => PropertyDescriptor | void;
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Registering a method
|
|
9
11
|
* @param config The method configuration
|
|
@@ -11,7 +13,7 @@ import { MethodValidatorFn } from '../validate/types';
|
|
|
11
13
|
* @kind decorator
|
|
12
14
|
*/
|
|
13
15
|
export function Method(...config: Partial<SchemaMethodConfig>[]) {
|
|
14
|
-
return (instanceOrCls: ClassInstance, property: string
|
|
16
|
+
return (instanceOrCls: ClassInstance, property: string): void => {
|
|
15
17
|
SchemaRegistryIndex.getForRegister(getClass(instanceOrCls)).registerMethod(property, ...config);
|
|
16
18
|
};
|
|
17
19
|
}
|
package/src/decorator/schema.ts
CHANGED
|
@@ -16,12 +16,12 @@ type ValidStringField<T> = { [K in Extract<keyof T, string>]: T[K] extends strin
|
|
|
16
16
|
* @augments `@travetto/schema:Schema`
|
|
17
17
|
* @kind decorator
|
|
18
18
|
*/
|
|
19
|
-
export function Schema(
|
|
19
|
+
export function Schema(config?: Partial<Pick<SchemaClassConfig, 'validators' | 'methods'>>) {
|
|
20
20
|
return <T, U extends Class<T>>(cls: U): void => {
|
|
21
21
|
cls.from ??= function <V>(this: Class<V>, data: DeepPartial<V>, view?: string): V {
|
|
22
22
|
return BindUtil.bindSchema(this, data, { view });
|
|
23
23
|
};
|
|
24
|
-
SchemaRegistryIndex.getForRegister(cls).registerClass(
|
|
24
|
+
SchemaRegistryIndex.getForRegister(cls).registerClass(config);
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
package/src/internal/types.ts
CHANGED
|
@@ -5,7 +5,7 @@ const InvalidSymbol = Symbol();
|
|
|
5
5
|
/**
|
|
6
6
|
* Point Implementation
|
|
7
7
|
*/
|
|
8
|
-
export class
|
|
8
|
+
export class PointImplementation {
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Validate we have an actual point
|
|
@@ -20,7 +20,7 @@ export class PointImpl {
|
|
|
20
20
|
*/
|
|
21
21
|
static bindSchema(input: unknown): [number, number] | typeof InvalidSymbol | undefined {
|
|
22
22
|
if (Array.isArray(input) && input.length === 2) {
|
|
23
|
-
const [a, b] = input.map(
|
|
23
|
+
const [a, b] = input.map(value => DataUtil.coerceType(value, Number, false));
|
|
24
24
|
return [a, b];
|
|
25
25
|
} else {
|
|
26
26
|
return InvalidSymbol;
|
|
@@ -28,4 +28,4 @@ export class PointImpl {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
Object.defineProperty(
|
|
31
|
+
Object.defineProperty(PointImplementation, 'name', { value: 'Point' });
|
package/src/name.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SchemaClassConfig } from './service/types.ts';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const ID_REGEX = /(\d{1,100})Δ$/;
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Name resolver, specifically for synthetic types
|
|
@@ -17,9 +17,9 @@ export class SchemaNameResolver {
|
|
|
17
17
|
getName(schema: SchemaClassConfig): string {
|
|
18
18
|
const cls = schema.class;
|
|
19
19
|
const id = cls.Ⲑid;
|
|
20
|
-
if (
|
|
20
|
+
if (ID_REGEX.test(cls.name)) {
|
|
21
21
|
if (!this.#schemaIdToName.has(id)) {
|
|
22
|
-
const name = cls.name.replace(
|
|
22
|
+
const name = cls.name.replace(ID_REGEX, (_, uniqueId) => uniqueId.slice(-this.#digits));
|
|
23
23
|
this.#schemaIdToName.set(id, name);
|
|
24
24
|
}
|
|
25
25
|
return this.#schemaIdToName.get(id)!;
|
package/src/service/changes.ts
CHANGED
|
@@ -40,17 +40,17 @@ class $SchemaChangeListener {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* On schema change, emit the change event for the whole schema
|
|
43
|
-
* @param
|
|
43
|
+
* @param handler The function to call on schema change
|
|
44
44
|
*/
|
|
45
|
-
onSchemaChange(handler: (
|
|
45
|
+
onSchemaChange(handler: (event: SchemaChangeEvent) => void): void {
|
|
46
46
|
this.#emitter.on('schema', handler);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* On schema field change, emit the change event for the whole schema
|
|
51
|
-
* @param
|
|
51
|
+
* @param handler The function to call on schema field change
|
|
52
52
|
*/
|
|
53
|
-
onFieldChange(handler: (
|
|
53
|
+
onFieldChange(handler: (event: FieldChangeEvent) => void): void {
|
|
54
54
|
this.#emitter.on('field', handler);
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -63,13 +63,13 @@ class $SchemaChangeListener {
|
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Track a specific class for dependencies
|
|
66
|
-
* @param
|
|
66
|
+
* @param cls The target class
|
|
67
67
|
* @param parent The parent class
|
|
68
68
|
* @param path The path within the object hierarchy to arrive at the class
|
|
69
69
|
* @param config The configuration or the class
|
|
70
70
|
*/
|
|
71
|
-
trackSchemaDependency(
|
|
72
|
-
const idValue =
|
|
71
|
+
trackSchemaDependency(cls: Class, parent: Class, path: SchemaFieldConfig[], config: SchemaClassConfig): void {
|
|
72
|
+
const idValue = cls.Ⲑid;
|
|
73
73
|
if (!this.#mapping.has(idValue)) {
|
|
74
74
|
this.#mapping.set(idValue, new Map());
|
|
75
75
|
}
|
|
@@ -86,13 +86,13 @@ class $SchemaChangeListener {
|
|
|
86
86
|
const clsId = cls.Ⲑid;
|
|
87
87
|
|
|
88
88
|
if (this.#mapping.has(clsId)) {
|
|
89
|
-
const
|
|
90
|
-
for (const
|
|
91
|
-
if (!updates.has(
|
|
92
|
-
updates.set(
|
|
89
|
+
const dependencies = this.#mapping.get(clsId)!;
|
|
90
|
+
for (const dependencyClsId of dependencies.keys()) {
|
|
91
|
+
if (!updates.has(dependencyClsId)) {
|
|
92
|
+
updates.set(dependencyClsId, { config: dependencies.get(dependencyClsId)!.config, subs: [] });
|
|
93
93
|
}
|
|
94
|
-
const
|
|
95
|
-
updates.get(
|
|
94
|
+
const childDependency = dependencies.get(dependencyClsId)!;
|
|
95
|
+
updates.get(dependencyClsId)!.subs.push({ path: [...childDependency.path], fields: changes });
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -103,49 +103,49 @@ class $SchemaChangeListener {
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Emit field level changes in the schema
|
|
106
|
-
* @param
|
|
107
|
-
* @param
|
|
106
|
+
* @param previous The previous class config
|
|
107
|
+
* @param current The current class config
|
|
108
108
|
*/
|
|
109
|
-
emitFieldChanges(
|
|
110
|
-
const
|
|
111
|
-
const
|
|
109
|
+
emitFieldChanges(event: ChangeEvent<SchemaClassConfig>): void {
|
|
110
|
+
const previous = 'previous' in event ? event.previous : undefined;
|
|
111
|
+
const current = 'current' in event ? event.current : undefined;
|
|
112
112
|
|
|
113
|
-
const
|
|
114
|
-
const
|
|
113
|
+
const previousFields = new Set(Object.keys(previous?.fields ?? {}));
|
|
114
|
+
const currentFields = new Set(Object.keys(current?.fields ?? {}));
|
|
115
115
|
|
|
116
116
|
const changes: ChangeEvent<SchemaFieldConfig>[] = [];
|
|
117
117
|
|
|
118
|
-
for (const
|
|
119
|
-
if (!
|
|
120
|
-
changes.push({
|
|
118
|
+
for (const field of currentFields) {
|
|
119
|
+
if (!previousFields.has(field) && current) {
|
|
120
|
+
changes.push({ current: current.fields[field], type: 'added' });
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
for (const
|
|
125
|
-
if (!
|
|
126
|
-
changes.push({
|
|
124
|
+
for (const field of previousFields) {
|
|
125
|
+
if (!currentFields.has(field) && previous) {
|
|
126
|
+
changes.push({ previous: previous.fields[field], type: 'removing' });
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// Handle class references changing, but keeping same id
|
|
131
131
|
const compareTypes = (a: Class, b: Class): boolean => a.Ⲑid ? a.Ⲑid === b.Ⲑid : a === b;
|
|
132
132
|
|
|
133
|
-
for (const
|
|
134
|
-
if (
|
|
135
|
-
const prevSchema =
|
|
136
|
-
const currSchema =
|
|
133
|
+
for (const field of currentFields) {
|
|
134
|
+
if (previousFields.has(field) && previous && current) {
|
|
135
|
+
const prevSchema = previous.fields[field];
|
|
136
|
+
const currSchema = current.fields[field];
|
|
137
137
|
if (
|
|
138
138
|
JSON.stringify(prevSchema) !== JSON.stringify(currSchema) ||
|
|
139
139
|
!compareTypes(prevSchema.type, currSchema.type)
|
|
140
140
|
) {
|
|
141
|
-
changes.push({
|
|
141
|
+
changes.push({ previous: previous.fields[field], current: current.fields[field], type: 'changed' });
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// Send field changes
|
|
147
|
-
this.#emitter.emit('field', { cls:
|
|
148
|
-
this.emitSchemaChanges({ cls:
|
|
147
|
+
this.#emitter.emit('field', { cls: current!.class, changes });
|
|
148
|
+
this.emitSchemaChanges({ cls: current!.class, changes });
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
} from './types';
|
|
9
9
|
|
|
10
10
|
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,
|
|
11
|
+
.replace(/([A-Z])([A-Z][a-z])/g, (all, left, right) => `${left}_${right.toLowerCase()}`)
|
|
12
|
+
.replace(/([a-z]|\b)([A-Z])/g, (all, left, right) => left ? `${left}_${right.toLowerCase()}` : right.toLowerCase())
|
|
13
13
|
.toLowerCase();
|
|
14
14
|
|
|
15
15
|
function assignMetadata<T>(key: symbol, base: SchemaCoreConfig, data: Partial<T>[]): T {
|
|
16
|
-
const
|
|
17
|
-
const out =
|
|
16
|
+
const metadata = base.metadata ??= {};
|
|
17
|
+
const out = metadata[key] ??= {};
|
|
18
18
|
for (const d of data) {
|
|
19
19
|
safeAssign(out, d);
|
|
20
20
|
}
|
|
@@ -92,8 +92,8 @@ function combineClassWithParent<T extends SchemaClassConfig>(base: T, parent: T)
|
|
|
92
92
|
case 'Required':
|
|
93
93
|
case 'Partial': {
|
|
94
94
|
base.fields = Object.fromEntries(
|
|
95
|
-
Object.entries(parent.fields).map(([
|
|
96
|
-
...
|
|
95
|
+
Object.entries(parent.fields).map(([key, value]) => [key, {
|
|
96
|
+
...value,
|
|
97
97
|
required: {
|
|
98
98
|
active: base.mappedOperation === 'Required'
|
|
99
99
|
}
|
|
@@ -105,8 +105,8 @@ function combineClassWithParent<T extends SchemaClassConfig>(base: T, parent: T)
|
|
|
105
105
|
case 'Omit': {
|
|
106
106
|
const keys = new Set<string>(base.mappedFields ?? []);
|
|
107
107
|
base.fields = Object.fromEntries(
|
|
108
|
-
Object.entries(parent.fields).filter(([
|
|
109
|
-
base.mappedOperation === 'Pick' ? keys.has(
|
|
108
|
+
Object.entries(parent.fields).filter(([key]) =>
|
|
109
|
+
base.mappedOperation === 'Pick' ? keys.has(key) : !keys.has(key)
|
|
110
110
|
)
|
|
111
111
|
);
|
|
112
112
|
break;
|
|
@@ -145,7 +145,7 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
register(...data: Partial<SchemaClassConfig>[]): SchemaClassConfig {
|
|
148
|
-
const
|
|
148
|
+
const config = this.#config ??= {
|
|
149
149
|
methods: {},
|
|
150
150
|
class: this.#cls,
|
|
151
151
|
views: {},
|
|
@@ -153,38 +153,38 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
153
153
|
interfaces: [],
|
|
154
154
|
fields: {},
|
|
155
155
|
};
|
|
156
|
-
return combineClasses(
|
|
156
|
+
return combineClasses(config, data);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
registerMetadata<T>(key: symbol, ...data: Partial<T>[]): T {
|
|
160
|
-
const
|
|
161
|
-
return assignMetadata(key,
|
|
160
|
+
const config = this.register({});
|
|
161
|
+
return assignMetadata(key, config, data);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
getMetadata<T>(key: symbol): T | undefined {
|
|
165
|
-
const
|
|
166
|
-
return castTo<T>(
|
|
165
|
+
const metadata = this.#config?.metadata;
|
|
166
|
+
return castTo<T>(metadata?.[key]);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
registerField(field: string
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const combined = combineInputs(
|
|
169
|
+
registerField(field: string, ...data: Partial<SchemaFieldConfig>[]): SchemaFieldConfig {
|
|
170
|
+
const classConfig = this.register({});
|
|
171
|
+
const config = classConfig.fields[field] ??= { name: field, owner: this.#cls, type: null! };
|
|
172
|
+
const combined = combineInputs(config, data);
|
|
173
173
|
return combined;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
registerFieldMetadata<T>(field: string
|
|
177
|
-
const
|
|
178
|
-
return assignMetadata(key,
|
|
176
|
+
registerFieldMetadata<T>(field: string, key: symbol, ...data: Partial<T>[]): T {
|
|
177
|
+
const config = this.registerField(field);
|
|
178
|
+
return assignMetadata(key, config, data);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
getFieldMetadata<T>(field: string
|
|
182
|
-
const
|
|
183
|
-
return castTo<T>(
|
|
181
|
+
getFieldMetadata<T>(field: string, key: symbol): T | undefined {
|
|
182
|
+
const metadata = this.#config?.fields[field]?.metadata;
|
|
183
|
+
return castTo<T>(metadata?.[key]);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
registerClass({ methods, ...
|
|
187
|
-
this.register({ ...
|
|
186
|
+
registerClass({ methods, ...config }: Partial<SchemaClassConfig> = {}): SchemaClassConfig {
|
|
187
|
+
this.register({ ...config });
|
|
188
188
|
if (methods?.[CONSTRUCTOR_PROPERTY]) {
|
|
189
189
|
const { parameters, ...rest } = methods[CONSTRUCTOR_PROPERTY];
|
|
190
190
|
this.registerMethod(CONSTRUCTOR_PROPERTY, rest);
|
|
@@ -195,36 +195,36 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
195
195
|
return this.#config;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
registerMethod(method: string
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
return combineMethods(
|
|
198
|
+
registerMethod(method: string, ...data: Partial<SchemaMethodConfig>[]): SchemaMethodConfig {
|
|
199
|
+
const classConfig = this.register();
|
|
200
|
+
const config = classConfig.methods[method] ??= { parameters: [], validators: [] };
|
|
201
|
+
return combineMethods(config, data);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
registerMethodMetadata<T>(method: string
|
|
205
|
-
const
|
|
206
|
-
return assignMetadata(key,
|
|
204
|
+
registerMethodMetadata<T>(method: string, key: symbol, ...data: Partial<T>[]): T {
|
|
205
|
+
const config = this.registerMethod(method);
|
|
206
|
+
return assignMetadata(key, config, data);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
getMethodMetadata<T>(method: string
|
|
210
|
-
const
|
|
211
|
-
return castTo<T>(
|
|
209
|
+
getMethodMetadata<T>(method: string, key: symbol): T | undefined {
|
|
210
|
+
const metadata = this.#config?.methods[method]?.metadata;
|
|
211
|
+
return castTo<T>(metadata?.[key]);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
registerParameter(method: string
|
|
214
|
+
registerParameter(method: string, idx: number, ...data: Partial<SchemaParameterConfig>[]): SchemaParameterConfig {
|
|
215
215
|
const params = this.registerMethod(method, {}).parameters;
|
|
216
|
-
const
|
|
217
|
-
return combineInputs(
|
|
216
|
+
const config = params[idx] ??= { method, index: idx, owner: this.#cls, array: false, type: null! };
|
|
217
|
+
return combineInputs(config, data);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
registerParameterMetadata<T>(method: string
|
|
221
|
-
const
|
|
222
|
-
return assignMetadata(key,
|
|
220
|
+
registerParameterMetadata<T>(method: string, idx: number, key: symbol, ...data: Partial<T>[]): T {
|
|
221
|
+
const config = this.registerParameter(method, idx);
|
|
222
|
+
return assignMetadata(key, config, data);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
getParameterMetadata<T>(method: string
|
|
226
|
-
const
|
|
227
|
-
return castTo<T>(
|
|
225
|
+
getParameterMetadata<T>(method: string, idx: number, key: symbol): T | undefined {
|
|
226
|
+
const metadata = this.#config?.methods[method]?.parameters[idx]?.metadata;
|
|
227
|
+
return castTo<T>(metadata?.[key]);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
finalize(parent?: SchemaClassConfig): void {
|
|
@@ -256,13 +256,13 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
256
256
|
const fields = config.views[view];
|
|
257
257
|
const withoutSet = 'without' in fields ? new Set<string>(fields.without) : undefined;
|
|
258
258
|
const fieldList = withoutSet ?
|
|
259
|
-
Object.keys(config.fields).filter(
|
|
259
|
+
Object.keys(config.fields).filter(field => !withoutSet.has(field)) :
|
|
260
260
|
('with' in fields ? fields.with : []);
|
|
261
261
|
|
|
262
262
|
this.#views.set(view,
|
|
263
|
-
fieldList.reduce<SchemaFieldMap>((
|
|
264
|
-
|
|
265
|
-
return
|
|
263
|
+
fieldList.reduce<SchemaFieldMap>((map, value) => {
|
|
264
|
+
map[value] = config.fields[value];
|
|
265
|
+
return map;
|
|
266
266
|
}, {})
|
|
267
267
|
);
|
|
268
268
|
}
|
|
@@ -278,19 +278,19 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
278
278
|
return this.#config;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
getField(field: string
|
|
281
|
+
getField(field: string): SchemaFieldConfig {
|
|
282
282
|
return this.#config.fields[field];
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
getMethod(method: string
|
|
286
|
-
const
|
|
287
|
-
if (!
|
|
285
|
+
getMethod(method: string): SchemaMethodConfig {
|
|
286
|
+
const methodConfig = this.#config.methods[method];
|
|
287
|
+
if (!methodConfig) {
|
|
288
288
|
throw new AppError(`Unknown method ${String(method)} on class ${this.#cls.Ⲑid}`);
|
|
289
289
|
}
|
|
290
|
-
return
|
|
290
|
+
return methodConfig;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
getMethodReturnType(method: string
|
|
293
|
+
getMethodReturnType(method: string): Class {
|
|
294
294
|
return this.getMethod(method).returnType!.type;
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -321,13 +321,13 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
321
321
|
/**
|
|
322
322
|
* Ensure type is set properly
|
|
323
323
|
*/
|
|
324
|
-
ensureInstanceTypeField<T>(
|
|
324
|
+
ensureInstanceTypeField<T>(value: T): T {
|
|
325
325
|
const config = this.getDiscriminatedConfig();
|
|
326
326
|
if (config) {
|
|
327
327
|
const typeField = castKey<T>(config.discriminatedField);
|
|
328
|
-
|
|
328
|
+
value[typeField] ??= castTo(config.discriminatedType); // Assign if missing
|
|
329
329
|
}
|
|
330
|
-
return
|
|
330
|
+
return value;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
getDiscriminatedConfig(): Required<Pick<SchemaClassConfig, 'discriminatedType' | 'discriminatedField' | 'discriminatedBase'>> | undefined {
|
|
@@ -36,8 +36,8 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
36
36
|
return this.#instance.getDiscriminatedTypes(cls);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
static resolveInstanceType<T>(cls: Class<T>,
|
|
40
|
-
return this.#instance.resolveInstanceType(cls,
|
|
39
|
+
static resolveInstanceType<T>(cls: Class<T>, item: T): Class {
|
|
40
|
+
return this.#instance.resolveInstanceType(cls, item);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
static visitFields<T>(cls: Class<T>, onField: (field: SchemaFieldConfig, path: SchemaFieldConfig[]) => void): void {
|
|
@@ -88,21 +88,21 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
88
88
|
Util.queueMacroTask().then(() => {
|
|
89
89
|
SchemaChangeListener.emitFieldChanges({
|
|
90
90
|
type: 'changed',
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
current: this.getClassConfig(event.current),
|
|
92
|
+
previous: this.getClassConfig(event.previous)
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
#onRemoving(event: ChangeEvent<Class> & { type: 'removing' }): void {
|
|
98
|
-
SchemaChangeListener.clearSchemaDependency(event.
|
|
98
|
+
SchemaChangeListener.clearSchemaDependency(event.previous);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
#onAdded(event: ChangeEvent<Class> & { type: 'added' }): void {
|
|
102
102
|
Util.queueMacroTask().then(() => {
|
|
103
103
|
SchemaChangeListener.emitFieldChanges({
|
|
104
104
|
type: 'added',
|
|
105
|
-
|
|
105
|
+
current: this.getClassConfig(event.current)
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
}
|
|
@@ -120,8 +120,8 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
120
120
|
|
|
121
121
|
// Rebuild indices after every "process" batch
|
|
122
122
|
this.#byDiscriminatedTypes.clear();
|
|
123
|
-
for (const
|
|
124
|
-
this.#registerDiscriminatedTypes(
|
|
123
|
+
for (const cls of this.store.getClasses()) {
|
|
124
|
+
this.#registerDiscriminatedTypes(cls);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -134,15 +134,15 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
134
134
|
*/
|
|
135
135
|
getBaseClass(cls: Class): Class {
|
|
136
136
|
if (!this.#baseSchema.has(cls)) {
|
|
137
|
-
let
|
|
137
|
+
let config = this.getClassConfig(cls);
|
|
138
138
|
let parent: Class | undefined = cls;
|
|
139
|
-
while (parent &&
|
|
139
|
+
while (parent && config.discriminatedType && !config.discriminatedBase) {
|
|
140
140
|
parent = getParentClass(parent);
|
|
141
141
|
if (parent) {
|
|
142
|
-
|
|
142
|
+
config = this.store.getOptional(parent)?.get() ?? config;
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
-
this.#baseSchema.set(cls,
|
|
145
|
+
this.#baseSchema.set(cls, config.class);
|
|
146
146
|
}
|
|
147
147
|
return this.#baseSchema.get(cls)!;
|
|
148
148
|
}
|
|
@@ -150,16 +150,16 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
150
150
|
/**
|
|
151
151
|
* Find the resolved type for a given instance
|
|
152
152
|
* @param cls Class for instance
|
|
153
|
-
* @param
|
|
153
|
+
* @param item Actual instance
|
|
154
154
|
*/
|
|
155
|
-
resolveInstanceType<T>(cls: Class<T>,
|
|
155
|
+
resolveInstanceType<T>(cls: Class<T>, item: T): Class {
|
|
156
156
|
const { discriminatedField, discriminatedType, class: targetClass } = this.store.get(cls).get();
|
|
157
157
|
if (!discriminatedField) {
|
|
158
158
|
return targetClass;
|
|
159
159
|
} else {
|
|
160
160
|
const base = this.getBaseClass(targetClass);
|
|
161
161
|
const map = this.#byDiscriminatedTypes.get(base);
|
|
162
|
-
const type = castTo<string>(
|
|
162
|
+
const type = castTo<string>(item[castKey<T>(discriminatedField)]) ?? discriminatedType;
|
|
163
163
|
if (!type) {
|
|
164
164
|
throw new AppError(`Unable to resolve discriminated type for class ${base.name} without a type`);
|
|
165
165
|
}
|
|
@@ -177,13 +177,13 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
177
177
|
/**
|
|
178
178
|
* Track changes to schemas, and track the dependent changes
|
|
179
179
|
* @param cls The root class of the hierarchy
|
|
180
|
-
* @param
|
|
180
|
+
* @param current The new class
|
|
181
181
|
* @param path The path within the object hierarchy
|
|
182
182
|
*/
|
|
183
|
-
trackSchemaDependencies(cls: Class,
|
|
184
|
-
const config = this.getClassConfig(
|
|
183
|
+
trackSchemaDependencies(cls: Class, current: Class = cls, path: SchemaFieldConfig[] = []): void {
|
|
184
|
+
const config = this.getClassConfig(current);
|
|
185
185
|
|
|
186
|
-
SchemaChangeListener.trackSchemaDependency(
|
|
186
|
+
SchemaChangeListener.trackSchemaDependency(current, cls, path, this.getClassConfig(cls));
|
|
187
187
|
|
|
188
188
|
// Read children
|
|
189
189
|
for (const field of Object.values(config.fields)) {
|