typedapi-client-helpers 0.3.2 → 0.3.4
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/CHANGELOG.md +31 -0
- package/README.md +65 -16
- package/package.json +1 -1
- package/scripts/openapi-generator/index.mjs +1 -1
- package/scripts/openapi-generator/openapi-utils.mjs +412 -411
- package/scripts/openapi-generator/schema.mjs +470 -38
|
@@ -42,6 +42,20 @@ function intersectionTypes(types, fallback = "Record<string, unknown>") {
|
|
|
42
42
|
return unique.length > 0 ? unique.join(" & ") : fallback;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function escapeRegExp(value) {
|
|
46
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function genericDeclarationBody(declaration) {
|
|
50
|
+
if (declaration.startsWith("export interface ")) {
|
|
51
|
+
const bodyStart = declaration.indexOf("{");
|
|
52
|
+
return bodyStart >= 0 ? declaration.slice(bodyStart + 1) : "";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const bodyStart = declaration.indexOf("=");
|
|
56
|
+
return bodyStart >= 0 ? declaration.slice(bodyStart + 1) : "";
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
function hasObjectShape(schema) {
|
|
46
60
|
return Boolean(
|
|
47
61
|
schema?.properties ||
|
|
@@ -55,6 +69,170 @@ function schemaWithoutComposition(schema) {
|
|
|
55
69
|
return local;
|
|
56
70
|
}
|
|
57
71
|
|
|
72
|
+
|
|
73
|
+
function normalizeGenericBindings(bindings, parameters, contextName) {
|
|
74
|
+
const normalized = Array.isArray(bindings)
|
|
75
|
+
? bindings
|
|
76
|
+
.map((binding) => ({
|
|
77
|
+
parameter: String(binding?.parameter ?? "").trim(),
|
|
78
|
+
path: String(binding?.path ?? "").trim(),
|
|
79
|
+
}))
|
|
80
|
+
.filter((binding) => binding.parameter && binding.path.startsWith("/"))
|
|
81
|
+
: [];
|
|
82
|
+
for (const binding of normalized) {
|
|
83
|
+
if (!parameters.includes(binding.parameter)) {
|
|
84
|
+
throw new Error(`${contextName} binding ${binding.path} refers to unknown parameter ${binding.parameter}.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return normalized;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeGenericArgument(argument, parameters, contextName) {
|
|
91
|
+
if (!argument || typeof argument !== "object") {
|
|
92
|
+
throw new Error(`${contextName} contains an invalid generic argument.`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const genericParameter = String(argument.genericParameter ?? "").trim();
|
|
96
|
+
const primitive = String(argument.primitive ?? "").trim();
|
|
97
|
+
const schemaId = String(argument.schemaId ?? "").trim();
|
|
98
|
+
const populated = [genericParameter, primitive, schemaId].filter(Boolean);
|
|
99
|
+
if (populated.length !== 1) {
|
|
100
|
+
throw new Error(`${contextName} generic arguments must specify exactly one of genericParameter, primitive, or schemaId.`);
|
|
101
|
+
}
|
|
102
|
+
if (genericParameter && !parameters.includes(genericParameter)) {
|
|
103
|
+
throw new Error(`${contextName} refers to unknown generic parameter ${genericParameter}.`);
|
|
104
|
+
}
|
|
105
|
+
if (genericParameter) return { genericParameter };
|
|
106
|
+
if (primitive) return { primitive };
|
|
107
|
+
return { schemaId };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizeGenericBase(base, ownerParameters, contextName) {
|
|
111
|
+
if (!base || typeof base !== "object") return undefined;
|
|
112
|
+
const definition = String(base.definition ?? "").trim();
|
|
113
|
+
const parameters = Array.isArray(base.parameters)
|
|
114
|
+
? base.parameters.map((item) => String(item)).filter(Boolean)
|
|
115
|
+
: [];
|
|
116
|
+
const rawArguments = Array.isArray(base.arguments) ? base.arguments : [];
|
|
117
|
+
const properties = Array.isArray(base.properties)
|
|
118
|
+
? [...new Set(base.properties.map((item) => String(item)).filter(Boolean))]
|
|
119
|
+
: [];
|
|
120
|
+
if (!definition || parameters.length === 0 || parameters.length !== rawArguments.length) {
|
|
121
|
+
throw new Error(`Invalid generic base metadata for ${contextName}.`);
|
|
122
|
+
}
|
|
123
|
+
const argumentsList = rawArguments.map((argument) =>
|
|
124
|
+
normalizeGenericArgument(argument, ownerParameters, `Generic base ${definition}`),
|
|
125
|
+
);
|
|
126
|
+
const bindings = normalizeGenericBindings(
|
|
127
|
+
base.bindings,
|
|
128
|
+
parameters,
|
|
129
|
+
`Generic base ${definition}`,
|
|
130
|
+
);
|
|
131
|
+
return {
|
|
132
|
+
definition,
|
|
133
|
+
parameters,
|
|
134
|
+
arguments: argumentsList,
|
|
135
|
+
properties,
|
|
136
|
+
bindings,
|
|
137
|
+
base: normalizeGenericBase(base.base, parameters, definition),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function genericMetadata(schema) {
|
|
142
|
+
const metadata = schema?.["x-typedapi-generic"];
|
|
143
|
+
if (!metadata || typeof metadata !== "object") return undefined;
|
|
144
|
+
const definition = String(metadata.definition ?? "").trim();
|
|
145
|
+
const parameters = Array.isArray(metadata.parameters)
|
|
146
|
+
? metadata.parameters.map((item) => String(item)).filter(Boolean)
|
|
147
|
+
: [];
|
|
148
|
+
const argumentsList = Array.isArray(metadata.arguments) ? metadata.arguments : [];
|
|
149
|
+
if (!definition || parameters.length === 0 || parameters.length !== argumentsList.length) {
|
|
150
|
+
throw new Error(`Invalid x-typedapi-generic metadata for ${definition || "an unnamed schema"}.`);
|
|
151
|
+
}
|
|
152
|
+
const bindings = normalizeGenericBindings(metadata.bindings, parameters, `Generic schema ${definition}`);
|
|
153
|
+
return {
|
|
154
|
+
definition,
|
|
155
|
+
parameters,
|
|
156
|
+
arguments: argumentsList,
|
|
157
|
+
bindings,
|
|
158
|
+
base: normalizeGenericBase(metadata.base, parameters, definition),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function decodeJsonPointerPart(value) {
|
|
163
|
+
return value.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function cloneSchemaWithGenericBindings(schema, metadata) {
|
|
167
|
+
const clone = structuredClone(schema);
|
|
168
|
+
for (const binding of metadata.bindings ?? []) {
|
|
169
|
+
const parts = binding.path.split("/").slice(1).map(decodeJsonPointerPart);
|
|
170
|
+
if (parts.length === 0) continue;
|
|
171
|
+
let parent = clone;
|
|
172
|
+
for (const part of parts.slice(0, -1)) {
|
|
173
|
+
parent = parent?.[part];
|
|
174
|
+
if (!parent || typeof parent !== "object") break;
|
|
175
|
+
}
|
|
176
|
+
if (!parent || typeof parent !== "object") continue;
|
|
177
|
+
const finalPart = parts.at(-1);
|
|
178
|
+
if (!Object.prototype.hasOwnProperty.call(parent, finalPart)) continue;
|
|
179
|
+
parent[finalPart] = { "x-typedapi-generic-parameter": binding.parameter };
|
|
180
|
+
}
|
|
181
|
+
return clone;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function copySelectedProperties(schema, selectedProperties) {
|
|
185
|
+
const selected = new Set(selectedProperties ?? []);
|
|
186
|
+
const properties = Object.fromEntries(
|
|
187
|
+
Object.entries(schema?.properties ?? {}).filter(([name]) => selected.has(name)),
|
|
188
|
+
);
|
|
189
|
+
const required = (schema?.required ?? []).filter((name) => selected.has(name));
|
|
190
|
+
const result = {
|
|
191
|
+
...schemaWithoutComposition(schema),
|
|
192
|
+
type: "object",
|
|
193
|
+
properties,
|
|
194
|
+
};
|
|
195
|
+
delete result["x-typedapi-generic"];
|
|
196
|
+
if (required.length > 0) result.required = required;
|
|
197
|
+
else delete result.required;
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function removeSelectedProperties(schema, selectedProperties) {
|
|
202
|
+
const selected = new Set(selectedProperties ?? []);
|
|
203
|
+
const result = structuredClone(schemaWithoutComposition(schema));
|
|
204
|
+
result.properties = Object.fromEntries(
|
|
205
|
+
Object.entries(result.properties ?? {}).filter(([name]) => !selected.has(name)),
|
|
206
|
+
);
|
|
207
|
+
result.required = (result.required ?? []).filter((name) => !selected.has(name));
|
|
208
|
+
delete result["x-typedapi-generic"];
|
|
209
|
+
if (Object.keys(result.properties).length === 0) delete result.properties;
|
|
210
|
+
if (result.required.length === 0) delete result.required;
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function hasOwnObjectMembers(schema) {
|
|
215
|
+
return Object.keys(schema?.properties ?? {}).length > 0 || Boolean(schema?.additionalProperties);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function discriminatorMappings(schema) {
|
|
219
|
+
const discriminator = schema?.discriminator;
|
|
220
|
+
if (!discriminator?.propertyName) return [];
|
|
221
|
+
const mapping = discriminator.mapping ?? {};
|
|
222
|
+
const entries = Object.entries(mapping)
|
|
223
|
+
.filter(([, ref]) => typeof ref === "string" && ref.includes("/"))
|
|
224
|
+
.map(([value, ref]) => ({ propertyName: discriminator.propertyName, value, rawName: refName(ref) }));
|
|
225
|
+
if (entries.length > 0) return entries;
|
|
226
|
+
|
|
227
|
+
return (schema.oneOf ?? [])
|
|
228
|
+
.filter((item) => item?.$ref)
|
|
229
|
+
.map((item) => ({
|
|
230
|
+
propertyName: discriminator.propertyName,
|
|
231
|
+
value: refName(item.$ref),
|
|
232
|
+
rawName: refName(item.$ref),
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
|
|
58
236
|
function assertUniqueTypePropertyNames(entries, contextName) {
|
|
59
237
|
const seen = new Map();
|
|
60
238
|
for (const rawName of entries) {
|
|
@@ -77,24 +255,149 @@ export class TypeResolver {
|
|
|
77
255
|
this.options = options;
|
|
78
256
|
this.inlineTypes = new Map();
|
|
79
257
|
this.componentNames = new Map();
|
|
258
|
+
this.genericSchemas = new Map();
|
|
259
|
+
this.genericDefinitions = new Map();
|
|
260
|
+
this.discriminatorProperties = new Map();
|
|
261
|
+
this.polymorphicBases = new Map();
|
|
262
|
+
const schemas = openApi.components?.schemas ?? {};
|
|
80
263
|
const usedNames = new Set();
|
|
81
264
|
|
|
82
|
-
|
|
265
|
+
const registerGenericDefinition = (normalized) => {
|
|
266
|
+
const existing = this.genericDefinitions.get(normalized.definitionName);
|
|
267
|
+
if (existing && existing.parameters.join("|") !== normalized.parameters.join("|")) {
|
|
268
|
+
throw new Error(`Generic schemas for ${normalized.definitionName} use inconsistent type parameters.`);
|
|
269
|
+
}
|
|
270
|
+
if (existing && JSON.stringify(existing.bindings) !== JSON.stringify(normalized.bindings)) {
|
|
271
|
+
throw new Error(`Generic schemas for ${normalized.definitionName} use inconsistent generic bindings.`);
|
|
272
|
+
}
|
|
273
|
+
if (existing && JSON.stringify(existing.base ?? null) !== JSON.stringify(normalized.base ?? null)) {
|
|
274
|
+
throw new Error(`Generic schemas for ${normalized.definitionName} use inconsistent generic inheritance metadata.`);
|
|
275
|
+
}
|
|
276
|
+
if (!existing || (existing.synthetic && !normalized.synthetic)) {
|
|
277
|
+
this.genericDefinitions.set(normalized.definitionName, normalized);
|
|
278
|
+
}
|
|
279
|
+
usedNames.add(normalized.definitionName);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
for (const [rawName, schema] of Object.entries(schemas)) {
|
|
283
|
+
const metadata = genericMetadata(schema);
|
|
284
|
+
if (!metadata) continue;
|
|
285
|
+
const definitionName = operationTypeName(metadata.definition);
|
|
286
|
+
const normalized = { ...metadata, definitionName, rawName, synthetic: false };
|
|
287
|
+
this.genericSchemas.set(rawName, normalized);
|
|
288
|
+
registerGenericDefinition(normalized);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const resolveConcreteBaseArguments = (base, ownerMetadata) => {
|
|
292
|
+
const ownerArguments = new Map(
|
|
293
|
+
ownerMetadata.parameters.map((parameter, index) => [parameter, ownerMetadata.arguments[index]]),
|
|
294
|
+
);
|
|
295
|
+
return base.arguments.map((argument, index) => {
|
|
296
|
+
const concrete = argument.genericParameter
|
|
297
|
+
? ownerArguments.get(argument.genericParameter)
|
|
298
|
+
: argument;
|
|
299
|
+
if (!concrete) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Generic base ${base.definition} could not resolve argument ${base.parameters[index]}.`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const { parameter: _ignoredParameter, ...resolvedArgument } = concrete;
|
|
305
|
+
return { parameter: base.parameters[index], ...resolvedArgument };
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const registerSyntheticBases = (ownerMetadata, sourceRawName) => {
|
|
310
|
+
const base = ownerMetadata.base;
|
|
311
|
+
if (!base) return;
|
|
312
|
+
const definitionName = operationTypeName(base.definition);
|
|
313
|
+
const normalized = {
|
|
314
|
+
definition: base.definition,
|
|
315
|
+
definitionName,
|
|
316
|
+
parameters: base.parameters,
|
|
317
|
+
arguments: resolveConcreteBaseArguments(base, ownerMetadata),
|
|
318
|
+
bindings: base.bindings,
|
|
319
|
+
base: base.base,
|
|
320
|
+
rawName: sourceRawName,
|
|
321
|
+
synthetic: true,
|
|
322
|
+
syntheticProperties: base.properties,
|
|
323
|
+
};
|
|
324
|
+
registerGenericDefinition(normalized);
|
|
325
|
+
registerSyntheticBases(normalized, sourceRawName);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
for (const metadata of this.genericSchemas.values()) {
|
|
329
|
+
registerSyntheticBases(metadata, metadata.rawName);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
for (const rawName of Object.keys(schemas)) {
|
|
333
|
+
if (this.genericSchemas.has(rawName)) continue;
|
|
83
334
|
const name = uniqueName(operationTypeName(rawName), usedNames);
|
|
84
335
|
this.componentNames.set(rawName, name);
|
|
85
336
|
}
|
|
337
|
+
|
|
338
|
+
for (const [rawBaseName, schema] of Object.entries(schemas)) {
|
|
339
|
+
const mappings = discriminatorMappings(schema);
|
|
340
|
+
if (mappings.length > 0) this.polymorphicBases.set(rawBaseName, mappings);
|
|
341
|
+
for (const mapping of mappings) {
|
|
342
|
+
const properties = this.discriminatorProperties.get(mapping.rawName) ?? new Map();
|
|
343
|
+
const previous = properties.get(mapping.propertyName);
|
|
344
|
+
if (previous !== undefined && previous !== mapping.value) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
`Schema ${mapping.rawName} has conflicting discriminator values for ${mapping.propertyName}.`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
properties.set(mapping.propertyName, mapping.value);
|
|
350
|
+
this.discriminatorProperties.set(mapping.rawName, properties);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
86
353
|
}
|
|
87
354
|
|
|
88
355
|
componentName(rawName) {
|
|
89
356
|
return this.componentNames.get(rawName) ?? operationTypeName(rawName);
|
|
90
357
|
}
|
|
91
358
|
|
|
359
|
+
componentType(rawName, options = {}) {
|
|
360
|
+
if (options.asInheritanceBase && this.polymorphicBases.has(rawName)) {
|
|
361
|
+
return `${this.componentName(rawName)}Base`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const metadata = this.genericSchemas.get(rawName);
|
|
365
|
+
if (!metadata) return this.componentName(rawName);
|
|
366
|
+
|
|
367
|
+
const argumentTypes = metadata.arguments.map((argument) => {
|
|
368
|
+
if (argument?.primitive) return String(argument.primitive);
|
|
369
|
+
if (argument?.schemaId) {
|
|
370
|
+
const schemaId = String(argument.schemaId);
|
|
371
|
+
return options.genericArgumentSubstitutions?.get(schemaId)
|
|
372
|
+
?? this.componentType(schemaId, options);
|
|
373
|
+
}
|
|
374
|
+
return "unknown";
|
|
375
|
+
});
|
|
376
|
+
return `${metadata.definitionName}<${argumentTypes.join(", ")}>`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
genericBaseType(base, ownerParameters) {
|
|
380
|
+
const argumentTypes = base.arguments.map((argument) => {
|
|
381
|
+
if (argument.genericParameter) return argument.genericParameter;
|
|
382
|
+
if (argument.primitive) return argument.primitive;
|
|
383
|
+
if (argument.schemaId) return this.componentType(argument.schemaId);
|
|
384
|
+
return "unknown";
|
|
385
|
+
});
|
|
386
|
+
return `${operationTypeName(base.definition)}<${argumentTypes.join(", ")}>`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
isGenericBaseReference(item, base) {
|
|
390
|
+
if (!item?.$ref) return false;
|
|
391
|
+
const metadata = this.genericSchemas.get(refName(item.$ref));
|
|
392
|
+
return metadata?.definitionName === operationTypeName(base.definition);
|
|
393
|
+
}
|
|
394
|
+
|
|
92
395
|
resolve(schemaOrRef, contextName = "Anonymous", options = {}) {
|
|
93
396
|
if (!schemaOrRef) return "unknown";
|
|
94
397
|
|
|
95
398
|
if (schemaOrRef.$ref) {
|
|
96
399
|
dereference(this.openApi, schemaOrRef, "Schema reference");
|
|
97
|
-
return this.
|
|
400
|
+
return this.componentType(refName(schemaOrRef.$ref), options);
|
|
98
401
|
}
|
|
99
402
|
|
|
100
403
|
const schema =
|
|
@@ -108,6 +411,8 @@ export class TypeResolver {
|
|
|
108
411
|
}
|
|
109
412
|
|
|
110
413
|
resolveNonNullable(schema, contextName, options = {}) {
|
|
414
|
+
const genericParameter = schema?.["x-typedapi-generic-parameter"];
|
|
415
|
+
if (genericParameter) return String(genericParameter);
|
|
111
416
|
if (schema.const !== undefined) return JSON.stringify(schema.const);
|
|
112
417
|
|
|
113
418
|
if (Array.isArray(schema.enum)) {
|
|
@@ -130,7 +435,10 @@ export class TypeResolver {
|
|
|
130
435
|
|
|
131
436
|
if (schema.allOf) {
|
|
132
437
|
const parts = schema.allOf.map((item, index) =>
|
|
133
|
-
this.resolve(item, `${contextName}${index + 1}`,
|
|
438
|
+
this.resolve(item, `${contextName}${index + 1}`, {
|
|
439
|
+
...options,
|
|
440
|
+
asInheritanceBase: true,
|
|
441
|
+
}),
|
|
134
442
|
);
|
|
135
443
|
const localSchema = schemaWithoutComposition(schema);
|
|
136
444
|
if (hasObjectShape(localSchema)) {
|
|
@@ -189,17 +497,18 @@ export class TypeResolver {
|
|
|
189
497
|
}
|
|
190
498
|
}
|
|
191
499
|
|
|
192
|
-
createInlineObjectType(schema, contextName) {
|
|
500
|
+
createInlineObjectType(schema, contextName, options = {}) {
|
|
193
501
|
const base = operationTypeName(contextName);
|
|
194
502
|
let name = base;
|
|
195
503
|
let counter = 2;
|
|
196
504
|
while (
|
|
197
505
|
this.componentNamesHasValue(name) ||
|
|
198
|
-
|
|
506
|
+
this.genericDefinitions.has(name) ||
|
|
507
|
+
(this.inlineTypes.has(name) && this.inlineTypes.get(name).schema !== schema)
|
|
199
508
|
) {
|
|
200
509
|
name = `${base}${counter++}`;
|
|
201
510
|
}
|
|
202
|
-
if (!this.inlineTypes.has(name)) this.inlineTypes.set(name, schema);
|
|
511
|
+
if (!this.inlineTypes.has(name)) this.inlineTypes.set(name, { schema, options });
|
|
203
512
|
return name;
|
|
204
513
|
}
|
|
205
514
|
|
|
@@ -207,26 +516,29 @@ export class TypeResolver {
|
|
|
207
516
|
return [...this.componentNames.values()].includes(value);
|
|
208
517
|
}
|
|
209
518
|
|
|
210
|
-
objectMembers(name, schema) {
|
|
519
|
+
objectMembers(name, schema, options = {}) {
|
|
211
520
|
const required = new Set(schema.required ?? []);
|
|
212
521
|
const lines = [];
|
|
213
522
|
|
|
214
523
|
const propertyEntries = Object.entries(schema.properties ?? {});
|
|
524
|
+
const syntheticDiscriminators = options.rawName
|
|
525
|
+
? this.discriminatorProperties.get(options.rawName) ?? new Map()
|
|
526
|
+
: new Map();
|
|
215
527
|
assertUniqueTypePropertyNames(
|
|
216
|
-
propertyEntries.map(([propertyName]) => propertyName),
|
|
528
|
+
[...propertyEntries.map(([propertyName]) => propertyName), ...syntheticDiscriminators.keys()],
|
|
217
529
|
name,
|
|
218
530
|
);
|
|
219
531
|
|
|
220
532
|
for (const [propertyName, propertySchema] of propertyEntries) {
|
|
221
533
|
const localPropertyName = typePropertyName(propertyName);
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
{
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
534
|
+
const discriminatorValue = syntheticDiscriminators.get(propertyName);
|
|
535
|
+
const optionalToken = discriminatorValue !== undefined || required.has(propertyName) ? "" : "?";
|
|
536
|
+
const propertyType = discriminatorValue !== undefined
|
|
537
|
+
? JSON.stringify(discriminatorValue)
|
|
538
|
+
: this.resolve(propertySchema, `${name}${pascalCase(propertyName)}`, {
|
|
539
|
+
...options,
|
|
540
|
+
inlineEnumAsUnion: true,
|
|
541
|
+
});
|
|
230
542
|
const resolvedSchema = dereference(
|
|
231
543
|
this.openApi,
|
|
232
544
|
propertySchema,
|
|
@@ -238,11 +550,17 @@ export class TypeResolver {
|
|
|
238
550
|
);
|
|
239
551
|
}
|
|
240
552
|
|
|
553
|
+
for (const [propertyName, value] of syntheticDiscriminators) {
|
|
554
|
+
if (Object.prototype.hasOwnProperty.call(schema.properties ?? {}, propertyName)) continue;
|
|
555
|
+
lines.push(` ${propertyKey(typePropertyName(propertyName))}: ${JSON.stringify(value)};`);
|
|
556
|
+
}
|
|
557
|
+
|
|
241
558
|
if (schema.additionalProperties) {
|
|
242
559
|
const valueType =
|
|
243
560
|
schema.additionalProperties === true
|
|
244
561
|
? "unknown"
|
|
245
562
|
: this.resolve(schema.additionalProperties, `${name}Value`, {
|
|
563
|
+
...options,
|
|
246
564
|
inlineEnumAsUnion: true,
|
|
247
565
|
});
|
|
248
566
|
lines.push(` [key: string]: ${valueType};`);
|
|
@@ -251,33 +569,133 @@ export class TypeResolver {
|
|
|
251
569
|
return lines;
|
|
252
570
|
}
|
|
253
571
|
|
|
254
|
-
createInterface(name, schema) {
|
|
572
|
+
createInterface(name, schema, options = {}) {
|
|
255
573
|
return [
|
|
256
574
|
`export interface ${name} {`,
|
|
257
|
-
...this.objectMembers(name, schema),
|
|
575
|
+
...this.objectMembers(name, schema, options),
|
|
258
576
|
"}",
|
|
259
577
|
].join("\n");
|
|
260
578
|
}
|
|
261
579
|
|
|
262
|
-
createObjectTypeLiteral(name, schema) {
|
|
263
|
-
return ["{", ...this.objectMembers(name, schema), "}"].join("\n");
|
|
580
|
+
createObjectTypeLiteral(name, schema, options = {}) {
|
|
581
|
+
return ["{", ...this.objectMembers(name, schema, options), "}"].join("\n");
|
|
264
582
|
}
|
|
265
583
|
|
|
266
|
-
createDeclaration(name, schema) {
|
|
584
|
+
createDeclaration(name, schema, options = {}) {
|
|
267
585
|
if (Array.isArray(schema.enum)) return this.createEnum(name, schema);
|
|
268
586
|
|
|
269
|
-
if (schema.
|
|
270
|
-
const
|
|
271
|
-
|
|
587
|
+
if (schema.oneOf || schema.anyOf) {
|
|
588
|
+
const variants = schema.oneOf ?? schema.anyOf;
|
|
589
|
+
const variantTypes = variants.map((item, index) =>
|
|
590
|
+
this.resolve(item, `${name}${index + 1}`, { ...options, inlineEnumAsUnion: true }),
|
|
272
591
|
);
|
|
592
|
+
return `export type ${name} = ${unionTypes(variantTypes)};`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (schema.allOf) {
|
|
596
|
+
const parts = schema.allOf.map((item, index) => {
|
|
597
|
+
const resolvedItem = dereference(this.openApi, item, "allOf schema reference");
|
|
598
|
+
if (!item?.$ref && hasObjectShape(resolvedItem) && !resolvedItem.allOf && !resolvedItem.oneOf && !resolvedItem.anyOf) {
|
|
599
|
+
return this.createObjectTypeLiteral(`${name}${index + 1}`, resolvedItem, {
|
|
600
|
+
...options,
|
|
601
|
+
rawName: undefined,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return this.resolve(item, `${name}${index + 1}`, {
|
|
605
|
+
...options,
|
|
606
|
+
inlineEnumAsUnion: true,
|
|
607
|
+
asInheritanceBase: true,
|
|
608
|
+
});
|
|
609
|
+
});
|
|
273
610
|
const localSchema = schemaWithoutComposition(schema);
|
|
274
|
-
|
|
275
|
-
|
|
611
|
+
const hasSyntheticDiscriminator = options.rawName && this.discriminatorProperties.has(options.rawName);
|
|
612
|
+
if (hasObjectShape(localSchema) || hasSyntheticDiscriminator)
|
|
613
|
+
parts.push(this.createObjectTypeLiteral(`${name}Own`, localSchema, options));
|
|
276
614
|
return `export type ${name} = ${intersectionTypes(parts)};`;
|
|
277
615
|
}
|
|
278
616
|
|
|
279
|
-
if (hasObjectShape(schema)) return this.createInterface(name, schema);
|
|
280
|
-
return `export type ${name} = ${this.resolve(schema, name, { inlineEnumAsUnion: true })};`;
|
|
617
|
+
if (hasObjectShape(schema)) return this.createInterface(name, schema, options);
|
|
618
|
+
return `export type ${name} = ${this.resolve(schema, name, { ...options, inlineEnumAsUnion: true })};`;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
createGenericDeclaration(metadata, schema) {
|
|
622
|
+
const substitutions = new Map();
|
|
623
|
+
metadata.arguments.forEach((argument, index) => {
|
|
624
|
+
if (argument?.schemaId) substitutions.set(String(argument.schemaId), metadata.parameters[index]);
|
|
625
|
+
});
|
|
626
|
+
const genericName = `${metadata.definitionName}<${metadata.parameters.join(", ")}>`;
|
|
627
|
+
const templateSchema = cloneSchemaWithGenericBindings(schema, metadata);
|
|
628
|
+
let declaration;
|
|
629
|
+
|
|
630
|
+
if (metadata.base) {
|
|
631
|
+
const inheritedProperties = new Set(metadata.base.properties ?? []);
|
|
632
|
+
const ownSchema = removeSelectedProperties(templateSchema, inheritedProperties);
|
|
633
|
+
const parts = [this.genericBaseType(metadata.base, metadata.parameters)];
|
|
634
|
+
|
|
635
|
+
for (const [index, item] of (templateSchema.allOf ?? []).entries()) {
|
|
636
|
+
if (this.isGenericBaseReference(item, metadata.base)) continue;
|
|
637
|
+
const resolvedItem = dereference(this.openApi, item, "allOf schema reference");
|
|
638
|
+
if (!item?.$ref && hasObjectShape(resolvedItem) && !resolvedItem.allOf && !resolvedItem.oneOf && !resolvedItem.anyOf) {
|
|
639
|
+
const ownInlineSchema = removeSelectedProperties(resolvedItem, inheritedProperties);
|
|
640
|
+
if (hasOwnObjectMembers(ownInlineSchema)) {
|
|
641
|
+
parts.push(this.createObjectTypeLiteral(`${genericName}${index + 1}`, ownInlineSchema, {
|
|
642
|
+
rawName: undefined,
|
|
643
|
+
genericArgumentSubstitutions: substitutions,
|
|
644
|
+
}));
|
|
645
|
+
}
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
parts.push(this.resolve(item, `${genericName}${index + 1}`, {
|
|
649
|
+
inlineEnumAsUnion: true,
|
|
650
|
+
genericArgumentSubstitutions: substitutions,
|
|
651
|
+
asInheritanceBase: true,
|
|
652
|
+
}));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (hasOwnObjectMembers(ownSchema)) {
|
|
656
|
+
parts.push(this.createObjectTypeLiteral(`${genericName}Own`, ownSchema, {
|
|
657
|
+
rawName: metadata.rawName,
|
|
658
|
+
genericArgumentSubstitutions: substitutions,
|
|
659
|
+
}));
|
|
660
|
+
}
|
|
661
|
+
declaration = `export type ${genericName} = ${intersectionTypes(parts)};`;
|
|
662
|
+
} else {
|
|
663
|
+
declaration = this.createDeclaration(genericName, templateSchema, {
|
|
664
|
+
rawName: metadata.rawName,
|
|
665
|
+
genericArgumentSubstitutions: substitutions,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const body = genericDeclarationBody(declaration);
|
|
670
|
+
const unusedParameters = metadata.parameters.filter((parameter) =>
|
|
671
|
+
!new RegExp(`\\b${escapeRegExp(parameter)}\\b`).test(body),
|
|
672
|
+
);
|
|
673
|
+
if (unusedParameters.length > 0) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
`Generic schema ${metadata.rawName} declares unbound type parameter${unusedParameters.length === 1 ? "" : "s"} ` +
|
|
676
|
+
`${unusedParameters.join(", ")}. Ensure x-typedapi-generic.bindings includes inherited or flattened generic properties.`,
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
return declaration;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
isPolymorphicBase(rawName) {
|
|
683
|
+
return this.polymorphicBases.has(rawName);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
createPolymorphicDeclarations(rawName, schema) {
|
|
687
|
+
const name = this.componentName(rawName);
|
|
688
|
+
const mappings = this.polymorphicBases.get(rawName) ?? [];
|
|
689
|
+
const variants = mappings.map((mapping) => this.componentType(mapping.rawName));
|
|
690
|
+
const { oneOf, anyOf, discriminator, ...baseSchema } = schema;
|
|
691
|
+
const declarations = [];
|
|
692
|
+
if (hasObjectShape(baseSchema) || baseSchema.allOf) {
|
|
693
|
+
declarations.push(this.createDeclaration(`${name}Base`, baseSchema));
|
|
694
|
+
} else {
|
|
695
|
+
declarations.push(`export interface ${name}Base {}`);
|
|
696
|
+
}
|
|
697
|
+
declarations.push(`export type ${name} = ${unionTypes(variants)};`);
|
|
698
|
+
return declarations;
|
|
281
699
|
}
|
|
282
700
|
|
|
283
701
|
createEnum(name, schema) {
|
|
@@ -494,17 +912,31 @@ export function generateDataContracts(openApi, operations, options = {}) {
|
|
|
494
912
|
const declarations = [];
|
|
495
913
|
const schemas = openApi.components?.schemas ?? {};
|
|
496
914
|
|
|
497
|
-
const
|
|
498
|
-
a.localeCompare(b),
|
|
915
|
+
const genericDefinitions = [...resolver.genericDefinitions.values()].sort((a, b) =>
|
|
916
|
+
a.definitionName.localeCompare(b.definitionName),
|
|
499
917
|
);
|
|
500
|
-
for (const
|
|
501
|
-
const
|
|
918
|
+
for (const metadata of genericDefinitions) {
|
|
919
|
+
const sourceSchema = dereference(
|
|
502
920
|
openApi,
|
|
503
|
-
|
|
504
|
-
"
|
|
921
|
+
schemas[metadata.rawName],
|
|
922
|
+
"Generic component schema reference",
|
|
505
923
|
);
|
|
924
|
+
const schema = metadata.synthetic
|
|
925
|
+
? copySelectedProperties(sourceSchema, metadata.syntheticProperties)
|
|
926
|
+
: sourceSchema;
|
|
927
|
+
declarations.push(resolver.createGenericDeclaration(metadata, schema));
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const entries = Object.entries(schemas).sort(([a], [b]) => a.localeCompare(b));
|
|
931
|
+
for (const [rawName, schemaOrRef] of entries) {
|
|
932
|
+
if (resolver.genericSchemas.has(rawName)) continue;
|
|
933
|
+
const schema = dereference(openApi, schemaOrRef, "Component schema reference");
|
|
934
|
+
if (resolver.isPolymorphicBase(rawName)) {
|
|
935
|
+
declarations.push(...resolver.createPolymorphicDeclarations(rawName, schema));
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
506
938
|
declarations.push(
|
|
507
|
-
resolver.createDeclaration(resolver.componentName(rawName), schema),
|
|
939
|
+
resolver.createDeclaration(resolver.componentName(rawName), schema, { rawName }),
|
|
508
940
|
);
|
|
509
941
|
}
|
|
510
942
|
|
|
@@ -557,9 +989,9 @@ export function generateDataContracts(openApi, operations, options = {}) {
|
|
|
557
989
|
}
|
|
558
990
|
}
|
|
559
991
|
|
|
560
|
-
for (const [name,
|
|
561
|
-
if (!resolver.componentNamesHasValue(name))
|
|
562
|
-
declarations.push(resolver.createDeclaration(name, schema));
|
|
992
|
+
for (const [name, inline] of resolver.inlineTypes.entries()) {
|
|
993
|
+
if (!resolver.componentNamesHasValue(name) && !resolver.genericDefinitions.has(name))
|
|
994
|
+
declarations.push(resolver.createDeclaration(name, inline.schema, inline.options));
|
|
563
995
|
}
|
|
564
996
|
|
|
565
997
|
|