@travetto/schema 8.0.0-alpha.19 → 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.19",
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.18"
31
+ "@travetto/registry": "^8.0.0-alpha.19"
32
32
  },
33
33
  "peerDependencies": {
34
- "@travetto/transformer": "^8.0.0-alpha.12"
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
  }
@@ -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>[] };
@@ -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
@@ -365,4 +366,21 @@ class ${uniqueId} extends ${type.mappedClassName} {
365
366
  const finalReturnType = SchemaTransformUtil.ensureType(state, innerReturnType ?? returnType, node);
366
367
  return finalReturnType ? [state.fromLiteral({ returnType: finalReturnType })] : [];
367
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
+ }
368
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
 
@@ -130,9 +133,40 @@ export class SchemaTransformer {
130
133
  params = [...params, state.fromLiteral(attrs)];
131
134
  }
132
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
+
133
166
  delete state[InSchema];
134
167
  delete state[IsOptIn];
135
168
  delete state[AccessorsSymbol];
169
+ delete state[GettersSymbol];
136
170
  delete state[AutoEnrollMethods];
137
171
 
138
172
  return state.factory.updateClassDeclaration(
@@ -143,7 +177,7 @@ export class SchemaTransformer {
143
177
  node.name,
144
178
  node.typeParameters,
145
179
  node.heritageClauses,
146
- node.members
180
+ newMembers
147
181
  );
148
182
  }
149
183
 
@@ -201,6 +235,7 @@ export class SchemaTransformer {
201
235
  if (this.isInvisible(state, node) || DeclarationUtil.isStatic(node)) {
202
236
  return node;
203
237
  }
238
+ state[GettersSymbol]?.add(node.name.getText());
204
239
  if (state[AccessorsSymbol]?.has(node.name.getText())) {
205
240
  return node;
206
241
  } else {