@travetto/schema 5.0.0-rc.1 → 5.0.0-rc.11

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
@@ -63,28 +63,28 @@ User:
63
63
 
64
64
  ### Fields
65
65
  This schema provides a powerful base for data binding and validation at runtime. Additionally there may be types that cannot be detected, or some information that the programmer would like to override. Below are the supported field decorators:
66
- * [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L31) defines a field that will be serialized.
67
- * [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L71) defines a that field should be required
68
- * [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L78) defines the allowable values that a field can have
69
- * [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L99) defines a regular expression that the field value should match
70
- * [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L107) enforces min length of a string
71
- * [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L117) enforces max length of a string
72
- * [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L107) enforces min value for a date or a number
73
- * [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L117) enforces max value for a date or a number
74
- * [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L144) ensures string field matches basic email regex
75
- * [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L151) ensures string field matches basic telephone regex
76
- * [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L158) ensures string field matches basic url regex
77
- * [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L197) exclude from auto schema registration
78
- * [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L172) ensures number passed in is only a whole number
79
- * [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L178) ensures number passed in allows fractional values
80
- * [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L190) provides support for standard currency
81
- * [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L86) indicates that a field is expecting natural language input, not just discrete values
82
- * [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L91) same as text, but expects longer form content
83
- * [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L58) defines a that field should not be bindable external to the class
84
- * [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L52) defines a that field should not be exported in serialization, but that it can be bound to
85
- * [@Secret](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L64) marks a field as being sensitive. This is used by certain logging activities to ensure sensitive information is not logged out.
86
- * [@Specifier](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L206) attributes additional specifiers to a field, allowing for more specification beyond just the field's type.
87
- * [@SubTypeField](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L212) allows for promoting a given field as the owner of the sub type discriminator (defaults to `type`).
66
+ * [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L30) defines a field that will be serialized.
67
+ * [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L70) defines a that field should be required
68
+ * [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L77) defines the allowable values that a field can have
69
+ * [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L98) defines a regular expression that the field value should match
70
+ * [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L106) enforces min length of a string
71
+ * [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L116) enforces max length of a string
72
+ * [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L106) enforces min value for a date or a number
73
+ * [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L116) enforces max value for a date or a number
74
+ * [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L143) ensures string field matches basic email regex
75
+ * [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L150) ensures string field matches basic telephone regex
76
+ * [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L157) ensures string field matches basic url regex
77
+ * [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L196) exclude from auto schema registration
78
+ * [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L171) ensures number passed in is only a whole number
79
+ * [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L177) ensures number passed in allows fractional values
80
+ * [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L189) provides support for standard currency
81
+ * [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L85) indicates that a field is expecting natural language input, not just discrete values
82
+ * [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L90) same as text, but expects longer form content
83
+ * [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L57) defines a that field should not be bindable external to the class
84
+ * [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L51) defines a that field should not be exported in serialization, but that it can be bound to
85
+ * [@Secret](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L63) marks a field as being sensitive. This is used by certain logging activities to ensure sensitive information is not logged out.
86
+ * [@Specifier](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L205) attributes additional specifiers to a field, allowing for more specification beyond just the field's type.
87
+ * [@SubTypeField](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L211) allows for promoting a given field as the owner of the sub type discriminator (defaults to `type`).
88
88
  Additionally, schemas can be nested to form more complex data structures that are able to bound and validated.
89
89
 
90
90
  Just like the class, all fields can be defined with
