@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.
|
|
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.
|
|
31
|
+
"@travetto/registry": "^8.0.0-alpha.19"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@travetto/transformer": "^8.0.0-alpha.
|
|
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
|
-
|
|
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
|
|
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
|
|
package/src/service/types.ts
CHANGED
|
@@ -232,7 +232,7 @@ export interface SchemaFieldConfig extends SchemaInputConfig {
|
|
|
232
232
|
/**
|
|
233
233
|
* Is this field a getter or setter
|
|
234
234
|
*/
|
|
235
|
-
accessor?:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
356
|
-
innerReturnType = state.getApparentTypeOfField(targetType.original!,
|
|
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
|
-
|
|
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 {
|