@travetto/schema 2.1.5 → 2.2.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 +58 -56
- package/package.json +3 -3
- package/src/bind-util.ts +27 -15
- package/src/decorator/common.ts +8 -4
- package/src/decorator/field.ts +39 -33
- package/src/decorator/schema.ts +4 -3
- package/src/extension/faker.ts +56 -50
- package/src/service/changes.ts +9 -9
- package/src/service/registry.ts +47 -41
- package/src/service/types.ts +3 -3
- package/src/typings.d.ts +8 -1
- package/src/validate/error.ts +4 -0
- package/src/validate/regexp.ts +1 -1
- package/src/validate/types.ts +2 -1
- package/src/validate/validator.ts +62 -45
- package/support/phase.init.ts +1 -1
- package/support/transform-util.ts +9 -4
- package/support/transformer.schema.ts +5 -5
package/src/extension/faker.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @file-if faker
|
|
2
|
-
import
|
|
2
|
+
import * as fakerType from 'faker';
|
|
3
3
|
|
|
4
4
|
import { Class } from '@travetto/base';
|
|
5
5
|
|
|
@@ -9,13 +9,16 @@ import { SchemaRegistry } from '../service/registry';
|
|
|
9
9
|
import { BindUtil } from '../bind-util';
|
|
10
10
|
|
|
11
11
|
// Load faker on demand, as its very heavy in loading
|
|
12
|
-
let faker = new Proxy(
|
|
13
|
-
|
|
14
|
-
}
|
|
12
|
+
let faker: typeof fakerType = new Proxy(
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
14
|
+
{} as typeof fakerType,
|
|
15
|
+
{
|
|
16
|
+
get: (t, prop, r) => (faker = require('faker'))[prop]
|
|
17
|
+
});
|
|
15
18
|
|
|
16
19
|
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
|
17
20
|
|
|
18
|
-
const between = (fromDays: number, toDays: number) =>
|
|
21
|
+
const between = (fromDays: number, toDays: number): Date =>
|
|
19
22
|
faker.date.between(
|
|
20
23
|
new Date(Date.now() + fromDays * DAY_IN_MS),
|
|
21
24
|
new Date(Date.now() + toDays * DAY_IN_MS)
|
|
@@ -27,53 +30,56 @@ const between = (fromDays: number, toDays: number) =>
|
|
|
27
30
|
export class SchemaFakerUtil {
|
|
28
31
|
|
|
29
32
|
static #stringToReType: [RegExp, () => string][] = [
|
|
30
|
-
[CommonRegExp.email, () => faker.internet.email()],
|
|
31
|
-
[CommonRegExp.url, () => faker.internet.url()],
|
|
32
|
-
[CommonRegExp.telephone, () => faker.phone.phoneNumber()],
|
|
33
|
-
[CommonRegExp.postalCode, () => faker.address.zipCode()]
|
|
33
|
+
[CommonRegExp.email, (): string => faker.internet.email()],
|
|
34
|
+
[CommonRegExp.url, (): string => faker.internet.url()],
|
|
35
|
+
[CommonRegExp.telephone, (): string => faker.phone.phoneNumber()],
|
|
36
|
+
[CommonRegExp.postalCode, (): string => faker.address.zipCode()]
|
|
34
37
|
];
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* Mapping of field types to faker utils
|
|
38
41
|
*/
|
|
39
|
-
static #namesToType
|
|
40
|
-
string: [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
[
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
[
|
|
69
|
-
|
|
70
|
-
|
|
42
|
+
static #namesToType: {
|
|
43
|
+
string: [RegExp, () => string][];
|
|
44
|
+
date: [RegExp, () => Date][];
|
|
45
|
+
} = {
|
|
46
|
+
string: [
|
|
47
|
+
[/^(image|img).*url$/, (): string => faker.image.imageUrl()],
|
|
48
|
+
[/^url$/, (): string => faker.internet.url()],
|
|
49
|
+
[/^email(addr(ress)?)?$/, (): string => faker.internet.email()],
|
|
50
|
+
[/^(tele)?phone(num|number)?$/, (): string => faker.phone.phoneNumber()],
|
|
51
|
+
[/^((postal|zip)code)|zip$/, (): string => faker.address.zipCode()],
|
|
52
|
+
[/f(irst)?name/, (): string => faker.name.firstName()],
|
|
53
|
+
[/l(ast)?name/, (): string => faker.name.lastName()],
|
|
54
|
+
[/^ip(add(ress)?)?$/, (): string => faker.internet.ip()],
|
|
55
|
+
[/^ip(add(ress)?)?(v?)6$/, (): string => faker.internet.ipv6()],
|
|
56
|
+
[/^username$/, (): string => faker.internet.userName()],
|
|
57
|
+
[/^domain(name)?$/, (): string => faker.internet.domainName()],
|
|
58
|
+
[/^file(path|name)?$/, (): string => faker.system.filePath()],
|
|
59
|
+
[/^street(1)?$/, (): string => faker.address.streetAddress()],
|
|
60
|
+
[/^street2$/, (): string => faker.address.secondaryAddress()],
|
|
61
|
+
[/^county$/, (): string => faker.address.county()],
|
|
62
|
+
[/^country$/, (): string => faker.address.country()],
|
|
63
|
+
[/^state$/, (): string => faker.address.state()],
|
|
64
|
+
[/^lon(gitude)?$/, (): string => faker.address.longitude()],
|
|
65
|
+
[/^lat(itude)?$/, (): string => faker.address.latitude()],
|
|
66
|
+
[/(profile).*(image|img)/, (): string => faker.image.avatar()],
|
|
67
|
+
[/(image|img)/, (): string => faker.image.image()],
|
|
68
|
+
[/^company(name)?$/, (): string => faker.company.companyName()],
|
|
69
|
+
[/(desc|description)$/, (): string => faker.lorem.sentences(10)]
|
|
70
|
+
],
|
|
71
|
+
date: [
|
|
72
|
+
[/dob|birth/, (): Date => faker.date.past(60)],
|
|
73
|
+
[/creat(e|ion)/, (): Date => between(-200, -100)],
|
|
74
|
+
[/(update|modif(y|ied))/, (): Date => between(-100, -50)]
|
|
75
|
+
],
|
|
76
|
+
};
|
|
71
77
|
|
|
72
78
|
/**
|
|
73
79
|
* Get an array of values
|
|
74
80
|
* @param cfg Field configuration
|
|
75
81
|
*/
|
|
76
|
-
static getArrayValue(cfg: FieldConfig) {
|
|
82
|
+
static getArrayValue(cfg: FieldConfig): unknown[] {
|
|
77
83
|
const min = cfg.minlength ? cfg.minlength.n : 0;
|
|
78
84
|
const max = cfg.maxlength ? cfg.maxlength.n : 10;
|
|
79
85
|
const size = faker.datatype.number({ min, max });
|
|
@@ -88,9 +94,9 @@ export class SchemaFakerUtil {
|
|
|
88
94
|
* Get a new number value
|
|
89
95
|
* @param cfg Number config
|
|
90
96
|
*/
|
|
91
|
-
static getNumberValue(cfg: FieldConfig) {
|
|
92
|
-
let min = cfg.min
|
|
93
|
-
let max = cfg.max
|
|
97
|
+
static getNumberValue(cfg: FieldConfig): number {
|
|
98
|
+
let min = cfg.min && typeof cfg.min.n === 'number' ? cfg.min.n : undefined;
|
|
99
|
+
let max = cfg.max && typeof cfg.max.n === 'number' ? cfg.max.n : undefined;
|
|
94
100
|
let precision = cfg.precision;
|
|
95
101
|
|
|
96
102
|
const name = cfg.name.toUpperCase();
|
|
@@ -123,10 +129,10 @@ export class SchemaFakerUtil {
|
|
|
123
129
|
* Get a date value
|
|
124
130
|
* @param cfg Field config
|
|
125
131
|
*/
|
|
126
|
-
static getDateValue(cfg: FieldConfig) {
|
|
132
|
+
static getDateValue(cfg: FieldConfig): Date {
|
|
127
133
|
const name = cfg.name.toUpperCase();
|
|
128
|
-
const min = cfg.min
|
|
129
|
-
const max = cfg.max
|
|
134
|
+
const min = cfg.min && typeof cfg.min.n !== 'number' ? cfg.min.n : undefined;
|
|
135
|
+
const max = cfg.max && typeof cfg.max.n !== 'number' ? cfg.max.n : undefined;
|
|
130
136
|
|
|
131
137
|
if (min !== undefined || max !== undefined) {
|
|
132
138
|
return faker.date.between(min || new Date(Date.now() - (50 * DAY_IN_MS)), max || new Date());
|
|
@@ -144,7 +150,7 @@ export class SchemaFakerUtil {
|
|
|
144
150
|
* Get a string value
|
|
145
151
|
* @param cfg Field config
|
|
146
152
|
*/
|
|
147
|
-
static getStringValue(cfg: FieldConfig) {
|
|
153
|
+
static getStringValue(cfg: FieldConfig): string {
|
|
148
154
|
const name = cfg.name.toLowerCase();
|
|
149
155
|
|
|
150
156
|
if (cfg.match) {
|
|
@@ -197,7 +203,7 @@ export class SchemaFakerUtil {
|
|
|
197
203
|
* @param cls The class to get an instance of
|
|
198
204
|
* @param view The view to define specifically
|
|
199
205
|
*/
|
|
200
|
-
static generate<T>(cls: Class<T>, view?: string) {
|
|
206
|
+
static generate<T>(cls: Class<T>, view?: string): T {
|
|
201
207
|
const cfg = SchemaRegistry.getViewSchema(cls, view);
|
|
202
208
|
const out: Record<string, unknown> = {};
|
|
203
209
|
|
package/src/service/changes.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { ChangeEvent } from '@travetto/registry';
|
|
|
6
6
|
import { FieldConfig, ClassConfig } from './types';
|
|
7
7
|
import { AllViewⲐ } from '../internal/types';
|
|
8
8
|
|
|
9
|
-
const id = (c: Class | string) => typeof c === 'string' ? c : c.ᚕid;
|
|
9
|
+
const id = (c: Class | string): string => typeof c === 'string' ? c : c.ᚕid;
|
|
10
10
|
|
|
11
11
|
interface FieldMapping {
|
|
12
12
|
path: FieldConfig[];
|
|
@@ -45,7 +45,7 @@ class $SchemaChangeListener {
|
|
|
45
45
|
* On schema change, emit the change event for the whole schema
|
|
46
46
|
* @param cb The function to call on schema change
|
|
47
47
|
*/
|
|
48
|
-
onSchemaChange(handler: (e: SchemaChangeEvent) => void) {
|
|
48
|
+
onSchemaChange(handler: (e: SchemaChangeEvent) => void): void {
|
|
49
49
|
this.#emitter.on('schema', handler);
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -53,21 +53,21 @@ class $SchemaChangeListener {
|
|
|
53
53
|
* On schema field change, emit the change event for the whole schema
|
|
54
54
|
* @param cb The function to call on schema field change
|
|
55
55
|
*/
|
|
56
|
-
onFieldChange(handler: (e: FieldChangeEvent) => void) {
|
|
56
|
+
onFieldChange(handler: (e: FieldChangeEvent) => void): void {
|
|
57
57
|
this.#emitter.on('field', handler);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Reset the listener
|
|
62
62
|
*/
|
|
63
|
-
reset() {
|
|
63
|
+
reset(): void {
|
|
64
64
|
this.#mapping.clear();
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* Clear dependency mappings for a given class
|
|
69
69
|
*/
|
|
70
|
-
clearSchemaDependency(cls: Class) {
|
|
70
|
+
clearSchemaDependency(cls: Class): void {
|
|
71
71
|
this.#mapping.delete(id(cls));
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -78,7 +78,7 @@ class $SchemaChangeListener {
|
|
|
78
78
|
* @param path The path within the object hierarchy to arrive at the class
|
|
79
79
|
* @param config The configuration or the class
|
|
80
80
|
*/
|
|
81
|
-
trackSchemaDependency(src: Class, parent: Class, path: FieldConfig[], config: ClassConfig) {
|
|
81
|
+
trackSchemaDependency(src: Class, parent: Class, path: FieldConfig[], config: ClassConfig): void {
|
|
82
82
|
const idValue = id(src);
|
|
83
83
|
if (!this.#mapping.has(idValue)) {
|
|
84
84
|
this.#mapping.set(idValue, new Map());
|
|
@@ -91,7 +91,7 @@ class $SchemaChangeListener {
|
|
|
91
91
|
* @param cls The class of the event
|
|
92
92
|
* @param changes The changes to send
|
|
93
93
|
*/
|
|
94
|
-
emitSchemaChanges({ cls, changes }: FieldChangeEvent) {
|
|
94
|
+
emitSchemaChanges({ cls, changes }: FieldChangeEvent): void {
|
|
95
95
|
const updates = new Map<string, SchemaChange>();
|
|
96
96
|
const clsId = id(cls);
|
|
97
97
|
|
|
@@ -116,7 +116,7 @@ class $SchemaChangeListener {
|
|
|
116
116
|
* @param prev The previous class config
|
|
117
117
|
* @param curr The current class config
|
|
118
118
|
*/
|
|
119
|
-
emitFieldChanges({ prev, curr }: ChangeEvent<ClassConfig>) {
|
|
119
|
+
emitFieldChanges({ prev, curr }: ChangeEvent<ClassConfig>): void {
|
|
120
120
|
|
|
121
121
|
const prevView = prev?.views[AllViewⲐ] || { fields: [], schema: {} };
|
|
122
122
|
const currView = curr!.views[AllViewⲐ];
|
|
@@ -139,7 +139,7 @@ class $SchemaChangeListener {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Handle class references changing, but keeping same id
|
|
142
|
-
const compareTypes = (a: Class, b: Class) => 'ᚕid' in a ? a.ᚕid === b.ᚕid : a === b;
|
|
142
|
+
const compareTypes = (a: Class, b: Class): boolean => 'ᚕid' in a ? a.ᚕid === b.ᚕid : a === b;
|
|
143
143
|
|
|
144
144
|
for (const c of currFields) {
|
|
145
145
|
if (prevFields.has(c)) {
|
package/src/service/registry.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { Class, AppError, Util } from '@travetto/base';
|
|
1
|
+
import { Class, AppError, Util, ClassInstance } from '@travetto/base';
|
|
2
2
|
import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
|
|
3
3
|
|
|
4
|
-
import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig } from './types';
|
|
4
|
+
import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig, ViewConfig } from './types';
|
|
5
5
|
import { SchemaChangeListener } from './changes';
|
|
6
6
|
import { BindUtil } from '../bind-util';
|
|
7
7
|
import { AllViewⲐ } from '../internal/types';
|
|
8
8
|
|
|
9
9
|
function hasType<T>(o: unknown): o is { type: Class<T> | string } {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
10
11
|
return !!o && !Util.isPrimitive(o) && 'type' in (o as object) && !!(o as Record<string, string>)['type'];
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -24,31 +25,31 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
24
25
|
super(RootRegistry);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
#computeSubTypeName(cls: Class) {
|
|
28
|
+
#computeSubTypeName(cls: Class): string {
|
|
28
29
|
if (!this.#typeKeys.has(cls)) {
|
|
29
30
|
this.#typeKeys.set(cls, cls.name
|
|
30
31
|
.replace(/([A-Z])([A-Z][a-z])/g, (all, l, r) => `${l}_${r.toLowerCase()}`)
|
|
31
32
|
.replace(/([a-z]|\b)([A-Z])/g, (all, l, r) => l ? `${l}_${r.toLowerCase()}` : r.toLowerCase())
|
|
32
33
|
.toLowerCase());
|
|
33
34
|
}
|
|
34
|
-
return this.#typeKeys.get(cls)
|
|
35
|
+
return this.#typeKeys.get(cls)!;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Get subtype name for a class
|
|
39
40
|
* @param cls Base class
|
|
40
41
|
*/
|
|
41
|
-
getSubTypeName(cls: Class) {
|
|
42
|
+
getSubTypeName(cls: Class): string | undefined {
|
|
42
43
|
if (this.get(cls).subType) {
|
|
43
44
|
return this.#computeSubTypeName(cls);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
|
-
* Ensure type is set
|
|
49
|
+
* Ensure type is set properly
|
|
49
50
|
*/
|
|
50
|
-
ensureInstanceTypeField<T>(cls: Class, o: T) {
|
|
51
|
-
const withType
|
|
51
|
+
ensureInstanceTypeField<T>(cls: Class, o: T): void {
|
|
52
|
+
const withType: { type?: string } = o;
|
|
52
53
|
if (this.get(cls)?.subType && 'type' in this.get(cls).views[AllViewⲐ].schema && !withType.type) { // Do we have a type field defined
|
|
53
54
|
withType.type = this.#computeSubTypeName(cls); // Assign if missing
|
|
54
55
|
}
|
|
@@ -59,8 +60,9 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
59
60
|
* @param cls Class for instance
|
|
60
61
|
* @param o Actual instance
|
|
61
62
|
*/
|
|
62
|
-
resolveSubTypeForInstance<T>(cls: Class<T>, o: T) {
|
|
63
|
-
|
|
63
|
+
resolveSubTypeForInstance<T>(cls: Class<T>, o: T): Class {
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
65
|
+
return this.resolveSubType(cls, hasType<T>(o) ? o.type : (o as unknown as ClassInstance<T>).constructor);
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/**
|
|
@@ -88,7 +90,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
88
90
|
* @param cls The class to register against
|
|
89
91
|
* @param type The subtype name
|
|
90
92
|
*/
|
|
91
|
-
registerSubTypes(cls: Class, type?: string) {
|
|
93
|
+
registerSubTypes(cls: Class, type?: string): string {
|
|
92
94
|
// Mark as subtype
|
|
93
95
|
(this.get(cls) ?? this.getOrCreatePending(cls)).subType = true;
|
|
94
96
|
|
|
@@ -118,7 +120,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
118
120
|
* @param curr The new class
|
|
119
121
|
* @param path The path within the object hierarchy
|
|
120
122
|
*/
|
|
121
|
-
trackSchemaDependencies(cls: Class, curr: Class = cls, path: FieldConfig[] = []) {
|
|
123
|
+
trackSchemaDependencies(cls: Class, curr: Class = cls, path: FieldConfig[] = []): void {
|
|
122
124
|
const config = this.get(curr);
|
|
123
125
|
|
|
124
126
|
SchemaChangeListener.trackSchemaDependency(curr, cls, path, this.get(cls));
|
|
@@ -132,7 +134,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
|
|
135
|
-
createPending(cls: Class) {
|
|
137
|
+
createPending(cls: Class): ClassConfig {
|
|
136
138
|
return {
|
|
137
139
|
class: cls,
|
|
138
140
|
validators: [],
|
|
@@ -149,16 +151,16 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
149
151
|
/**
|
|
150
152
|
* Get schema for a given view
|
|
151
153
|
* @param cls The class to retrieve the schema for
|
|
152
|
-
* @param view
|
|
154
|
+
* @param view The view name
|
|
153
155
|
*/
|
|
154
|
-
getViewSchema<T>(cls: Class<T>, view?: string | typeof AllViewⲐ) {
|
|
156
|
+
getViewSchema<T>(cls: Class<T>, view?: string | typeof AllViewⲐ): ViewConfig {
|
|
155
157
|
view = view ?? AllViewⲐ;
|
|
156
158
|
|
|
157
|
-
const
|
|
158
|
-
if (!
|
|
159
|
+
const schema = this.get(cls)!;
|
|
160
|
+
if (!schema) {
|
|
159
161
|
throw new Error(`Unknown schema class ${cls.name}`);
|
|
160
162
|
}
|
|
161
|
-
const res =
|
|
163
|
+
const res = schema.views[view];
|
|
162
164
|
if (!res) {
|
|
163
165
|
throw new Error(`Unknown view ${view.toString()} for ${cls.name}`);
|
|
164
166
|
}
|
|
@@ -206,11 +208,13 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
206
208
|
* @param view View name
|
|
207
209
|
* @param fields Fields to register
|
|
208
210
|
*/
|
|
209
|
-
registerPendingView<T>(target: Class<T>, view: string, fields: ViewFieldsConfig<T>) {
|
|
211
|
+
registerPendingView<T>(target: Class<T>, view: string, fields: ViewFieldsConfig<T>): void {
|
|
210
212
|
if (!this.#pendingViews.has(target)) {
|
|
211
213
|
this.#pendingViews.set(target, new Map());
|
|
212
214
|
}
|
|
213
|
-
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
216
|
+
const generalConfig = fields as unknown as ViewFieldsConfig<unknown>;
|
|
217
|
+
this.#pendingViews.get(target)!.set(view, generalConfig);
|
|
214
218
|
}
|
|
215
219
|
|
|
216
220
|
/**
|
|
@@ -220,7 +224,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
220
224
|
* @param idx The param index
|
|
221
225
|
* @param config The config to register
|
|
222
226
|
*/
|
|
223
|
-
registerPendingParamFacet(target: Class, prop: string, idx: number, config: Partial<FieldConfig>) {
|
|
227
|
+
registerPendingParamFacet(target: Class, prop: string, idx: number, config: Partial<FieldConfig>): Class {
|
|
224
228
|
config.index = idx;
|
|
225
229
|
return this.registerPendingFieldFacet(target, `${prop}.${idx}`, config);
|
|
226
230
|
}
|
|
@@ -231,11 +235,13 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
231
235
|
* @param prop The property name
|
|
232
236
|
* @param config The config to register
|
|
233
237
|
*/
|
|
234
|
-
registerPendingFieldFacet(target: Class, prop: string, config: Partial<FieldConfig>) {
|
|
238
|
+
registerPendingFieldFacet(target: Class, prop: string, config: Partial<FieldConfig>): Class {
|
|
235
239
|
const allViewConf = this.getOrCreatePending(target).views![AllViewⲐ];
|
|
236
240
|
|
|
237
241
|
if (!allViewConf.schema[prop]) {
|
|
238
242
|
allViewConf.fields.push(prop);
|
|
243
|
+
// Partial config while building
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
239
245
|
allViewConf.schema[prop] = {} as FieldConfig;
|
|
240
246
|
}
|
|
241
247
|
|
|
@@ -252,7 +258,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
252
258
|
* @param type List of types
|
|
253
259
|
* @param conf Extra config
|
|
254
260
|
*/
|
|
255
|
-
registerPendingParamConfig(target: Class, method: string, idx: number, type: ClassList, conf?: Partial<FieldConfig>) {
|
|
261
|
+
registerPendingParamConfig(target: Class, method: string, idx: number, type: ClassList, conf?: Partial<FieldConfig>): Class {
|
|
256
262
|
conf ??= {};
|
|
257
263
|
conf.index = idx;
|
|
258
264
|
return this.registerPendingFieldConfig(target, `${method}.${idx}`, type, conf);
|
|
@@ -265,7 +271,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
265
271
|
* @param type List of types
|
|
266
272
|
* @param conf Extra config
|
|
267
273
|
*/
|
|
268
|
-
registerPendingFieldConfig(target: Class, prop: string, type: ClassList, conf?: Partial<FieldConfig>) {
|
|
274
|
+
registerPendingFieldConfig(target: Class, prop: string, type: ClassList, conf?: Partial<FieldConfig>): Class {
|
|
269
275
|
const fieldConf: FieldConfig = {
|
|
270
276
|
owner: target,
|
|
271
277
|
name: prop,
|
|
@@ -282,14 +288,14 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
282
288
|
* @param dest Target config
|
|
283
289
|
* @param src Source config
|
|
284
290
|
*/
|
|
285
|
-
mergeConfigs(dest: ClassConfig, src: ClassConfig) {
|
|
291
|
+
mergeConfigs(dest: ClassConfig, src: Partial<ClassConfig>): ClassConfig {
|
|
286
292
|
dest.views[AllViewⲐ] = {
|
|
287
|
-
schema: { ...dest.views[AllViewⲐ].schema, ...src.views[AllViewⲐ].schema },
|
|
288
|
-
fields: [...dest.views[AllViewⲐ].fields, ...src.views[AllViewⲐ].fields]
|
|
293
|
+
schema: { ...dest.views[AllViewⲐ].schema, ...src.views?.[AllViewⲐ].schema },
|
|
294
|
+
fields: [...dest.views[AllViewⲐ].fields, ...src.views?.[AllViewⲐ].fields ?? []]
|
|
289
295
|
};
|
|
290
296
|
dest.subType = src.subType || dest.subType;
|
|
291
297
|
dest.title = src.title || dest.title;
|
|
292
|
-
dest.validators = [...src.validators, ...dest.validators];
|
|
298
|
+
dest.validators = [...src.validators ?? [], ...dest.validators];
|
|
293
299
|
return dest;
|
|
294
300
|
}
|
|
295
301
|
|
|
@@ -298,32 +304,32 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
298
304
|
* @param target The target class
|
|
299
305
|
* @param conf The class config
|
|
300
306
|
*/
|
|
301
|
-
finalizeViews<T>(target: Class<T>, conf: ClassConfig) {
|
|
307
|
+
finalizeViews<T>(target: Class<T>, conf: ClassConfig): ClassConfig {
|
|
302
308
|
const allViewConf = conf.views![AllViewⲐ];
|
|
303
|
-
const pending = this.#pendingViews.get(target) ?? new Map<string, ViewFieldsConfig<
|
|
309
|
+
const pending = this.#pendingViews.get(target) ?? new Map<string, ViewFieldsConfig<string>>();
|
|
304
310
|
this.#pendingViews.delete(target);
|
|
305
311
|
|
|
306
312
|
for (const [view, fields] of pending.entries()) {
|
|
307
|
-
const withoutSet = 'without' in fields ? new Set(fields.without
|
|
313
|
+
const withoutSet = 'without' in fields ? new Set<string>(fields.without) : undefined;
|
|
308
314
|
const fieldList = withoutSet ?
|
|
309
315
|
allViewConf.fields.filter(x => !withoutSet.has(x)) :
|
|
310
|
-
('with' in fields ? fields.with
|
|
316
|
+
('with' in fields ? fields.with : []);
|
|
311
317
|
|
|
312
318
|
conf.views![view] = {
|
|
313
|
-
fields: fieldList
|
|
314
|
-
schema: fieldList.reduce((acc, v) => {
|
|
319
|
+
fields: fieldList,
|
|
320
|
+
schema: fieldList.reduce<SchemaConfig>((acc, v) => {
|
|
315
321
|
acc[v] = allViewConf.schema[v];
|
|
316
322
|
return acc;
|
|
317
|
-
}, {}
|
|
323
|
+
}, {})
|
|
318
324
|
};
|
|
319
325
|
}
|
|
320
326
|
|
|
321
327
|
return conf;
|
|
322
328
|
}
|
|
323
329
|
|
|
324
|
-
onInstallFinalize(cls: Class) {
|
|
330
|
+
onInstallFinalize(cls: Class): ClassConfig {
|
|
325
331
|
|
|
326
|
-
let config: ClassConfig = this.createPending(cls)
|
|
332
|
+
let config: ClassConfig = this.createPending(cls);
|
|
327
333
|
|
|
328
334
|
// Merge parent
|
|
329
335
|
const parent = this.getParentClass(cls);
|
|
@@ -339,7 +345,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
339
345
|
// Merge pending, back on top, to allow child to have higher precedence
|
|
340
346
|
const pending = this.getOrCreatePending(cls);
|
|
341
347
|
if (pending) {
|
|
342
|
-
config = this.mergeConfigs(config, pending
|
|
348
|
+
config = this.mergeConfigs(config, pending);
|
|
343
349
|
}
|
|
344
350
|
|
|
345
351
|
// Write views out
|
|
@@ -348,7 +354,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
348
354
|
return config;
|
|
349
355
|
}
|
|
350
356
|
|
|
351
|
-
override onInstall(cls: Class, e: ChangeEvent<Class>) {
|
|
357
|
+
override onInstall(cls: Class, e: ChangeEvent<Class>): void {
|
|
352
358
|
super.onInstall(cls, e);
|
|
353
359
|
|
|
354
360
|
if (this.has(cls)) { // Track dependencies of schemas
|
|
@@ -356,7 +362,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
356
362
|
}
|
|
357
363
|
}
|
|
358
364
|
|
|
359
|
-
override onUninstall<T>(cls: Class<T>, e: ChangeEvent<Class>) {
|
|
365
|
+
override onUninstall<T>(cls: Class<T>, e: ChangeEvent<Class>): void {
|
|
360
366
|
super.onUninstall(cls, e);
|
|
361
367
|
if (e.type === 'removing' && this.hasExpired(cls)) {
|
|
362
368
|
// Recompute subtypes
|
|
@@ -374,7 +380,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
|
|
|
374
380
|
}
|
|
375
381
|
}
|
|
376
382
|
|
|
377
|
-
override emit(ev: ChangeEvent<Class>) {
|
|
383
|
+
override emit(ev: ChangeEvent<Class>): void {
|
|
378
384
|
super.emit(ev);
|
|
379
385
|
if (ev.type === 'changed') {
|
|
380
386
|
SchemaChangeListener.emitFieldChanges({
|
package/src/service/types.ts
CHANGED
|
@@ -77,7 +77,7 @@ export interface FieldConfig extends DescribableConfig {
|
|
|
77
77
|
/**
|
|
78
78
|
* Owner class
|
|
79
79
|
*/
|
|
80
|
-
owner
|
|
80
|
+
owner?: Class;
|
|
81
81
|
/**
|
|
82
82
|
* Field name
|
|
83
83
|
*/
|
|
@@ -112,7 +112,7 @@ export interface FieldConfig extends DescribableConfig {
|
|
|
112
112
|
/**
|
|
113
113
|
* The numeric precision
|
|
114
114
|
*/
|
|
115
|
-
precision?: [number, number
|
|
115
|
+
precision?: [number, number | undefined];
|
|
116
116
|
/**
|
|
117
117
|
* Is the field required
|
|
118
118
|
*/
|
|
@@ -151,4 +151,4 @@ export interface FieldConfig extends DescribableConfig {
|
|
|
151
151
|
access?: 'readonly' | 'writeonly';
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
export type ViewFieldsConfig<T> = { with: (keyof T)[] } | { without: (keyof T)[] };
|
|
154
|
+
export type ViewFieldsConfig<T> = { with: Extract<(keyof T), string>[] } | { without: Extract<(keyof T), string>[] };
|
package/src/typings.d.ts
CHANGED
package/src/validate/error.ts
CHANGED
|
@@ -20,4 +20,8 @@ export class TypeMismatchError extends AppError {
|
|
|
20
20
|
constructor(cls: Class | string, type: string) {
|
|
21
21
|
super(`Expected ${typeof cls === 'string' ? cls : cls.name} but found ${type}`, 'data');
|
|
22
22
|
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isValidationError(err: unknown): err is ValidationError {
|
|
26
|
+
return !!err && err instanceof Error && 'path' in err;
|
|
23
27
|
}
|
package/src/validate/regexp.ts
CHANGED
|
@@ -18,7 +18,7 @@ export const CommonRegExp = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
// Rebind regexes
|
|
21
|
-
for (const k of Object.keys(CommonRegExp)
|
|
21
|
+
for (const k of Object.keys(CommonRegExp)) {
|
|
22
22
|
CommonRegExp[k].name = `[[:${k}:]]`;
|
|
23
23
|
Messages.set(CommonRegExp[k].name!, Messages.get(k)!);
|
|
24
24
|
}
|
package/src/validate/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type ValidationKindCore = 'required' | 'match' | 'min' | 'max' | 'minlength' | 'maxlength' | 'enum' | 'type';
|
|
2
|
+
export type ValidationKind = ValidationKindCore | string;
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Individual Validation Error
|