@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 CHANGED
@@ -36,6 +36,7 @@ export class User {
36
36
  name: string;
37
37
  age: number;
38
38
  favoriteFood?: 'pizza' | 'burrito' | 'salad';
39
+ height?: `${number}${'m' | 'ft'}`;
39
40
  }
40
41
  ```
41
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/schema",
3
- "version": "3.1.8",
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.1.4"
30
+ "@travetto/registry": "^3.2.0-rc.0"
31
31
  },
32
32
  "peerDependencies": {
33
- "@travetto/transformer": "^3.1.5"
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 arr = part.indexOf('[') > 0;
58
+ const partArr = part.indexOf('[') > 0;
59
59
  const name = part.split(/[^A-Za-z_0-9]/)[0];
60
- const idx = arr ? part.split(/[\[\]]/)[1] : '';
61
- const key = arr ? (/^\d+$/.test(idx) ? parseInt(idx, 10) : (idx.trim() || undefined)) : undefined;
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
- if (last.indexOf('[') < 0) {
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 = arr ? last.split(/[\[\]]/)[1] : '';
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
- let key = arr ? (/^\d+$/.test(idx) ? parseInt(idx, 10) : (idx.trim() || undefined)) : undefined;
88
- if (sub[name] === undefined) {
89
- sub[name] = (typeof key === 'string') ? {} : [];
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
- sub = sub[name] as Record<string, unknown>;
92
- if (key === undefined) {
93
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
94
- key = sub.length as number;
95
- }
96
- if (sub[key] && ObjectUtil.isPlainObject(val) && ObjectUtil.isPlainObject(sub[key])) {
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, unknown> {
113
- const out: Record<string, unknown> = {};
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
- out[pre] = value ?? '';
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;
@@ -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 { type AnyType, DeclarationUtil, DecoratorUtil, DocUtil, ParamDocumentation, TransformerState } from '@travetto/transformer';
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 (typeExpr.key === 'union') {
113
- const values = typeExpr.subTypes.map(x => x.key === 'literal' ? x.value : undefined)
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 === typeExpr.subTypes.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 = SchemaTransformUtil.toConcreteType(state, type, target); break;
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
- SchemaTransformUtil.toConcreteType(state, type, target) :
233
+ this.toConcreteType(state, type, target) :
218
234
  state.factory.createIdentifier(type.ctor.name);
219
235
  }
220
236
  }