@travetto/schema 8.0.0-alpha.2 → 8.0.0-alpha.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/schema",
3
- "version": "8.0.0-alpha.2",
3
+ "version": "8.0.0-alpha.20",
4
4
  "type": "module",
5
5
  "description": "Data type registry for runtime validation, reflection and binding.",
6
6
  "keywords": [
@@ -28,10 +28,10 @@
28
28
  "directory": "module/schema"
29
29
  },
30
30
  "dependencies": {
31
- "@travetto/registry": "^8.0.0-alpha.2"
31
+ "@travetto/registry": "^8.0.0-alpha.19"
32
32
  },
33
33
  "peerDependencies": {
34
- "@travetto/transformer": "^8.0.0-alpha.2"
34
+ "@travetto/transformer": "^8.0.0-alpha.13"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@travetto/transformer": {
package/src/bind-util.ts CHANGED
@@ -20,6 +20,16 @@ function isInstance<T>(value: unknown): value is T {
20
20
  */
21
21
  export class BindUtil {
22
22
 
23
+ /**
24
+ * Utility to make a property accessor enumerable at runtime
25
+ */
26
+ static registerAccessor(instance: any, property: string): void {
27
+ Object.defineProperty(instance, property, {
28
+ ...Object.getOwnPropertyDescriptor(Object.getPrototypeOf(instance), property),
29
+ enumerable: true
30
+ });
31
+ }
32
+
23
33
  /**
24
34
  * Coerce a value to match the field config type
25
35
  * @param config The field config to coerce to
@@ -153,6 +163,10 @@ export class BindUtil {
153
163
  const instance = classConstruct<T & { type?: string }>(resolvedCls);
154
164
 
155
165
  for (const key of TypedObject.keys(instance)) { // Do not retain undefined fields
166
+ const descriptor = Object.getOwnPropertyDescriptor(instance, key);
167
+ if (descriptor?.get) {
168
+ continue;
169
+ }
156
170
  if (instance[key] === undefined) {
157
171
  delete instance[key];
158
172
  }
@@ -244,13 +258,6 @@ export class BindUtil {
244
258
  }
245
259
 
246
260
  input[castKey<T>(schemaFieldName)] = castTo(value);
247
-
248
- if (field.accessor) {
249
- Object.defineProperty(input, schemaFieldName, {
250
- ...adapter.getAccessorDescriptor(schemaFieldName),
251
- enumerable: true
252
- });
253
- }
254
261
  }
255
262
  }
256
263
  }
@@ -4,7 +4,7 @@ import { RuntimeError, BinaryUtil, castKey, castTo, type Class, describeFunction
4
4
  import {
5
5
  type SchemaClassConfig, type SchemaMethodConfig, type SchemaFieldConfig,
6
6
  type SchemaParameterConfig, type SchemaInputConfig, type SchemaFieldMap, type SchemaCoreConfig,
7
- type SchemaBasicType, CONSTRUCTOR_PROPERTY
7
+ CONSTRUCTOR_PROPERTY
8
8
  } from './types.ts';
9
9
 
10
10
  export type SchemaDiscriminatedInfo = Required<Pick<SchemaClassConfig, 'discriminatedType' | 'discriminatedField' | 'discriminatedBase'>>;
@@ -23,19 +23,13 @@ function assignMetadata<T>(key: symbol, base: SchemaCoreConfig, data: Partial<T>
23
23
  return castTo(out);
24
24
  }
25
25
 
26
- function combineCore<T extends SchemaCoreConfig>(base: T, config: Partial<T>): T {
27
- return safeAssign(base, {
26
+ function combineCore<T extends SchemaCoreConfig>(base: T, config: Partial<T>): Pick<Partial<T>, 'metadata' | 'private' | 'description' | 'examples'> {
27
+ return {
28
28
  ...config.metadata ? { metadata: { ...base.metadata, ...config.metadata } } : {},
29
29
  ...config.private ? { private: config.private ?? base.private } : {},
30
30
  ...config.description ? { description: config.description || base.description } : {},
31
31
  ...config.examples ? { examples: [...(base.examples ?? []), ...(config.examples ?? [])] } : {},
32
- });
33
- }
34
-
35
- function ensureBinary<T extends SchemaBasicType>(config?: T): void {
36
- if (config?.type) {
37
- config.binary = BinaryUtil.isBinaryTypeReference(config.type);
38
- }
32
+ };
39
33
  }
40
34
 
41
35
  function combineInputs<T extends SchemaInputConfig>(base: T, configs: Partial<T>[]): T {
@@ -51,10 +45,9 @@ function combineInputs<T extends SchemaInputConfig>(base: T, configs: Partial<T>
51
45
  values: (config.enum?.values ?? base.enum?.values ?? []).toSorted()
52
46
  }
53
47
  } : {},
48
+ ...combineCore(base, config)
54
49
  });
55
50
  }
56
- combineCore(base, config);
57
- ensureBinary(base);
58
51
  }
59
52
  return base;
60
53
  }
@@ -63,16 +56,15 @@ function combineMethods<T extends SchemaMethodConfig>(base: T, configs: Partial<
63
56
  for (const config of configs) {
64
57
  safeAssign(base, {
65
58
  ...config,
59
+ ...combineCore(base, config),
66
60
  parameters: config.parameters ?? base.parameters,
67
61
  validators: [...base.validators, ...(config.validators ?? [])],
68
62
  });
69
- combineCore(base, config);
70
63
  if (config.parameters) {
71
64
  for (const param of config.parameters) {
72
65
  safeAssign(base.parameters[param.index], param);
73
66
  }
74
67
  }
75
- ensureBinary(config.returnType);
76
68
  }
77
69
  return base;
78
70
  }
@@ -137,11 +129,11 @@ function combineClasses<T extends SchemaClassConfig>(base: T, configs: Partial<T
137
129
  ...config,
138
130
  ...config.views ? { views: { ...base.views, ...config.views } } : {},
139
131
  ...config.validators ? { validators: [...base.validators, ...config.validators] } : {},
132
+ ...combineCore(base, config),
140
133
  interfaces: [...base.interfaces, ...(config.interfaces ?? [])],
141
134
  methods: { ...base.methods, ...config.methods },
142
135
  fields: { ...base.fields, ...config.fields },
143
136
  });
144
- combineCore(base, config);
145
137
  }
146
138
  return base;
147
139
  }
@@ -284,6 +276,38 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
284
276
 
285
277
  for (const method of Object.values(config.methods)) {
286
278
  method.parameters = method.parameters.toSorted((a, b) => (a.index! - b.index!));
279
+ if (method.returnType?.type) {
280
+ method.returnType.binary = BinaryUtil.isBinaryTypeReference(method.returnType.type);
281
+ }
282
+ for (const input of method.parameters) {
283
+ if (input.type) {
284
+ input.binary = BinaryUtil.isBinaryTypeReference(input.type);
285
+ }
286
+ }
287
+ }
288
+
289
+ // Validate if aliases are duplicated
290
+ const aliasMap: Record<string, SchemaFieldConfig> = {};
291
+ for (const field of Object.values(config.fields)) {
292
+ if (field.type) {
293
+ field.binary = BinaryUtil.isBinaryTypeReference(field.type);
294
+ }
295
+ for (const alias of field.aliases ?? []) {
296
+ if (alias in aliasMap && aliasMap[alias].name !== field.name) {
297
+ throw new RuntimeError(
298
+ `Alias ${alias} has been specified multiple times ${this.#cls.name}#(${field.name},${aliasMap[alias].name})`,
299
+ {
300
+ details: {
301
+ alias,
302
+ class: this.#cls.name,
303
+ field1: field.name,
304
+ field2: aliasMap[alias].name
305
+ }
306
+ }
307
+ );
308
+ }
309
+ aliasMap[alias] = field;
310
+ }
287
311
  }
288
312
  }
289
313
 
@@ -232,7 +232,7 @@ export interface SchemaFieldConfig extends SchemaInputConfig {
232
232
  /**
233
233
  * Is this field a getter or setter
234
234
  */
235
- accessor?: string;
235
+ accessor?: boolean;
236
236
  }
237
237
 
238
238
  export type ViewFieldsConfig<T> = { with: Extract<(keyof T), string>[] } | { without: Extract<(keyof T), string>[] };
@@ -199,13 +199,13 @@ export class SchemaValidator {
199
199
  delete error.regex;
200
200
  }
201
201
 
202
- const msg = result.message ?? (
202
+ const message = result.message ?? (
203
203
  Messages.get(error.regex ?? '') ??
204
204
  Messages.get(error.kind) ??
205
205
  Messages.get('default')!
206
206
  );
207
207
 
208
- error.message = msg
208
+ error.message = message
209
209
  .replace(/\{([^}]+)\}/g, (_, key: (keyof ValidationError)) => `${error[key]}`);
210
210
 
211
211
  out.push(error);
@@ -14,6 +14,7 @@ export class SchemaTransformUtil {
14
14
  static INPUT_IMPORT = '@travetto/schema/src/decorator/input.ts';
15
15
  static COMMON_IMPORT = '@travetto/schema/src/decorator/common.ts';
16
16
  static TYPES_IMPORT = '@travetto/schema/src/types.ts';
17
+ static BIND_UTIL_IMPORT = '@travetto/schema/src/bind-util.ts';
17
18
 
18
19
  /**
19
20
  * Produce concrete type given transformer type
@@ -40,7 +41,8 @@ export class SchemaTransformUtil {
40
41
  const cls = state.factory.createClassDeclaration(
41
42
  [
42
43
  state.createDecorator(this.SCHEMA_IMPORT, 'Schema', state.fromLiteral({
43
- description: type.comment,
44
+
45
+ description: type.description,
44
46
  mappedOperation: type.operation,
45
47
  mappedFields: type.fields,
46
48
  })),
@@ -71,10 +73,16 @@ class ${uniqueId} extends ${type.mappedClassName} {
71
73
  const cls = state.factory.createClassDeclaration(
72
74
  [
73
75
  state.createDecorator(this.SCHEMA_IMPORT, 'Schema', state.fromLiteral({
74
- description: type.comment
76
+ description: type.description
75
77
  })),
76
78
  ],
77
- id, [], [],
79
+ id,
80
+ [],
81
+ type.extendsFrom ? [
82
+ state.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
83
+ state.factory.createExpressionWithTypeArguments(state.getOrImport(type.extendsFrom), [])
84
+ ])
85
+ ] : [],
78
86
  Object.entries(type.fieldTypes)
79
87
  .map(([key, value]) =>
80
88
  this.computeInput(state, state.factory.createPropertyDeclaration(
@@ -350,13 +358,29 @@ class ${uniqueId} extends ${type.mappedClassName} {
350
358
  targetType = returnType.typeArguments[0];
351
359
  }
352
360
 
353
- // TODO: Standardize this using jsdoc
354
361
  let innerReturnType: AnyType | undefined;
355
- if (targetType.key === 'managed' && targetType.importName.startsWith('@travetto/')) {
356
- innerReturnType = state.getApparentTypeOfField(targetType.original!, 'body');
362
+ if (targetType.key === 'managed' && targetType.innerTypeProperty) {
363
+ innerReturnType = state.getApparentTypeOfField(targetType.original!, targetType.innerTypeProperty);
357
364
  }
358
365
 
359
366
  const finalReturnType = SchemaTransformUtil.ensureType(state, innerReturnType ?? returnType, node);
360
367
  return finalReturnType ? [state.fromLiteral({ returnType: finalReturnType })] : [];
361
368
  }
369
+
370
+ /**
371
+ * Helper to create inline defineProperty statement for accessors
372
+ */
373
+ static createAccessorDefineProperty(state: TransformerState, accessorName: string): ts.Statement {
374
+ const bindUtilImport = state.importFile(this.BIND_UTIL_IMPORT);
375
+ return state.factory.createExpressionStatement(
376
+ state.factory.createCallExpression(
377
+ state.createAccess(bindUtilImport.identifier, 'BindUtil', 'registerAccessor'),
378
+ undefined,
379
+ [
380
+ state.factory.createThis(),
381
+ state.factory.createStringLiteral(accessorName)
382
+ ]
383
+ )
384
+ );
385
+ }
362
386
  }
@@ -6,6 +6,7 @@ const CONSTRUCTOR_PROPERTY = 'CONSTRUCTOR';
6
6
  const InSchema = Symbol();
7
7
  const IsOptIn = Symbol();
8
8
  const AccessorsSymbol = Symbol();
9
+ const GettersSymbol = Symbol();
9
10
  const AutoEnrollMethods = Symbol();
10
11
 
11
12
  interface AutoState {
@@ -13,6 +14,7 @@ interface AutoState {
13
14
  [IsOptIn]?: boolean;
14
15
  [AutoEnrollMethods]?: Set<string>;
15
16
  [AccessorsSymbol]?: Set<string>;
17
+ [GettersSymbol]?: Set<string>;
16
18
  }
17
19
 
18
20
  /**
@@ -64,6 +66,7 @@ export class SchemaTransformer {
64
66
  */
65
67
  static startSchema(state: AutoState & TransformerState, node: ts.ClassDeclaration): ts.ClassDeclaration {
66
68
  state[AccessorsSymbol] = new Set();
69
+ state[GettersSymbol] = new Set();
67
70
  state[AutoEnrollMethods] = new Set();
68
71
  state[InSchema] = true;
69
72
 
@@ -90,10 +93,11 @@ export class SchemaTransformer {
90
93
  const existing = state.findDecorator(this, node, 'Schema', SchemaTransformUtil.SCHEMA_IMPORT);
91
94
  const cons = node.members.find(member => ts.isConstructorDeclaration(member));
92
95
 
93
- const attrs: Record<string, string | boolean | ts.Expression | number | object | unknown[]> = {};
96
+ const attrs: Record<string, string | boolean | ts.Expression | number | object | unknown[] | undefined> = {};
94
97
 
95
98
  if (comments.description) {
96
99
  attrs.description = comments.description;
100
+ attrs.examples = comments.examples;
97
101
  }
98
102
 
99
103
  // Extract all interfaces
@@ -129,9 +133,40 @@ export class SchemaTransformer {
129
133
  params = [...params, state.fromLiteral(attrs)];
130
134
  }
131
135
 
136
+ let newMembers = node.members;
137
+ const accessors = state[GettersSymbol] ? [...state[GettersSymbol]] : [];
138
+ if (accessors.length > 0) {
139
+ node = DeclarationUtil.ensureConstructor(state.factory, node);
140
+
141
+ const accessorStatements = accessors.map(accessorName =>
142
+ SchemaTransformUtil.createAccessorDefineProperty(state, accessorName)
143
+ );
144
+
145
+ const consIndex = node.members.findIndex(member => ts.isConstructorDeclaration(member));
146
+ const consNode = node.members[consIndex] as ts.ConstructorDeclaration;
147
+ const insertIndex = DeclarationUtil.getConstructorInsertIndex(consNode);
148
+ const newStatements = [
149
+ ...consNode.body?.statements.slice(0, insertIndex) ?? [],
150
+ ...accessorStatements,
151
+ ...consNode.body?.statements.slice(insertIndex) ?? []
152
+ ];
153
+ const updatedCons = state.factory.updateConstructorDeclaration(
154
+ consNode,
155
+ consNode.modifiers,
156
+ consNode.parameters,
157
+ state.factory.createBlock(newStatements, true)
158
+ );
159
+ newMembers = ts.factory.createNodeArray([
160
+ ...node.members.slice(0, consIndex),
161
+ updatedCons,
162
+ ...node.members.slice(consIndex + 1)
163
+ ]);
164
+ }
165
+
132
166
  delete state[InSchema];
133
167
  delete state[IsOptIn];
134
168
  delete state[AccessorsSymbol];
169
+ delete state[GettersSymbol];
135
170
  delete state[AutoEnrollMethods];
136
171
 
137
172
  return state.factory.updateClassDeclaration(
@@ -142,7 +177,7 @@ export class SchemaTransformer {
142
177
  node.name,
143
178
  node.typeParameters,
144
179
  node.heritageClauses,
145
- node.members
180
+ newMembers
146
181
  );
147
182
  }
148
183
 
@@ -200,6 +235,7 @@ export class SchemaTransformer {
200
235
  if (this.isInvisible(state, node) || DeclarationUtil.isStatic(node)) {
201
236
  return node;
202
237
  }
238
+ state[GettersSymbol]?.add(node.name.getText());
203
239
  if (state[AccessorsSymbol]?.has(node.name.getText())) {
204
240
  return node;
205
241
  } else {