@@ -198,7 +198,6 @@ export async function validate(): Promise<void> {
198
198
 
199
199
  const person = Person.from({
200
200
  name: 'Test',
201
- // @ts-expect-error
202
201
  age: 'abc',
203
202
  address: {
204
203
  street1: '1234 Fun'
@@ -313,7 +312,7 @@ This feature is meant to allow for simple Typescript types to be able to be back
313
312
 
314
313
  **Code: Simple Custom Type**
315
314
  ```typescript
316
- import { DataUtil } from '@travetto/base';
315
+ import { DataUtil } from '@travetto/schema';
317
316
 
318
317
  /**
319
318
  * @concrete #PointImpl
@@ -330,7 +329,8 @@ export class PointImpl {
330
329
 
331
330
  static bindSchema(input: unknown): [number, number] | typeof INVALID | undefined {
332
331
  if (Array.isArray(input) && input.length === 2) {
333
- return input.map(x => DataUtil.coerceType(x, Number, false)) as [number, number];
332
+ const [a, b] = input.map(x => DataUtil.coerceType(x, Number, false));
333
+ return [a, b];
334
334
  } else {
335
335
  return INVALID;
336
336
  }
@@ -379,7 +379,7 @@ Validation Failed {
379
379
  ```
380
380
 
381
381
  ## Data Utilities
382
- Data utilities for binding values, and type conversion. Currently [DataUtil](https://github.com/travetto/travetto/tree/main/module/schema/src/data.ts#L10) includes:
382
+ Data utilities for binding values, and type conversion. Currently [DataUtil](https://github.com/travetto/travetto/tree/main/module/schema/src/data.ts#L11) includes:
383
383
  * `deepAssign(a, b, mode ?)` which allows for deep assignment of `b` onto `a`, the `mode` determines how aggressive the assignment is, and how flexible it is. `mode` can have any of the following values:
384
384
  * `loose`, which is the default is the most lenient. It will not error out, and overwrites will always happen
385
385
  * `coerce`, will attempt to force values from `b` to fit the types of `a`, and if it can't it will error out
package/__index__.ts CHANGED
@@ -13,5 +13,4 @@ export * from './src/validate/error';
13
13
  export * from './src/validate/types';
14
14
  export * from './src/bind-util';
15
15
  export * from './src/data';
16
- export * from './src/name';
17
- export * from './src/types';
16
+ export * from './src/name';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/schema",
3
- "version": "5.0.0-rc.1",
3
+ "version": "5.0.0-rc.11",
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": "^5.0.0-rc.1"
30
+ "@travetto/registry": "^5.0.0-rc.10"
31
31
  },
32
32
  "peerDependencies": {
33
- "@travetto/transformer": "^5.0.0-rc.1"
33
+ "@travetto/transformer": "^5.0.0-rc.7"
34
34
  },
35
35
  "peerDependenciesMeta": {
36
36
  "@travetto/transformer": {
package/src/bind-util.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Class, ConcreteClass, TypedObject } from '@travetto/base';
1
+ import { castTo, Class, classConstruct, asFull, TypedObject, castKey } from '@travetto/runtime';
2
2
 
3
3
  import { DataUtil } from './data';
4
4
  import { AllViewⲐ } from './internal/types';
@@ -11,6 +11,10 @@ type BindConfig = {
11
11
  filterValue?: (value: unknown, field: FieldConfig) => boolean;
12
12
  };
13
13
 
14
+ function isInstance<T>(o: unknown): o is T {
15
+ return !!o && !DataUtil.isPrimitive(o);
16
+ }
17
+
14
18
  /**
15
19
  * Utilities for binding objects to schemas
16
20
  */
@@ -35,8 +39,7 @@ export class BindUtil {
35
39
  }
36
40
  }
37
41
  }
38
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
39
- return val as T;
42
+ return castTo(val);
40
43
  }
41
44
 
42
45
  /**
@@ -64,13 +67,11 @@ export class BindUtil {
64
67
  if (!(name in sub)) {
65
68
  sub[name] = typeof key === 'number' ? [] : {};
66
69
  }
67
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
68
- sub = sub[name] as Record<string, unknown>;
70
+ sub = castTo(sub[name]);
69
71
 
70
72
  if (idx && key !== undefined) {
71
73
  sub[key] ??= {};
72
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
73
- sub = sub[key] as Record<string, unknown>;
74
+ sub = castTo(sub[key]);
74
75
  }
75
76
  }
76
77
 
@@ -89,11 +90,9 @@ export class BindUtil {
89
90
  let key = (/^\d+$/.test(idx) ? parseInt(idx, 10) : (idx.trim() || undefined));
90
91
  sub[name] ??= (typeof key === 'string') ? {} : [];
91
92
 
92
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
93
- const arrSub = sub[name] as Record<string, unknown>;
93
+ const arrSub: Record<string, unknown> & { length: number } = castTo(sub[name]);
94
94
  if (key === undefined) {
95
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
96
- key = arrSub.length as number;
95
+ key = arrSub.length;
97
96
  }
98
97
  if (arrSub[key] && DataUtil.isPlainObject(val) && DataUtil.isPlainObject(arrSub[key])) {
99
98
  arrSub[key] = DataUtil.deepAssign(arrSub[key], val, 'coerce');
@@ -127,8 +126,7 @@ export class BindUtil {
127
126
  }
128
127
  }
129
128
  } else {
130
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
131
- out[pre] = (value ?? '') as V;
129
+ out[pre] = castTo(value ?? '');
132
130
  }
133
131
  }
134
132
  return out;
@@ -147,24 +145,19 @@ export class BindUtil {
147
145
  if (data === null || data === undefined) {
148
146
  return data;
149
147
  }
150
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
151
- const cls = SchemaRegistry.resolveInstanceType<T>(cons, data as T);
148
+ const cls = SchemaRegistry.resolveInstanceType<T>(cons, asFull<T>(data));
152
149
  if (data instanceof cls) {
153
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
154
- return data as T;
150
+ return castTo(data);
155
151
  } else {
156
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
157
- const tgt = new (cls as ConcreteClass<T>)() as T;
158
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
159
- SchemaRegistry.ensureInstanceTypeField(cls, tgt as T & { type?: string });
152
+ const tgt = classConstruct<T & { type?: string }>(cls);
153
+ SchemaRegistry.ensureInstanceTypeField(cls, tgt);
160
154
 
161
155
  for (const k of TypedObject.keys(tgt)) { // Do not retain undefined fields
162
156
  if (tgt[k] === undefined) {
163
157
  delete tgt[k];
164
158
  }
165
159
  }
166
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
167
- return this.bindSchemaToObject(cls, tgt, data as object, cfg);
160
+ return this.bindSchemaToObject(cls, tgt, data, cfg);
168
161
  }
169
162
  }
170
163
 
@@ -179,14 +172,13 @@ export class BindUtil {
179
172
  const view = cfg.view ?? AllViewⲐ; // Does not convey
180
173
  delete cfg.view;
181
174
 
182
- if (!!data && !DataUtil.isPrimitive(data)) {
175
+ if (!!data && isInstance<T>(data)) {
183
176
  const conf = SchemaRegistry.get(cons);
184
177
 
185
178
  // If no configuration
186
179
  if (!conf) {
187
180
  for (const k of TypedObject.keys(data)) {
188
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
189
- obj[k] = data[k as keyof typeof data];
181
+ obj[k] = data[k];
190
182
  }
191
183
  } else {
192
184
 
@@ -217,8 +209,7 @@ export class BindUtil {
217
209
  continue;
218
210
  }
219
211
 
220
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
221
- let v: unknown = data[inboundField as keyof typeof data];
212
+ let v: unknown = data[castKey<T>(inboundField)];
222
213
 
223
214
  // Filtering values
224
215
  if (cfg.filterValue && !cfg.filterValue(v, field)) {
@@ -248,8 +239,7 @@ export class BindUtil {
248
239
  }
249
240
  }
250
241
 
251
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
252
- obj[schemaFieldName as keyof typeof obj] = v as (typeof obj)[keyof typeof obj];
242
+ obj[castKey<T>(schemaFieldName)] = castTo(v);
253
243
 
254
244
  if (field.accessor) {
255
245
  Object.defineProperty(obj, schemaFieldName, {
package/src/data.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isNumberObject as isNum, isBooleanObject as isBool, isStringObject as isStr } from 'node:util/types';
2
2
 
3
- import { Class, ClassInstance, TypedObject } from '@travetto/base';
3
+ import { asConstructable, castTo, Class, asFull, TypedObject } from '@travetto/runtime';
4
+ import { UnknownType } from './internal/types';
4
5
 
5
6
  const REGEX_PAT = /[\/](.*)[\/](i|g|m|s)?/;
6
7
 
@@ -62,15 +63,13 @@ export class DataUtil {
62
63
  if (isArrA !== isArrB || isSimpA !== isSimpB) {
63
64
  throw new Error(`Cannot merge differing types ${a} and ${b}`);
64
65
  }
65
- if (isArrB) { // Arrays
66
+ if (Array.isArray(b)) { // Arrays
66
67
  ret = a; // Write onto A
67
68
  if (mode === 'replace') {
68
69
  ret = b;
69
70
  } else {
70
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
71
- const retArr = ret as unknown[];
72
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
73
- const bArr = b as unknown[];
71
+ const retArr: unknown[] = castTo(ret);
72
+ const bArr = b;
74
73
  for (let i = 0; i < bArr.length; i++) {
75
74
  retArr[i] = this.#deepAssignRaw(retArr[i], bArr[i], mode);
76
75
  }
@@ -83,16 +82,13 @@ export class DataUtil {
83
82
  if (mode === 'strict') { // Bail on strict
84
83
  throw new Error(`Cannot merge ${a} [${typeof a}] with ${b} [${typeof b}]`);
85
84
  } else if (mode === 'coerce') { // Force on coerce
86
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
87
- ret = this.coerceType(b, (a as ClassInstance).constructor, false);
85
+ ret = this.coerceType(b, asConstructable(a).constructor, false);
88
86
  }
89
87
  }
90
88
  } else { // Object merge
91
89
  ret = a;
92
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
93
- const bObj = b as Record<string, unknown>;
94
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
95
- const retObj = ret as Record<string, unknown>;
90
+ const bObj: Record<string, unknown> = castTo(b);
91
+ const retObj: Record<string, unknown> = castTo(ret);
96
92
 
97
93
  for (const key of Object.keys(bObj)) {
98
94
  retObj[key] = this.#deepAssignRaw(retObj[key], bObj[key], mode);
@@ -125,11 +121,13 @@ export class DataUtil {
125
121
  */
126
122
  static coerceType(input: unknown, type: typeof String, strict?: boolean): string;
127
123
  static coerceType(input: unknown, type: typeof Number, strict?: boolean): number;
124
+ static coerceType(input: unknown, type: typeof BigInt, strict?: boolean): bigint;
128
125
  static coerceType(input: unknown, type: typeof Boolean, strict?: boolean): boolean;
129
126
  static coerceType(input: unknown, type: typeof Date, strict?: boolean): Date;
130
127
  static coerceType(input: unknown, type: typeof RegExp, strict?: boolean): RegExp;
131
- static coerceType<T>(input: unknown, type: Class<T>, strict?: boolean): T;
132
- static coerceType(input: unknown, type: Class<unknown>, strict = true): unknown {
128
+ static coerceType(input: unknown, type: typeof UnknownType, strict?: boolean): unknown;
129
+ static coerceType<T>(input: unknown, type: Class<T> | Function, strict?: boolean): T;
130
+ static coerceType(input: unknown, type: Class<unknown> | Function, strict = true): unknown {
133
131
  // Do nothing
134
132
  if (input === null || input === undefined) {
135
133
  return input;
@@ -143,14 +141,17 @@ export class DataUtil {
143
141
  case Date: {
144
142
  let res: Date | undefined;
145
143
  if (typeof input === 'object' && 'toDate' in input && typeof input.toDate === 'function') {
146
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
147
- res = input.toDate() as Date;
144
+ res = castTo(input.toDate());
148
145
  } else {
149
- res = typeof input === 'number' || /^[-]?\d+$/.test(`${input}`) ?
150
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
151
- new Date(parseInt(input as string, 10)) : new Date(input as Date);
146
+ res = input instanceof Date ?
147
+ input :
148
+ typeof input === 'number' ?
149
+ new Date(input) :
150
+ (typeof input === 'string' && /^[-]?\d+$/.test(input)) ?
151
+ new Date(parseInt(input, 10)) :
152
+ new Date(input.toString());
152
153
  }
153
- if (strict && Number.isNaN(res.getTime())) {
154
+ if (strict && res && Number.isNaN(res.getTime())) {
154
155
  throw new Error(`Invalid date value: ${input}`);
155
156
  }
156
157
  return res;
@@ -162,6 +163,20 @@ export class DataUtil {
162
163
  }
163
164
  return res;
164
165
  }
166
+ case BigInt: {
167
+ if (typeof input === 'bigint') {
168
+ return input;
169
+ }
170
+ try {
171
+ return BigInt((typeof input === 'boolean' || typeof input === 'number') ?
172
+ input : `${input}`.replace(/n$/i, ''));
173
+ } catch {
174
+ if (strict) {
175
+ throw new Error(`Invalid numeric value: ${input}`);
176
+ }
177
+ return;
178
+ }
179
+ }
165
180
  case Boolean: {
166
181
  const match = `${input}`.match(/^((?<TRUE>true|yes|1|on)|false|no|off|0)$/i);
167
182
  if (strict && !match) {
@@ -186,6 +201,9 @@ export class DataUtil {
186
201
  return;
187
202
  }
188
203
  }
204
+ case UnknownType: {
205
+ return input;
206
+ }
189
207
  case Object: {
190
208
  if (!strict || this.isPlainObject(input)) {
191
209
  return input;
@@ -207,9 +225,8 @@ export class DataUtil {
207
225
  * Clone top level properties to a new object
208
226
  * @param o Object to clone
209
227
  */
210
- static shallowClone<T = unknown>(a: T): T {
211
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
212
- return (Array.isArray(a) ? a.slice(0) : (this.isSimpleValue(a) ? a : { ...(a as {}) })) as T;
228
+ static shallowClone<T>(a: T): T {
229
+ return castTo(Array.isArray(a) ? a.slice(0) : (this.isSimpleValue(a) ? a : { ...castTo<object>(a) }));
213
230
  }
214
231
 
215
232
  /**
@@ -222,8 +239,7 @@ export class DataUtil {
222
239
  if (!a || this.isSimpleValue(a)) {
223
240
  throw new Error(`Cannot merge onto a simple value, ${a}`);
224
241
  }
225
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
226
- return this.#deepAssignRaw(a, b, mode) as T & U;
242
+ return castTo(this.#deepAssignRaw(a, b, mode));
227
243
  }
228
244
 
229
245
  /**
@@ -245,8 +261,7 @@ export class DataUtil {
245
261
  }
246
262
  }
247
263
  }
248
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
249
- return out as T;
264
+ return asFull(out);
250
265
  } else {
251
266
  return obj;
252
267
  }
@@ -1,4 +1,4 @@
1
- import { Class, ClassInstance } from '@travetto/base';
1
+ import { Class, ClassInstance } from '@travetto/runtime';
2
2
 
3
3
  import { DescribableConfig } from '../service/types';
4
4
  import { SchemaRegistry } from '../service/registry';
@@ -1,15 +1,15 @@
1
- import { ClassInstance } from '@travetto/base';
1
+ import { Any, castTo, ClassInstance } from '@travetto/runtime';
2
2
 
3
3
  import { SchemaRegistry } from '../service/registry';
4
4
  import { CommonRegExp } from '../validate/regexp';
5
5
  import { ClassList, FieldConfig } from '../service/types';
6
6
 
7
- type PropType<V> = (<T extends Partial<Record<K, V>>, K extends string>(t: T, k: K, idx?: number) => void) & {
7
+ type PropType<V> = (<T extends Partial<Record<K, V>>, K extends string>(t: T, k: K, idx?: TypedPropertyDescriptor<Any> | number) => void) & {
8
8
  Param: (t: unknown, k: string, idx: number) => void;
9
9
  };
10
10
 
11
11
  function prop<V>(obj: Partial<FieldConfig>): PropType<V> {
12
- const fn = (t: ClassInstance, k: string, idx?: number): void => {
12
+ const fn = (t: ClassInstance, k: string, idx?: number | TypedPropertyDescriptor<Any>): void => {
13
13
  if (idx !== undefined && typeof idx === 'number') {
14
14
  SchemaRegistry.registerPendingParamFacet(t.constructor, k, idx, obj);
15
15
  } else {
@@ -18,8 +18,7 @@ function prop<V>(obj: Partial<FieldConfig>): PropType<V> {
18
18
  };
19
19
  fn.Param = fn;
20
20
 
21
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
22
- return fn as PropType<V>;
21
+ return castTo(fn);
23
22
  }
24
23
 
25
24
  /**
@@ -29,7 +28,7 @@ function prop<V>(obj: Partial<FieldConfig>): PropType<V> {
29
28
  * @augments `@travetto/schema:Field`
30
29
  */
31
30
  export function Field(type: ClassList, ...config: Partial<FieldConfig>[]) {
32
- return (f: ClassInstance, k: string, idx?: number): void => {
31
+ return (f: ClassInstance, k: string, idx?: number | TypedPropertyDescriptor<Any>): void => {
33
32
  if (idx !== undefined && typeof idx === 'number') {
34
33
  SchemaRegistry.registerPendingParamConfig(f.constructor, k, idx, type, Object.assign({}, ...config));
35
34
  } else {
@@ -1,14 +1,14 @@
1
- import { Class } from '@travetto/base';
1
+ import { castTo, Class, DeepPartial } from '@travetto/runtime';
2
2
 
3
3
  import { BindUtil } from '../bind-util';
4
4
  import { SchemaRegistry } from '../service/registry';
5
5
  import { ClassConfig, ViewFieldsConfig } from '../service/types';
6
- import { DeepPartial } from '../types';
7
6
  import { ValidatorFn } from '../validate/types';
8
7
 
9
8
  /**
10
9
  * Register a class as a Schema
11
10
  *
11
+ *
12
12
  * @augments `@travetto/schema:Schema`
13
13
  */
14
14
  export function Schema(cfg?: Partial<Pick<ClassConfig, 'subTypeName' | 'subTypeField' | 'baseType'>>) { // Auto is used during compilation
@@ -28,8 +28,7 @@ export function Schema(cfg?: Partial<Pick<ClassConfig, 'subTypeName' | 'subTypeF
28
28
  */
29
29
  export function Validator<T>(fn: ValidatorFn<T, string>) {
30
30
  return (target: Class<T>): void => {
31
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
32
- SchemaRegistry.getOrCreatePending(target).validators!.push(fn as ValidatorFn<unknown, unknown>);
31
+ SchemaRegistry.getOrCreatePending(target).validators!.push(castTo(fn));
33
32
  };
34
33
  }
35
34
 
package/src/global.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { Class } from '@travetto/base';
2
- import type { DeepPartial } from './types';
1
+ import type { Class, DeepPartial } from '@travetto/runtime';
3
2
 
4
3
  declare global {
5
4
  interface Function {
@@ -1 +1,2 @@
1
- export const AllViewⲐ: unique symbol = Symbol.for('@travetto/schema:all');
1
+ export const AllViewⲐ: unique symbol = Symbol.for('@travetto/schema:all');
2
+ export class UnknownType { }
package/src/name.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RuntimeIndex } from '@travetto/manifest';
1
+ import { describeFunction } from '@travetto/runtime';
2
2
  import { ClassConfig } from './service/types';
3
3
 
4
4
  const SYN_RE = /(__)(\d+)Ⲑsyn$/;
@@ -19,7 +19,7 @@ export class SchemaNameResolver {
19
19
 
20
20
  getName(schema: ClassConfig): string {
21
21
  const id = schema.class.Ⲑid;
22
- if (RuntimeIndex.getFunctionMetadataFromClass(schema.class)?.synthetic && SYN_RE.test(schema.class.name)) {
22
+ if (describeFunction(schema.class)?.synthetic && SYN_RE.test(schema.class.name)) {
23
23
  if (!this.#schemaIdToName.has(id)) {
24
24
  const name = schema.class.name.replace(SYN_RE, (_, pref, uid) => `__${(+uid % this.#base).toString().padStart(this.#digits, '0')}`);
25
25
  this.#schemaIdToName.set(id, name);
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
 
3
- import { Class } from '@travetto/base';
3
+ import { Class } from '@travetto/runtime';
4
4
  import { ChangeEvent } from '@travetto/registry';
5
5
 
6
6
  import { FieldConfig, ClassConfig } from './types';
@@ -1,5 +1,4 @@
1
- import { RuntimeIndex } from '@travetto/manifest';
2
- import { Class, AppError } from '@travetto/base';
1
+ import { Class, AppError, describeFunction, castTo, classConstruct, asFull, castKey } from '@travetto/runtime';
3
2
  import { MetadataRegistry, RootRegistry, ChangeEvent } from '@travetto/registry';
4
3
 
5
4
  import { ClassList, FieldConfig, ClassConfig, SchemaConfig, ViewFieldsConfig, ViewConfig } from './types';
@@ -52,8 +51,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
52
51
  */
53
52
  getMetadata<K>(cls: Class, key: symbol): K | undefined {
54
53
  const cfg = this.get(cls);
55
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
56
- return cfg.metadata?.[key] as K;
54
+ return castTo(cfg.metadata?.[key]);
57
55
  }
58
56
 
59
57
  /**
@@ -65,8 +63,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
65
63
  */
66
64
  getOrCreatePendingMetadata<K>(cls: Class, key: symbol, value: K): K {
67
65
  const cfg = this.getOrCreatePending(cls);
68
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
69
- return ((cfg.metadata ??= {})[key] ??= value) as K;
66
+ return castTo((cfg.metadata ??= {})[key] ??= value);
70
67
  }
71
68
 
72
69
  /**
@@ -74,11 +71,9 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
74
71
  */
75
72
  ensureInstanceTypeField<T>(cls: Class, o: T): void {
76
73
  const schema = this.get(cls);
77
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
78
- const typeField = (schema.subTypeField) as keyof T;
74
+ const typeField = castKey<T>(schema.subTypeField);
79
75
  if (schema.subTypeName && typeField in schema.views[AllViewⲐ].schema && !o[typeField]) { // Do we have a type field defined
80
- // @ts-expect-error
81
- o[typeField] = schema.subTypeName; // Assign if missing
76
+ o[typeField] = castTo(schema.subTypeName); // Assign if missing
82
77
  }
83
78
  }
84
79
 
@@ -113,11 +108,9 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
113
108
  const baseSchema = this.get(base);
114
109
 
115
110
  if (clsSchema.subTypeName || baseSchema.baseType) { // We have a sub type
116
- // @ts-expect-error
117
- const type = o[baseSchema.subTypeField] ?? clsSchema.subTypeName ?? baseSchema.subTypeName;
111
+ const type = castTo<string>(o[castKey<T>(baseSchema.subTypeField)]) ?? clsSchema.subTypeName ?? baseSchema.subTypeName;
118
112
  const ret = this.#subTypes.get(base)!.get(type)!;
119
- // @ts-expect-error
120
- if (ret && !(new ret() instanceof cls)) {
113
+ if (ret && !(classConstruct(ret) instanceof cls)) {
121
114
  throw new AppError(`Resolved class ${ret.name} is not assignable to ${cls.name}`);
122
115
  }
123
116
  return ret;
@@ -188,7 +181,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
188
181
  class: cls,
189
182
  validators: [],
190
183
  subTypeField: 'type',
191
- baseType: RuntimeIndex.getFunctionMetadata(cls)?.abstract,
184
+ baseType: describeFunction(cls)?.abstract,
192
185
  metadata: {},
193
186
  methods: {},
194
187
  views: {
@@ -238,8 +231,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
238
231
  if (!this.#pendingViews.has(target)) {
239
232
  this.#pendingViews.set(target, new Map());
240
233
  }
241
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
242
- const generalConfig = fields as unknown as ViewFieldsConfig<unknown>;
234
+ const generalConfig: ViewFieldsConfig<unknown> = castTo(fields);
243
235
  this.#pendingViews.get(target)!.set(view, generalConfig);
244
236
  }
245
237
 
@@ -263,6 +255,9 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
263
255
  if (config.specifiers) {
264
256
  config.specifiers = [...params[idx]?.specifiers ?? [], ...config.specifiers];
265
257
  }
258
+ if (config.enum?.values) {
259
+ config.enum.values = config.enum.values.slice().sort();
260
+ }
266
261
 
267
262
  params[idx] = {
268
263
  // @ts-expect-error
@@ -287,8 +282,7 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
287
282
  if (!allViewConf.schema[prop]) {
288
283
  allViewConf.fields.push(prop);
289
284
  // Partial config while building
290
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
291
- allViewConf.schema[prop] = {} as FieldConfig;
285
+ allViewConf.schema[prop] = asFull<FieldConfig>({});
292
286
  }
293
287
  if (config.aliases) {
294
288
  config.aliases = [...allViewConf.schema[prop].aliases ?? [], ...config.aliases];
@@ -296,6 +290,9 @@ class $SchemaRegistry extends MetadataRegistry<ClassConfig, FieldConfig> {
296
290
  if (config.specifiers) {
297
291
  config.specifiers = [...allViewConf.schema[prop].specifiers ?? [], ...config.specifiers];
298
292
  }
293
+ if (config.enum?.values) {
294
+ config.enum.values = config.enum.values.slice().sort();
295
+ }
299
296
 
300
297
  Object.assign(allViewConf.schema[prop], config);
301
298
 
@@ -1,5 +1,4 @@
1
- import { Class } from '@travetto/base';
2
- import { Primitive } from '@travetto/schema';
1
+ import { Any, Class, Primitive } from '@travetto/runtime';
3
2
 
4
3
  import { AllViewⲐ } from '../internal/types';
5
4
  import { ValidatorFn } from '../validate/types';
@@ -66,7 +65,7 @@ export interface ClassConfig extends DescribableConfig {
66
65
  /**
67
66
  * Global validators
68
67
  */
69
- validators: ValidatorFn<unknown, unknown>[];
68
+ validators: ValidatorFn<Any, unknown>[];
70
69
  /**
71
70
  * Is the class a base type
72
71
  */
@@ -1,4 +1,4 @@
1
- import { Class, AppError } from '@travetto/base';
1
+ import { Class, AppError } from '@travetto/runtime';
2
2
  import { ValidationError } from './types';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { TypedObject } from '@travetto/base';
1
+ import { TypedObject } from '@travetto/runtime';
2
2
  import { Messages } from './messages';
3
3
 
4
4
  declare global {
@@ -1,4 +1,4 @@
1
- import { Class, ClassInstance, TypedObject } from '@travetto/base';
1
+ import { castKey, castTo, Class, ClassInstance, TypedObject } from '@travetto/runtime';
2
2
 
3
3
  import { FieldConfig, SchemaConfig } from '../service/types';
4
4
  import { SchemaRegistry } from '../service/registry';
@@ -18,6 +18,14 @@ function resolveSchema<T>(base: Class<T>, o: T, view?: string): SchemaConfig {
18
18
  ).schema;
19
19
  }
20
20
 
21
+ function isClassInstance<T>(o: unknown): o is ClassInstance<T> {
22
+ return !DataUtil.isPlainObject(o) && o !== null && typeof o === 'object' && !!o.constructor;
23
+ }
24
+
25
+ function isRangeValue(o: unknown): o is number | string | Date {
26
+ return typeof o === 'string' || typeof o === 'number' || o instanceof Date;
27
+ }
28
+
21
29
  declare global {
22
30
  interface RegExp {
23
31
  name?: string;
@@ -42,8 +50,7 @@ export class SchemaValidator {
42
50
  const fields = TypedObject.keys<SchemaConfig>(schema);
43
51
  for (const field of fields) {
44
52
  if (schema[field].access !== 'readonly') { // Do not validate readonly fields
45
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
46
- errors = errors.concat(this.#validateFieldSchema(schema[field], o[field as keyof T], relative));
53
+ errors = errors.concat(this.#validateFieldSchema(schema[field], o[castKey<T>(field)], relative));
47
54
  }
48
55
  }
49
56
 
@@ -174,18 +181,15 @@ export class SchemaValidator {
174
181
  criteria.push(['maxlength', field.maxlength]);
175
182
  }
176
183
 
177
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
178
- if (field.enum && !field.enum.values.includes(value as string)) {
184
+ if (field.enum && !field.enum.values.includes(castTo(value))) {
179
185
  criteria.push(['enum', field.enum]);
180
186
  }
181
187
 
182
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
183
- if (field.min && this.#validateRange(field, 'min', value as number)) {
188
+ if (field.min && (!isRangeValue(value) || this.#validateRange(field, 'min', value))) {
184
189
  criteria.push(['min', field.min]);
185
190
  }
186
191
 
187
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
188
- if (field.max && this.#validateRange(field, 'max', value as number)) {
192
+ if (field.max && (!isRangeValue(value) || this.#validateRange(field, 'max', value))) {
189
193
  criteria.push(['max', field.max]);
190
194
  }
191
195
 
@@ -268,10 +272,8 @@ export class SchemaValidator {
268
272
  * @param view The optional view to limit the scope to
269
273
  */
270
274
  static async validate<T>(cls: Class<T>, o: T, view?: string): Promise<T> {
271
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
272
- if (!DataUtil.isPlainObject(o) && !(o instanceof cls || cls.Ⲑid === (o as ClassInstance<T>).constructor.Ⲑid)) {
273
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
274
- throw new TypeMismatchError(cls.name, (o as ClassInstance).constructor.name);
275
+ if (isClassInstance(o) && !(o instanceof cls || cls.Ⲑid === o.constructor.Ⲑid)) {
276
+ throw new TypeMismatchError(cls.name, o.constructor.name);
275
277
  }
276
278
  cls = SchemaRegistry.resolveInstanceType(cls, o);
277
279
 
@@ -1,12 +1,13 @@
1
1
  import ts from 'typescript';
2
2
  import {
3
3
  type AnyType, DeclarationUtil, LiteralUtil,
4
- DecoratorUtil, DocUtil, ParamDocumentation, TransformerState
4
+ DecoratorUtil, DocUtil, ParamDocumentation, TransformerState, transformCast
5
5
  } from '@travetto/transformer';
6
6
 
7
7
  const SCHEMA_MOD = '@travetto/schema/src/decorator/schema';
8
8
  const FIELD_MOD = '@travetto/schema/src/decorator/field';
9
9
  const COMMON_MOD = '@travetto/schema/src/decorator/common';
10
+ const TYPES_FILE = '@travetto/schema/src/internal/types';
10
11
 
11
12
  export class SchemaTransformUtil {
12
13
 
@@ -27,6 +28,10 @@ export class SchemaTransformUtil {
27
28
  }
28
29
  break;
29
30
  }
31
+ case 'unknown': {
32
+ const imp = state.importFile(TYPES_FILE);
33
+ return state.createAccess(imp.ident, 'UnknownType');
34
+ }
30
35
  case 'shape': {
31
36
  const uniqueId = state.generateUniqueIdentifier(node, type);
32
37
 
@@ -49,11 +54,16 @@ export class SchemaTransformUtil {
49
54
  this.computeField(state, state.factory.createPropertyDeclaration(
50
55
  [], k,
51
56
  v.undefinable || v.nullable ? state.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined,
52
- undefined, undefined
57
+ v.key === 'unknown' ? state.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword) : undefined, undefined
53
58
  ), { type: v, root })
54
59
  )
55
60
  );
56
- cls.getText = (): string => '';
61
+ cls.getText = (): string => [
62
+ `class ${uniqueId} {`,
63
+ ...Object.entries(type.fieldTypes)
64
+ .map(([k, v]) => ` ${k}${v.nullable ? '?' : ''}: ${v.name};`),
65
+ '}'
66
+ ].join('\n');
57
67
  state.addStatements([cls], root || node);
58
68
  }
59
69
  return id;
@@ -65,7 +75,6 @@ export class SchemaTransformUtil {
65
75
  break;
66
76
  }
67
77
  case 'foreign':
68
- case 'unknown':
69
78
  default: {
70
79
  // Object
71
80
  }
@@ -121,8 +130,7 @@ export class SchemaTransformUtil {
121
130
  // If we have a union type
122
131
  if (primaryExpr.key === 'union') {
123
132
  const values = primaryExpr.subTypes.map(x => x.key === 'literal' ? x.value : undefined)
124
- .filter(x => x !== undefined && x !== null)
125
- .sort();
133
+ .filter(x => x !== undefined && x !== null);
126
134
 
127
135
  if (values.length === primaryExpr.subTypes.length) {
128
136
  attrs.push(state.factory.createPropertyAssignment('enum', state.fromLiteral({
@@ -178,6 +186,7 @@ export class SchemaTransformUtil {
178
186
  state.createDecorator(FIELD_MOD, 'Field', ...params)
179
187
  ];
180
188
 
189
+ let ret: unknown;
181
190
  if (ts.isPropertyDeclaration(node)) {
182
191
  const comments = DocUtil.describeDocs(node);
183
192
  if (comments.description) {
@@ -186,22 +195,19 @@ export class SchemaTransformUtil {
186
195
  })));
187
196
  }
188
197
 
189
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
190
- return state.factory.updatePropertyDeclaration(node as Exclude<typeof node, T>,
191
- newModifiers, node.name, node.questionToken, node.type, node.initializer) as T;
198
+ ret = state.factory.updatePropertyDeclaration(node,
199
+ newModifiers, node.name, node.questionToken, node.type, node.initializer);
192
200
  } else if (ts.isParameter(node)) {
193
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
194
- return state.factory.updateParameterDeclaration(node as Exclude<typeof node, T>,
195
- newModifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) as T;
201
+ ret = state.factory.updateParameterDeclaration(node,
202
+ newModifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer);
196
203
  } else if (ts.isGetAccessorDeclaration(node)) {
197
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
198
- return state.factory.updateGetAccessorDeclaration(node as Exclude<typeof node, T>,
199
- newModifiers, node.name, node.parameters, node.type, node.body) as T;
204
+ ret = state.factory.updateGetAccessorDeclaration(node,
205
+ newModifiers, node.name, node.parameters, node.type, node.body);
200
206
  } else {
201
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
202
- return state.factory.updateSetAccessorDeclaration(node as Exclude<typeof node, T>,
203
- newModifiers, node.name, node.parameters, node.body) as T;
207
+ ret = state.factory.updateSetAccessorDeclaration(node,
208
+ newModifiers, node.name, node.parameters, node.body);
204
209
  }
210
+ return transformCast(ret);
205
211
  }
206
212
 
207
213
  /**
package/src/types.ts DELETED
@@ -1,7 +0,0 @@
1
- export type Primitive = number | bigint | boolean | string | Date;
2
-
3
- export type DeepPartial<T> = {
4
- [P in keyof T]?: (T[P] extends (Primitive | undefined) ? (T[P] | undefined) :
5
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
- (T[P] extends any[] ? (DeepPartial<T[P][number]> | null | undefined)[] : DeepPartial<T[P]>));
7
- };