@depup/openapi-typescript 7.13.0-depup.0

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +1162 -0
  2. package/CONTRIBUTING.md +149 -0
  3. package/LICENSE +21 -0
  4. package/README.md +32 -0
  5. package/bin/cli.js +297 -0
  6. package/changes.json +14 -0
  7. package/dist/index.cjs +152 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.d.cts +517 -0
  10. package/dist/index.d.mts +515 -0
  11. package/dist/index.d.ts +517 -0
  12. package/dist/index.mjs +85 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/lib/redoc.cjs +129 -0
  15. package/dist/lib/redoc.cjs.map +1 -0
  16. package/dist/lib/redoc.mjs +122 -0
  17. package/dist/lib/redoc.mjs.map +1 -0
  18. package/dist/lib/ts.cjs +470 -0
  19. package/dist/lib/ts.cjs.map +1 -0
  20. package/dist/lib/ts.mjs +431 -0
  21. package/dist/lib/ts.mjs.map +1 -0
  22. package/dist/lib/utils.cjs +295 -0
  23. package/dist/lib/utils.cjs.map +1 -0
  24. package/dist/lib/utils.mjs +278 -0
  25. package/dist/lib/utils.mjs.map +1 -0
  26. package/dist/transform/components-object.cjs +150 -0
  27. package/dist/transform/components-object.cjs.map +1 -0
  28. package/dist/transform/components-object.mjs +127 -0
  29. package/dist/transform/components-object.mjs.map +1 -0
  30. package/dist/transform/header-object.cjs +48 -0
  31. package/dist/transform/header-object.cjs.map +1 -0
  32. package/dist/transform/header-object.mjs +42 -0
  33. package/dist/transform/header-object.mjs.map +1 -0
  34. package/dist/transform/index.cjs +124 -0
  35. package/dist/transform/index.cjs.map +1 -0
  36. package/dist/transform/index.mjs +118 -0
  37. package/dist/transform/index.mjs.map +1 -0
  38. package/dist/transform/media-type-object.cjs +14 -0
  39. package/dist/transform/media-type-object.cjs.map +1 -0
  40. package/dist/transform/media-type-object.mjs +12 -0
  41. package/dist/transform/media-type-object.mjs.map +1 -0
  42. package/dist/transform/operation-object.cjs +105 -0
  43. package/dist/transform/operation-object.cjs.map +1 -0
  44. package/dist/transform/operation-object.mjs +96 -0
  45. package/dist/transform/operation-object.mjs.map +1 -0
  46. package/dist/transform/parameter-object.cjs +11 -0
  47. package/dist/transform/parameter-object.cjs.map +1 -0
  48. package/dist/transform/parameter-object.mjs +9 -0
  49. package/dist/transform/parameter-object.mjs.map +1 -0
  50. package/dist/transform/parameters-array.cjs +120 -0
  51. package/dist/transform/parameters-array.cjs.map +1 -0
  52. package/dist/transform/parameters-array.mjs +114 -0
  53. package/dist/transform/parameters-array.mjs.map +1 -0
  54. package/dist/transform/path-item-object.cjs +84 -0
  55. package/dist/transform/path-item-object.cjs.map +1 -0
  56. package/dist/transform/path-item-object.mjs +78 -0
  57. package/dist/transform/path-item-object.mjs.map +1 -0
  58. package/dist/transform/paths-enum.cjs +35 -0
  59. package/dist/transform/paths-enum.cjs.map +1 -0
  60. package/dist/transform/paths-enum.mjs +33 -0
  61. package/dist/transform/paths-enum.mjs.map +1 -0
  62. package/dist/transform/paths-object.cjs +134 -0
  63. package/dist/transform/paths-object.cjs.map +1 -0
  64. package/dist/transform/paths-object.mjs +128 -0
  65. package/dist/transform/paths-object.mjs.map +1 -0
  66. package/dist/transform/request-body-object.cjs +68 -0
  67. package/dist/transform/request-body-object.cjs.map +1 -0
  68. package/dist/transform/request-body-object.mjs +62 -0
  69. package/dist/transform/request-body-object.mjs.map +1 -0
  70. package/dist/transform/response-object.cjs +123 -0
  71. package/dist/transform/response-object.cjs.map +1 -0
  72. package/dist/transform/response-object.mjs +117 -0
  73. package/dist/transform/response-object.mjs.map +1 -0
  74. package/dist/transform/responses-object.cjs +36 -0
  75. package/dist/transform/responses-object.cjs.map +1 -0
  76. package/dist/transform/responses-object.mjs +30 -0
  77. package/dist/transform/responses-object.mjs.map +1 -0
  78. package/dist/transform/schema-object.cjs +502 -0
  79. package/dist/transform/schema-object.cjs.map +1 -0
  80. package/dist/transform/schema-object.mjs +493 -0
  81. package/dist/transform/schema-object.mjs.map +1 -0
  82. package/dist/transform/webhooks-object.cjs +37 -0
  83. package/dist/transform/webhooks-object.cjs.map +1 -0
  84. package/dist/transform/webhooks-object.mjs +31 -0
  85. package/dist/transform/webhooks-object.mjs.map +1 -0
  86. package/package.json +102 -0
  87. package/src/index.ts +108 -0
  88. package/src/lib/redoc.ts +164 -0
  89. package/src/lib/ts.ts +616 -0
  90. package/src/lib/utils.ts +394 -0
  91. package/src/transform/components-object.ts +161 -0
  92. package/src/transform/header-object.ts +45 -0
  93. package/src/transform/index.ts +116 -0
  94. package/src/transform/media-type-object.ts +18 -0
  95. package/src/transform/operation-object.ts +104 -0
  96. package/src/transform/parameter-object.ts +15 -0
  97. package/src/transform/parameters-array.ts +129 -0
  98. package/src/transform/path-item-object.ts +102 -0
  99. package/src/transform/paths-enum.ts +43 -0
  100. package/src/transform/paths-object.ts +134 -0
  101. package/src/transform/request-body-object.ts +59 -0
  102. package/src/transform/response-object.ts +112 -0
  103. package/src/transform/responses-object.ts +36 -0
  104. package/src/transform/schema-object.ts +737 -0
  105. package/src/transform/webhooks-object.ts +27 -0
  106. package/src/types.ts +735 -0
