@travetto/schema 3.1.8 → 3.2.0-rc.0
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 +1 -0
- package/package.json +3 -3
- package/src/bind-util.ts +26 -22
- package/src/service/types.ts +4 -1
- package/support/transform-util.ts +23 -7
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/schema",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0-rc.0",
|
|
4
4
|
"description": "Data type registry for runtime validation, reflection and binding.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"schema",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"directory": "module/schema"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/registry": "^3.
|
|
30
|
+
"@travetto/registry": "^3.2.0-rc.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/transformer": "^3.
|
|
33
|
+
"@travetto/transformer": "^3.2.0-rc.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@travetto/transformer": {
|
package/src/bind-util.ts
CHANGED
|
@@ -55,10 +55,10 @@ export class BindUtil {
|
|
|
55
55
|
let sub = out;
|
|
56
56
|
while (parts.length > 0) {
|
|
57
57
|
const part = parts.shift()!;
|
|
58
|
-
const
|
|
58
|
+
const partArr = part.indexOf('[') > 0;
|
|
59
59
|
const name = part.split(/[^A-Za-z_0-9]/)[0];
|
|
60
|
-
const idx =
|
|
61
|
-
const key =
|
|
60
|
+
const idx = partArr ? part.split(/[\[\]]/)[1] : '';
|
|
61
|
+
const key = partArr ? (/^\d+$/.test(idx) ? parseInt(idx, 10) : (idx.trim() || undefined)) : undefined;
|
|
62
62
|
|
|
63
63
|
if (!(name in sub)) {
|
|
64
64
|
sub[name] = typeof key === 'number' ? [] : {};
|
|
@@ -73,31 +73,31 @@ export class BindUtil {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
const arr = last.indexOf('[') > 0;
|
|
77
|
+
|
|
78
|
+
if (!arr) {
|
|
77
79
|
if (sub[last] && ObjectUtil.isPlainObject(val)) {
|
|
78
80
|
sub[last] = DataUtil.deepAssign(sub[last], val, 'coerce');
|
|
79
81
|
} else {
|
|
80
82
|
sub[last] = val;
|
|
81
83
|
}
|
|
82
84
|
} else {
|
|
83
|
-
const arr = last.indexOf('[') > 0;
|
|
84
85
|
const name = last.split(/[^A-Za-z_0-9]/)[0];
|
|
85
|
-
const idx =
|
|
86
|
+
const idx = last.split(/[\[\]]/)[1];
|
|
87
|
+
|
|
88
|
+
let key = (/^\d+$/.test(idx) ? parseInt(idx, 10) : (idx.trim() || undefined));
|
|
89
|
+
sub[name] ??= (typeof key === 'string') ? {} : [];
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
92
|
+
const arrSub = sub[name] as Record<string, unknown>;
|
|
93
|
+
if (key === undefined) {
|
|
90
94
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
sub[key] = DataUtil.deepAssign(sub[key], val, 'coerce');
|
|
98
|
-
} else {
|
|
99
|
-
sub[key] = val;
|
|
100
|
-
}
|
|
95
|
+
key = arrSub.length as number;
|
|
96
|
+
}
|
|
97
|
+
if (arrSub[key] && ObjectUtil.isPlainObject(val) && ObjectUtil.isPlainObject(arrSub[key])) {
|
|
98
|
+
arrSub[key] = DataUtil.deepAssign(arrSub[key], val, 'coerce');
|
|
99
|
+
} else {
|
|
100
|
+
arrSub[key] = val;
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -109,8 +109,8 @@ export class BindUtil {
|
|
|
109
109
|
* @param conf The object to flatten the paths for
|
|
110
110
|
* @param val The starting prefix
|
|
111
111
|
*/
|
|
112
|
-
static flattenPaths(data: Record<string, unknown>, prefix: string = ''): Record<string,
|
|
113
|
-
const out: Record<string,
|
|
112
|
+
static flattenPaths<V extends string = string>(data: Record<string, unknown>, prefix: string = ''): Record<string, V> {
|
|
113
|
+
const out: Record<string, V> = {};
|
|
114
114
|
for (const [key, value] of Object.entries(data)) {
|
|
115
115
|
const pre = `${prefix}${key}`;
|
|
116
116
|
if (ObjectUtil.isPlainObject(value)) {
|
|
@@ -126,7 +126,8 @@ export class BindUtil {
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
} else {
|
|
129
|
-
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
130
|
+
out[pre] = (value ?? '') as V;
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
return out;
|
|
@@ -273,6 +274,9 @@ export class BindUtil {
|
|
|
273
274
|
if ((val === undefined || val === null) && applyDefaults) {
|
|
274
275
|
val = field.default;
|
|
275
276
|
}
|
|
277
|
+
if (!field.required && (val === undefined || val === null)) {
|
|
278
|
+
return val;
|
|
279
|
+
}
|
|
276
280
|
const complex = SchemaRegistry.has(field.type);
|
|
277
281
|
if (field.array) {
|
|
278
282
|
const valArr = !Array.isArray(val) ? [val] : val;
|
package/src/service/types.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { ValidatorFn } from '../validate/types';
|
|
|
5
5
|
|
|
6
6
|
export type ClassList = Class | [Class];
|
|
7
7
|
|
|
8
|
+
type TemplateLiteralPart = string | NumberConstructor | StringConstructor | BooleanConstructor;
|
|
9
|
+
export type TemplateLiteral = { op: 'and' | 'or', values: (TemplateLiteralPart | TemplateLiteral)[] };
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* Basic describable configuration
|
|
10
13
|
*/
|
|
@@ -135,7 +138,7 @@ export interface FieldConfig extends DescribableConfig {
|
|
|
135
138
|
/**
|
|
136
139
|
* Does the field expect a match
|
|
137
140
|
*/
|
|
138
|
-
match?: { re: RegExp, message?: string };
|
|
141
|
+
match?: { re: RegExp, message?: string, template?: TemplateLiteral };
|
|
139
142
|
/**
|
|
140
143
|
* Minimum value configuration
|
|
141
144
|
*/
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type AnyType, DeclarationUtil, LiteralUtil,
|
|
4
|
+
DecoratorUtil, DocUtil, ParamDocumentation, TransformerState
|
|
5
|
+
} from '@travetto/transformer';
|
|
3
6
|
|
|
4
7
|
const SCHEMA_MOD = '@travetto/schema/src/decorator/schema';
|
|
5
8
|
const FIELD_MOD = '@travetto/schema/src/decorator/field';
|
|
@@ -15,6 +18,7 @@ export class SchemaTransformUtil {
|
|
|
15
18
|
case 'pointer': return this.toConcreteType(state, type.target, node, root);
|
|
16
19
|
case 'managed': return state.getOrImport(type);
|
|
17
20
|
case 'tuple': return state.fromLiteral(type.subTypes.map(x => this.toConcreteType(state, x, node, root)!));
|
|
21
|
+
case 'template': return state.createIdentifier(type.ctor.name);
|
|
18
22
|
case 'literal': {
|
|
19
23
|
if ((type.ctor === Array) && type.typeArguments?.length) {
|
|
20
24
|
return state.fromLiteral([this.toConcreteType(state, type.typeArguments[0], node, root)]);
|
|
@@ -108,17 +112,28 @@ export class SchemaTransformUtil {
|
|
|
108
112
|
));
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
const primaryExpr = typeExpr.key === 'literal' && typeExpr.typeArguments?.[0] ? typeExpr.typeArguments[0] : typeExpr;
|
|
116
|
+
|
|
117
|
+
// We need to ensure we aren't being tripped up by the wrapper for arrays, sets, etc.
|
|
111
118
|
// If we have a union type
|
|
112
|
-
if (
|
|
113
|
-
const values =
|
|
114
|
-
.filter(x => x !== undefined && x !== null)
|
|
119
|
+
if (primaryExpr.key === 'union') {
|
|
120
|
+
const values = primaryExpr.subTypes.map(x => x.key === 'literal' ? x.value : undefined)
|
|
121
|
+
.filter(x => x !== undefined && x !== null)
|
|
122
|
+
.sort();
|
|
115
123
|
|
|
116
|
-
if (values.length ===
|
|
124
|
+
if (values.length === primaryExpr.subTypes.length) {
|
|
117
125
|
attrs.push(state.factory.createPropertyAssignment('enum', state.fromLiteral({
|
|
118
126
|
values,
|
|
119
127
|
message: `{path} is only allowed to be "${values.join('" or "')}"`
|
|
120
128
|
})));
|
|
121
129
|
}
|
|
130
|
+
} else if (primaryExpr.key === 'template' && primaryExpr.template) {
|
|
131
|
+
const re = LiteralUtil.templateLiteralToRegex(primaryExpr.template);
|
|
132
|
+
attrs.push(state.factory.createPropertyAssignment('match', state.fromLiteral({
|
|
133
|
+
re: new RegExp(re),
|
|
134
|
+
template: primaryExpr.template,
|
|
135
|
+
message: `{path} must match "${re}"`
|
|
136
|
+
})));
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
if (ts.isParameter(node)) {
|
|
@@ -210,11 +225,12 @@ export class SchemaTransformUtil {
|
|
|
210
225
|
const { out, type } = this.unwrapType(anyType);
|
|
211
226
|
switch (type?.key) {
|
|
212
227
|
case 'managed': out.type = state.typeToIdentifier(type); break;
|
|
213
|
-
case 'shape': out.type =
|
|
228
|
+
case 'shape': out.type = this.toConcreteType(state, type, target); break;
|
|
229
|
+
case 'template': out.type = state.factory.createIdentifier(type.ctor.name); break;
|
|
214
230
|
case 'literal': {
|
|
215
231
|
if (type.ctor) {
|
|
216
232
|
out.type = out.array ?
|
|
217
|
-
|
|
233
|
+
this.toConcreteType(state, type, target) :
|
|
218
234
|
state.factory.createIdentifier(type.ctor.name);
|
|
219
235
|
}
|
|
220
236
|
}
|