@@ -0,0 +1,394 @@
1
+ import { escapePointer, parseRef } from "@redocly/openapi-core/lib/ref-utils.js";
2
+ import c from "ansi-colors";
3
+ import supportsColor from "supports-color";
4
+ import ts from "typescript";
5
+ import type { DiscriminatorObject, OpenAPI3, OpenAPITSOptions, ReferenceObject, SchemaObject } from "../types.js";
6
+ import { tsLiteral, tsModifiers, tsPropertyIndex } from "./ts.js";
7
+
8
+ if (!supportsColor.stdout || supportsColor.stdout.hasBasic === false) {
9
+ c.enabled = false;
10
+ }
11
+
12
+ const DEBUG_GROUPS: Record<string, c.StyleFunction | undefined> = {
13
+ redoc: c.cyanBright,
14
+ lint: c.yellowBright,
15
+ bundle: c.magentaBright,
16
+ ts: c.blueBright,
17
+ };
18
+
19
+ export { c };
20
+
21
+ /** Given a discriminator object, get the property name */
22
+ export function createDiscriminatorProperty(
23
+ discriminator: DiscriminatorObject,
24
+ { path, readonly = false }: { path: string; readonly?: boolean },
25
+ ): ts.TypeElement {
26
+ // get the inferred propertyName value from the last section of the path (as the spec suggests to do)
27
+ let value = parseRef(path).pointer.pop();
28
+ // if mapping, and there’s a match, use this rather than the inferred name
29
+ if (discriminator.mapping) {
30
+ // Mapping value can either be a fully-qualified ref (#/components/schemas/XYZ) or a schema name (XYZ)
31
+ const matchedValue = Object.entries(discriminator.mapping).find(
32
+ ([, v]) => (!v.startsWith("#") && v === value) || (v.startsWith("#") && parseRef(v).pointer.pop() === value),
33
+ );
34
+ if (matchedValue) {
35
+ value = matchedValue[0]; // why was this designed backwards!?
36
+ }
37
+ }
38
+ return ts.factory.createPropertySignature(
39
+ /* modifiers */ tsModifiers({
40
+ readonly,
41
+ }),
42
+ /* name */ tsPropertyIndex(discriminator.propertyName),
43
+ /* questionToken */ undefined,
44
+ /* type */ tsLiteral(value),
45
+ );
46
+ }
47
+
48
+ /** Create a $ref pointer (even from other $refs) */
49
+ export function createRef(parts: (number | string | undefined | null)[]): string {
50
+ let pointer = "#";
51
+ for (const part of parts) {
52
+ if (part === undefined || part === null || part === "") {
53
+ continue;
54
+ }
55
+ const maybeRef = parseRef(String(part)).pointer;
56
+ if (maybeRef.length) {
57
+ for (const refPart of maybeRef) {
58
+ pointer += `/${escapePointer(refPart)}`;
59
+ }
60
+ } else {
61
+ pointer += `/${escapePointer(part)}`;
62
+ }
63
+ }
64
+ return pointer;
65
+ }
66
+
67
+ /** Print debug message (cribbed from the `debug` package, but without all the bells & whistles */
68
+ export function debug(msg: string, group?: string, time?: number) {
69
+ if (
70
+ process.env.DEBUG &&
71
+ (!group ||
72
+ process.env.DEBUG === "*" ||
73
+ process.env.DEBUG === "openapi-ts:*" ||
74
+ process.env.DEBUG.toLocaleLowerCase() === `openapi-ts:${group.toLocaleLowerCase()}`)
75
+ ) {
76
+ const groupColor = (group && DEBUG_GROUPS[group]) || c.whiteBright;
77
+ const groupName = groupColor(`openapi-ts:${group ?? "info"}`);
78
+ let timeFormatted = "";
79
+ if (typeof time === "number") {
80
+ timeFormatted = c.green(` ${formatTime(time)} `);
81
+ }
82
+ // biome-ignore lint/suspicious/noConsole: this is a CLI
83
+ console.debug(` ${c.bold(groupName)}${timeFormatted}${msg}`);
84
+ }
85
+ }
86
+
87
+ /** Print error message */
88
+ export function error(msg: string) {
89
+ // biome-ignore lint/suspicious/noConsole: this is a CLI
90
+ console.error(c.red(` ✘ ${msg}`));
91
+ }
92
+
93
+ /** Format a performance log in a friendly format */
94
+ export function formatTime(t: number) {
95
+ if (typeof t === "number") {
96
+ if (t < 1000) {
97
+ return `${Math.round(10 * t) / 10}ms`;
98
+ }
99
+ if (t < 60000) {
100
+ return `${Math.round(t / 100) / 10}s`;
101
+ }
102
+ return `${Math.round(t / 6000) / 10}m`;
103
+ }
104
+ return t;
105
+ }
106
+
107
+ /** Call Object.entries() and optionally sort */
108
+ export function getEntries<T>(
109
+ obj: ArrayLike<T> | Record<string, T>,
110
+ options?: {
111
+ alphabetize?: boolean;
112
+ excludeDeprecated?: boolean;
113
+ },
114
+ ) {
115
+ let entries = Object.entries(obj);
116
+ if (options?.alphabetize) {
117
+ entries.sort(([a], [b]) => a.localeCompare(b, "en-us", { numeric: true }));
118
+ }
119
+ if (options?.excludeDeprecated) {
120
+ entries = entries.filter(([, v]) => !(v && typeof v === "object" && "deprecated" in v && v.deprecated));
121
+ }
122
+ return entries;
123
+ }
124
+
125
+ /** resolve a $ref in a schema */
126
+ export function resolveRef<T>(
127
+ schema: any,
128
+ $ref: string,
129
+ { silent = false, visited = [] }: { silent: boolean; visited?: string[] },
130
+ ): T | undefined {
131
+ const { pointer } = parseRef($ref);
132
+ if (!pointer.length) {
133
+ return undefined;
134
+ }
135
+ let node = schema;
136
+ for (const key of pointer) {
137
+ if (node && typeof node === "object" && node[key]) {
138
+ node = node[key];
139
+ } else {
140
+ warn(`Could not resolve $ref "${$ref}"`, silent);
141
+ return undefined;
142
+ }
143
+ }
144
+
145
+ // if this is also a $ref, keep tracing
146
+ if (node && typeof node === "object" && node.$ref) {
147
+ if (visited.includes(node.$ref)) {
148
+ warn(`Could not resolve circular $ref "${$ref}"`, silent);
149
+ return undefined;
150
+ }
151
+ return resolveRef(schema, node.$ref, {
152
+ silent,
153
+ visited: [...visited, node.$ref],
154
+ });
155
+ }
156
+
157
+ return node;
158
+ }
159
+
160
+ function createDiscriminatorEnum(values: string[], prevSchema?: SchemaObject): SchemaObject {
161
+ return {
162
+ type: "string",
163
+ enum: values,
164
+ description: prevSchema?.description
165
+ ? `${prevSchema.description} (enum property replaced by openapi-typescript)`
166
+ : "discriminator enum property added by openapi-typescript",
167
+ };
168
+ }
169
+
170
+ /** Adds or replaces the discriminator enum with the passed `values` in a schema defined by `ref` */
171
+ function patchDiscriminatorEnum(
172
+ schema: SchemaObject,
173
+ ref: string,
174
+ values: string[],
175
+ discriminator: DiscriminatorObject,
176
+ discriminatorRef: string,
177
+ options: OpenAPITSOptions,
178
+ ): boolean {
179
+ const resolvedSchema = resolveRef<SchemaObject>(schema, ref, {
180
+ silent: options.silent ?? false,
181
+ });
182
+
183
+ if (resolvedSchema?.allOf) {
184
+ // if the schema is an allOf, we can append a new schema object to the allOf array
185
+ resolvedSchema.allOf.push({
186
+ type: "object",
187
+ // discriminator enum properties always need to be required
188
+ required: [discriminator.propertyName],
189
+ properties: {
190
+ [discriminator.propertyName]: createDiscriminatorEnum(values),
191
+ },
192
+ });
193
+
194
+ return true;
195
+ } else if (typeof resolvedSchema === "object" && "type" in resolvedSchema && resolvedSchema.type === "object") {
196
+ // if the schema is an object, we can apply the discriminator enums to its properties
197
+ if (!resolvedSchema.properties) {
198
+ resolvedSchema.properties = {};
199
+ }
200
+
201
+ // discriminator enum properties always need to be required
202
+ if (!resolvedSchema.required) {
203
+ resolvedSchema.required = [discriminator.propertyName];
204
+ } else if (!resolvedSchema.required.includes(discriminator.propertyName)) {
205
+ resolvedSchema.required.push(discriminator.propertyName);
206
+ }
207
+
208
+ // add/replace the discriminator enum property
209
+ resolvedSchema.properties[discriminator.propertyName] = createDiscriminatorEnum(
210
+ values,
211
+ resolvedSchema.properties[discriminator.propertyName] as SchemaObject,
212
+ );
213
+
214
+ return true;
215
+ }
216
+
217
+ warn(
218
+ `Discriminator mapping has an invalid schema (neither an object schema nor an allOf array): ${ref} => ${values.join(
219
+ ", ",
220
+ )} (Discriminator: ${discriminatorRef})`,
221
+ options.silent,
222
+ );
223
+
224
+ return false;
225
+ }
226
+
227
+ type InternalDiscriminatorMapping = Record<string, { inferred?: string; defined?: string[] }>;
228
+
229
+ /** Return a key–value map of discriminator objects found in a schema */
230
+ export function scanDiscriminators(schema: OpenAPI3, options: OpenAPITSOptions) {
231
+ // all discriminator objects found in the schema
232
+ const objects: Record<string, DiscriminatorObject> = {};
233
+
234
+ // refs of all mapped schema objects we have successfully handled to infer the discriminator enum value
235
+ const refsHandled: string[] = [];
236
+
237
+ // perform 2 passes: first, collect all discriminator definitions and handle oneOf and mappings
238
+ walk(schema, (obj, path) => {
239
+ const discriminator = obj?.discriminator as DiscriminatorObject | undefined;
240
+ if (!discriminator?.propertyName) {
241
+ return;
242
+ }
243
+
244
+ // collect discriminator object for later usage
245
+ const ref = createRef(path);
246
+
247
+ objects[ref] = discriminator;
248
+
249
+ // if a mapping is available we will help Typescript to infer properties by adding the discriminator enum with its single mapped value to each schema
250
+ // we only handle the mapping in advance for discriminator + oneOf compositions right now
251
+ if (!obj?.oneOf || !Array.isArray(obj.oneOf)) {
252
+ return;
253
+ }
254
+
255
+ const oneOf: (SchemaObject | ReferenceObject)[] = obj.oneOf;
256
+ const mapping: InternalDiscriminatorMapping = {};
257
+
258
+ // the mapping can be inferred from the oneOf refs next to the discriminator object
259
+ for (const item of oneOf) {
260
+ if ("$ref" in item) {
261
+ // the name of the schema is the inferred discriminator enum value
262
+ const value = item.$ref.split("/").pop();
263
+
264
+ if (value) {
265
+ if (!mapping[item.$ref]) {
266
+ mapping[item.$ref] = { inferred: value };
267
+ } else {
268
+ mapping[item.$ref].inferred = value;
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ // the mapping can be defined in the discriminator object itself
275
+ if (discriminator.mapping) {
276
+ for (const mappedValue in discriminator.mapping) {
277
+ const mappedRef = discriminator.mapping[mappedValue];
278
+ if (!mappedRef) {
279
+ continue;
280
+ }
281
+
282
+ if (!mapping[mappedRef]?.defined) {
283
+ // this overrides inferred values, but we don't need them anymore as soon as we have a defined value
284
+ mapping[mappedRef] = { defined: [] };
285
+ }
286
+
287
+ mapping[mappedRef].defined?.push(mappedValue);
288
+ }
289
+ }
290
+
291
+ for (const [mappedRef, { inferred, defined }] of Object.entries(mapping)) {
292
+ if (refsHandled.includes(mappedRef)) {
293
+ continue;
294
+ }
295
+
296
+ if (!inferred && !defined) {
297
+ continue;
298
+ }
299
+
300
+ // prefer defined values over automatically inferred ones
301
+ // the inferred enum values from the schema might not represent the actual enum values of the discriminator,
302
+ // so if we have defined values, use them instead
303
+ // biome-ignore lint/style/noNonNullAssertion: we just checked for this
304
+ const mappedValues = defined ?? [inferred!];
305
+
306
+ if (
307
+ patchDiscriminatorEnum(schema as unknown as SchemaObject, mappedRef, mappedValues, discriminator, ref, options)
308
+ ) {
309
+ refsHandled.push(mappedRef);
310
+ }
311
+ }
312
+ });
313
+
314
+ // second, collect the schema objects that inherit from discriminators
315
+ // (sometimes this mapping is implicit, so it can’t be done until we know
316
+ // about every discriminator in the document)
317
+ walk(schema, (obj, path) => {
318
+ if (!obj || !Array.isArray(obj.allOf)) {
319
+ return;
320
+ }
321
+
322
+ for (const item of (obj as any).allOf) {
323
+ if ("$ref" in item) {
324
+ if (!objects[item.$ref]) {
325
+ return;
326
+ }
327
+
328
+ const ref = createRef(path);
329
+ const discriminator = objects[item.$ref];
330
+ const mappedValues: string[] = [];
331
+
332
+ if (discriminator.mapping) {
333
+ for (const mappedValue in discriminator.mapping) {
334
+ if (discriminator.mapping[mappedValue] === ref) {
335
+ mappedValues.push(mappedValue);
336
+ }
337
+ }
338
+
339
+ if (mappedValues.length > 0) {
340
+ if (
341
+ patchDiscriminatorEnum(
342
+ schema as unknown as SchemaObject,
343
+ ref,
344
+ mappedValues,
345
+ discriminator,
346
+ item.$ref,
347
+ options,
348
+ )
349
+ ) {
350
+ refsHandled.push(ref);
351
+ }
352
+ }
353
+ }
354
+
355
+ objects[ref] = {
356
+ ...objects[item.$ref],
357
+ };
358
+ } else if (item.discriminator?.propertyName) {
359
+ objects[createRef(path)] = { ...item.discriminator };
360
+ }
361
+ }
362
+ });
363
+
364
+ return { objects, refsHandled };
365
+ }
366
+
367
+ /** Walk through any JSON-serializable (i.e. non-circular) object */
368
+ export function walk(
369
+ obj: unknown,
370
+ cb: (value: Record<string, unknown>, path: (string | number)[]) => void,
371
+ path: (string | number)[] = [],
372
+ ): void {
373
+ if (!obj || typeof obj !== "object") {
374
+ return;
375
+ }
376
+ if (Array.isArray(obj)) {
377
+ for (let i = 0; i < obj.length; i++) {
378
+ walk(obj[i], cb, path.concat(i));
379
+ }
380
+ return;
381
+ }
382
+ cb(obj as Record<string, unknown>, path);
383
+ for (const k of Object.keys(obj)) {
384
+ walk((obj as Record<string, unknown>)[k], cb, path.concat(k));
385
+ }
386
+ }
387
+
388
+ /** Print warning message */
389
+ export function warn(msg: string, silent = false) {
390
+ if (!silent) {
391
+ // biome-ignore lint/suspicious/noConsole: this is a CLI
392
+ console.warn(c.yellow(` ⚠ ${msg}`));
393
+ }
394
+ }
@@ -0,0 +1,161 @@
1
+ import { performance } from "node:perf_hooks";
2
+ import * as changeCase from "change-case";
3
+ import ts from "typescript";
4
+ import { addJSDocComment, NEVER, QUESTION_TOKEN, tsModifiers, tsPropertyIndex } from "../lib/ts.js";
5
+ import { createRef, debug, getEntries } from "../lib/utils.js";
6
+ import type { ComponentsObject, GlobalContext, SchemaObject, TransformNodeOptions } from "../types.js";
7
+ import transformHeaderObject from "./header-object.js";
8
+ import transformParameterObject from "./parameter-object.js";
9
+ import transformPathItemObject from "./path-item-object.js";
10
+ import transformRequestBodyObject from "./request-body-object.js";
11
+ import transformResponseObject from "./response-object.js";
12
+ import transformSchemaObject from "./schema-object.js";
13
+
14
+ /**
15
+ * Determines if a schema object represents an enum type to prevent duplicate exports
16
+ * when using --root-types and --enum flags together.
17
+ *
18
+ * When both flags are enabled:
19
+ * - --enum flag generates TypeScript enums at the bottom of the file
20
+ * - --root-types flag would normally also export these as root type aliases
21
+ * - This results in duplicate exports (both enum and type alias for the same schema)
22
+ *
23
+ * This function identifies enum schemas so they can be excluded from root type generation,
24
+ * allowing only the TypeScript enum to be generated.
25
+ *
26
+ * @param schema The schema object to check
27
+ * @returns true if the schema represents an enum type
28
+ */
29
+ export function isEnumSchema(schema: unknown): boolean {
30
+ return (
31
+ typeof schema === "object" &&
32
+ schema !== null &&
33
+ !Array.isArray(schema) &&
34
+ "enum" in schema &&
35
+ Array.isArray((schema as any).enum) &&
36
+ (!("type" in schema) || (schema as any).type !== "object") &&
37
+ !("properties" in schema) &&
38
+ !("additionalProperties" in schema)
39
+ );
40
+ }
41
+
42
+ type ComponentTransforms = keyof Omit<ComponentsObject, "examples" | "securitySchemes" | "links" | "callbacks">;
43
+
44
+ const transformers: Record<ComponentTransforms, (node: any, options: TransformNodeOptions) => ts.TypeNode> = {
45
+ schemas: transformSchemaObject,
46
+ responses: transformResponseObject,
47
+ parameters: transformParameterObject,
48
+ requestBodies: transformRequestBodyObject,
49
+ headers: transformHeaderObject,
50
+ pathItems: transformPathItemObject,
51
+ };
52
+
53
+ /**
54
+ * Transform the ComponentsObject (4.8.7)
55
+ * @see https://spec.openapis.org/oas/latest.html#components-object
56
+ */
57
+ export default function transformComponentsObject(componentsObject: ComponentsObject, ctx: GlobalContext): ts.Node[] {
58
+ const type: ts.TypeElement[] = [];
59
+ const rootTypeAliases: { [key: string]: ts.TypeAliasDeclaration } = {};
60
+ for (const key of Object.keys(transformers) as ComponentTransforms[]) {
61
+ const componentT = performance.now();
62
+
63
+ const items: ts.TypeElement[] = [];
64
+ if (componentsObject[key]) {
65
+ for (const [name, item] of getEntries<SchemaObject>(componentsObject[key], ctx)) {
66
+ let subType = transformers[key](item, {
67
+ path: createRef(["components", key, name]),
68
+ schema: item,
69
+ ctx,
70
+ });
71
+
72
+ let hasQuestionToken = false;
73
+ if (ctx.transform) {
74
+ const result = ctx.transform(item, {
75
+ path: createRef(["components", key, name]),
76
+ schema: item,
77
+ ctx,
78
+ });
79
+ if (result) {
80
+ if ("schema" in result) {
81
+ subType = result.schema;
82
+ hasQuestionToken = result.questionToken;
83
+ } else {
84
+ subType = result;
85
+ }
86
+ }
87
+ }
88
+
89
+ const property = ts.factory.createPropertySignature(
90
+ /* modifiers */ tsModifiers({ readonly: ctx.immutable }),
91
+ /* name */ tsPropertyIndex(name),
92
+ /* questionToken */ hasQuestionToken ? QUESTION_TOKEN : undefined,
93
+ /* type */ subType,
94
+ );
95
+ addJSDocComment(item as unknown as any, property);
96
+ items.push(property);
97
+
98
+ if (ctx.rootTypes) {
99
+ // Skip enum schemas when generating root types to prevent duplication (only when --enum flag is enabled)
100
+ const shouldSkipEnumSchema = ctx.enum && key === "schemas" && isEnumSchema(item);
101
+
102
+ if (!shouldSkipEnumSchema) {
103
+ const componentKey = changeCase.pascalCase(singularizeComponentKey(key));
104
+ const componentName = ctx.rootTypesKeepCasing && key === "schemas" ? name : changeCase.pascalCase(name);
105
+ let aliasName = `${componentKey}${componentName}`;
106
+
107
+ // Add counter suffix (e.g. "_2") if conflict in name
108
+ let conflictCounter = 1;
109
+
110
+ while (rootTypeAliases[aliasName] !== undefined) {
111
+ conflictCounter++;
112
+ aliasName = `${componentKey}${componentName}_${conflictCounter}`;
113
+ }
114
+ const ref = ts.factory.createTypeReferenceNode(`components['${key}']['${name}']`);
115
+ if (ctx.rootTypesNoSchemaPrefix && key === "schemas") {
116
+ aliasName = aliasName.replace(componentKey, "");
117
+ }
118
+ const typeAlias = ts.factory.createTypeAliasDeclaration(
119
+ /* modifiers */ tsModifiers({ export: true }),
120
+ /* name */ aliasName,
121
+ /* typeParameters */ undefined,
122
+ /* type */ ref,
123
+ );
124
+ rootTypeAliases[aliasName] = typeAlias;
125
+ }
126
+ }
127
+ }
128
+ }
129
+ type.push(
130
+ ts.factory.createPropertySignature(
131
+ /* modifiers */ undefined,
132
+ /* name */ tsPropertyIndex(key),
133
+ /* questionToken */ undefined,
134
+ /* type */ items.length ? ts.factory.createTypeLiteralNode(items) : NEVER,
135
+ ),
136
+ );
137
+
138
+ debug(`Transformed components → ${key}`, "ts", performance.now() - componentT);
139
+ }
140
+
141
+ // Extract root types
142
+ let rootTypes: ts.TypeAliasDeclaration[] = [];
143
+ if (ctx.rootTypes) {
144
+ rootTypes = Object.keys(rootTypeAliases).map((k) => rootTypeAliases[k]);
145
+ }
146
+
147
+ return [ts.factory.createTypeLiteralNode(type), ...rootTypes];
148
+ }
149
+
150
+ export function singularizeComponentKey(
151
+ key: `x-${string}` | "schemas" | "responses" | "parameters" | "requestBodies" | "headers" | "pathItems",
152
+ ): string {
153
+ switch (key) {
154
+ // Handle special singular case
155
+ case "requestBodies":
156
+ return "requestBody";
157
+ // Default to removing the "s"
158
+ default:
159
+ return key.slice(0, -1);
160
+ }
161
+ }
@@ -0,0 +1,45 @@
1
+ import { escapePointer } from "@redocly/openapi-core/lib/ref-utils.js";
2
+ import ts from "typescript";
3
+ import { addJSDocComment, tsModifiers, tsPropertyIndex, UNKNOWN } from "../lib/ts.js";
4
+ import { getEntries } from "../lib/utils.js";
5
+ import type { HeaderObject, TransformNodeOptions } from "../types.js";
6
+ import transformMediaTypeObject from "./media-type-object.js";
7
+ import transformSchemaObject from "./schema-object.js";
8
+
9
+ /**
10
+ * Transform HeaderObject nodes (4.8.21)
11
+ * @see https://spec.openapis.org/oas/v3.1.0#header-object
12
+ */
13
+ export default function transformHeaderObject(headerObject: HeaderObject, options: TransformNodeOptions): ts.TypeNode {
14
+ if (headerObject.schema) {
15
+ return transformSchemaObject(headerObject.schema, options);
16
+ }
17
+
18
+ if (headerObject.content) {
19
+ const type: ts.TypeElement[] = [];
20
+ for (const [contentType, mediaTypeObject] of getEntries(headerObject.content ?? {}, options.ctx)) {
21
+ const nextPath = `${options.path ?? "#"}/${escapePointer(contentType)}`;
22
+ const mediaType =
23
+ "$ref" in mediaTypeObject
24
+ ? transformSchemaObject(mediaTypeObject, {
25
+ ...options,
26
+ path: nextPath,
27
+ })
28
+ : transformMediaTypeObject(mediaTypeObject, {
29
+ ...options,
30
+ path: nextPath,
31
+ });
32
+ const property = ts.factory.createPropertySignature(
33
+ /* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
34
+ /* name */ tsPropertyIndex(contentType),
35
+ /* questionToken */ undefined,
36
+ /* type */ mediaType,
37
+ );
38
+ addJSDocComment(mediaTypeObject, property);
39
+ type.push(property);
40
+ }
41
+ return ts.factory.createTypeLiteralNode(type);
42
+ }
43
+
44
+ return UNKNOWN;
45
+ }