@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.
- package/CHANGELOG.md +1162 -0
- package/CONTRIBUTING.md +149 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/bin/cli.js +297 -0
- package/changes.json +14 -0
- package/dist/index.cjs +152 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +517 -0
- package/dist/index.d.mts +515 -0
- package/dist/index.d.ts +517 -0
- package/dist/index.mjs +85 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/redoc.cjs +129 -0
- package/dist/lib/redoc.cjs.map +1 -0
- package/dist/lib/redoc.mjs +122 -0
- package/dist/lib/redoc.mjs.map +1 -0
- package/dist/lib/ts.cjs +470 -0
- package/dist/lib/ts.cjs.map +1 -0
- package/dist/lib/ts.mjs +431 -0
- package/dist/lib/ts.mjs.map +1 -0
- package/dist/lib/utils.cjs +295 -0
- package/dist/lib/utils.cjs.map +1 -0
- package/dist/lib/utils.mjs +278 -0
- package/dist/lib/utils.mjs.map +1 -0
- package/dist/transform/components-object.cjs +150 -0
- package/dist/transform/components-object.cjs.map +1 -0
- package/dist/transform/components-object.mjs +127 -0
- package/dist/transform/components-object.mjs.map +1 -0
- package/dist/transform/header-object.cjs +48 -0
- package/dist/transform/header-object.cjs.map +1 -0
- package/dist/transform/header-object.mjs +42 -0
- package/dist/transform/header-object.mjs.map +1 -0
- package/dist/transform/index.cjs +124 -0
- package/dist/transform/index.cjs.map +1 -0
- package/dist/transform/index.mjs +118 -0
- package/dist/transform/index.mjs.map +1 -0
- package/dist/transform/media-type-object.cjs +14 -0
- package/dist/transform/media-type-object.cjs.map +1 -0
- package/dist/transform/media-type-object.mjs +12 -0
- package/dist/transform/media-type-object.mjs.map +1 -0
- package/dist/transform/operation-object.cjs +105 -0
- package/dist/transform/operation-object.cjs.map +1 -0
- package/dist/transform/operation-object.mjs +96 -0
- package/dist/transform/operation-object.mjs.map +1 -0
- package/dist/transform/parameter-object.cjs +11 -0
- package/dist/transform/parameter-object.cjs.map +1 -0
- package/dist/transform/parameter-object.mjs +9 -0
- package/dist/transform/parameter-object.mjs.map +1 -0
- package/dist/transform/parameters-array.cjs +120 -0
- package/dist/transform/parameters-array.cjs.map +1 -0
- package/dist/transform/parameters-array.mjs +114 -0
- package/dist/transform/parameters-array.mjs.map +1 -0
- package/dist/transform/path-item-object.cjs +84 -0
- package/dist/transform/path-item-object.cjs.map +1 -0
- package/dist/transform/path-item-object.mjs +78 -0
- package/dist/transform/path-item-object.mjs.map +1 -0
- package/dist/transform/paths-enum.cjs +35 -0
- package/dist/transform/paths-enum.cjs.map +1 -0
- package/dist/transform/paths-enum.mjs +33 -0
- package/dist/transform/paths-enum.mjs.map +1 -0
- package/dist/transform/paths-object.cjs +134 -0
- package/dist/transform/paths-object.cjs.map +1 -0
- package/dist/transform/paths-object.mjs +128 -0
- package/dist/transform/paths-object.mjs.map +1 -0
- package/dist/transform/request-body-object.cjs +68 -0
- package/dist/transform/request-body-object.cjs.map +1 -0
- package/dist/transform/request-body-object.mjs +62 -0
- package/dist/transform/request-body-object.mjs.map +1 -0
- package/dist/transform/response-object.cjs +123 -0
- package/dist/transform/response-object.cjs.map +1 -0
- package/dist/transform/response-object.mjs +117 -0
- package/dist/transform/response-object.mjs.map +1 -0
- package/dist/transform/responses-object.cjs +36 -0
- package/dist/transform/responses-object.cjs.map +1 -0
- package/dist/transform/responses-object.mjs +30 -0
- package/dist/transform/responses-object.mjs.map +1 -0
- package/dist/transform/schema-object.cjs +502 -0
- package/dist/transform/schema-object.cjs.map +1 -0
- package/dist/transform/schema-object.mjs +493 -0
- package/dist/transform/schema-object.mjs.map +1 -0
- package/dist/transform/webhooks-object.cjs +37 -0
- package/dist/transform/webhooks-object.cjs.map +1 -0
- package/dist/transform/webhooks-object.mjs +31 -0
- package/dist/transform/webhooks-object.mjs.map +1 -0
- package/package.json +102 -0
- package/src/index.ts +108 -0
- package/src/lib/redoc.ts +164 -0
- package/src/lib/ts.ts +616 -0
- package/src/lib/utils.ts +394 -0
- package/src/transform/components-object.ts +161 -0
- package/src/transform/header-object.ts +45 -0
- package/src/transform/index.ts +116 -0
- package/src/transform/media-type-object.ts +18 -0
- package/src/transform/operation-object.ts +104 -0
- package/src/transform/parameter-object.ts +15 -0
- package/src/transform/parameters-array.ts +129 -0
- package/src/transform/path-item-object.ts +102 -0
- package/src/transform/paths-enum.ts +43 -0
- package/src/transform/paths-object.ts +134 -0
- package/src/transform/request-body-object.ts +59 -0
- package/src/transform/response-object.ts +112 -0
- package/src/transform/responses-object.ts +36 -0
- package/src/transform/schema-object.ts +737 -0
- package/src/transform/webhooks-object.ts +27 -0
- package/src/types.ts +735 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import ts, { type InterfaceDeclaration, type TypeLiteralNode } from "typescript";
|
|
3
|
+
import { NEVER, STRING, stringToAST, tsModifiers, tsRecord } from "../lib/ts.js";
|
|
4
|
+
import { createRef, debug } from "../lib/utils.js";
|
|
5
|
+
import type { GlobalContext, OpenAPI3 } from "../types.js";
|
|
6
|
+
import transformComponentsObject from "./components-object.js";
|
|
7
|
+
import makeApiPathsEnum from "./paths-enum.js";
|
|
8
|
+
import transformPathsObject from "./paths-object.js";
|
|
9
|
+
import transformSchemaObject from "./schema-object.js";
|
|
10
|
+
import transformWebhooksObject from "./webhooks-object.js";
|
|
11
|
+
|
|
12
|
+
type SchemaTransforms = keyof Pick<OpenAPI3, "paths" | "webhooks" | "components" | "$defs">;
|
|
13
|
+
|
|
14
|
+
const transformers: Record<SchemaTransforms, (node: any, options: GlobalContext) => ts.Node | ts.Node[]> = {
|
|
15
|
+
paths: transformPathsObject,
|
|
16
|
+
webhooks: transformWebhooksObject,
|
|
17
|
+
components: transformComponentsObject,
|
|
18
|
+
$defs: (node, options) => transformSchemaObject(node, { path: createRef(["$defs"]), ctx: options, schema: node }),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Inline helper types for readOnly/writeOnly markers (when readWriteMarkers is enabled)
|
|
22
|
+
const READ_WRITE_HELPER_TYPES = `
|
|
23
|
+
export type $Read<T> = { readonly $read: T };
|
|
24
|
+
export type $Write<T> = { readonly $write: T };
|
|
25
|
+
export type Readable<T> = T extends $Write<any> ? never : T extends $Read<infer U> ? Readable<U> : T extends (infer E)[] ? Readable<E>[] : T extends object ? { [K in keyof T as NonNullable<T[K]> extends $Write<any> ? never : K]: Readable<T[K]> } : T;
|
|
26
|
+
export type Writable<T> = T extends $Read<any> ? never : T extends $Write<infer U> ? Writable<U> : T extends (infer E)[] ? Writable<E>[] : T extends object ? { [K in keyof T as NonNullable<T[K]> extends $Read<any> ? never : K]: Writable<T[K]> } & { [K in keyof T as NonNullable<T[K]> extends $Read<any> ? K : never]?: never } : T;
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
export default function transformSchema(schema: OpenAPI3, ctx: GlobalContext) {
|
|
30
|
+
const type: ts.Node[] = [];
|
|
31
|
+
|
|
32
|
+
// Add inline helper types for readOnly/writeOnly markers
|
|
33
|
+
if (ctx.readWriteMarkers) {
|
|
34
|
+
const helperNodes = stringToAST(READ_WRITE_HELPER_TYPES) as ts.Node[];
|
|
35
|
+
type.push(...helperNodes);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (ctx.inject) {
|
|
39
|
+
const injectNodes = stringToAST(ctx.inject) as ts.Node[];
|
|
40
|
+
type.push(...injectNodes);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const root of Object.keys(transformers) as SchemaTransforms[]) {
|
|
44
|
+
const emptyObj = ts.factory.createTypeAliasDeclaration(
|
|
45
|
+
/* modifiers */ tsModifiers({ export: true }),
|
|
46
|
+
/* name */ root,
|
|
47
|
+
/* typeParameters */ undefined,
|
|
48
|
+
/* type */ tsRecord(STRING, NEVER),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (schema[root] && typeof schema[root] === "object") {
|
|
52
|
+
const rootT = performance.now();
|
|
53
|
+
const subTypes = ([] as ts.Node[]).concat(transformers[root](schema[root], ctx));
|
|
54
|
+
for (const subType of subTypes) {
|
|
55
|
+
if (ts.isTypeNode(subType)) {
|
|
56
|
+
if ((subType as ts.TypeLiteralNode).members?.length) {
|
|
57
|
+
type.push(
|
|
58
|
+
ctx.exportType
|
|
59
|
+
? ts.factory.createTypeAliasDeclaration(
|
|
60
|
+
/* modifiers */ tsModifiers({ export: true }),
|
|
61
|
+
/* name */ root,
|
|
62
|
+
/* typeParameters */ undefined,
|
|
63
|
+
/* type */ subType,
|
|
64
|
+
)
|
|
65
|
+
: ts.factory.createInterfaceDeclaration(
|
|
66
|
+
/* modifiers */ tsModifiers({ export: true }),
|
|
67
|
+
/* name */ root,
|
|
68
|
+
/* typeParameters */ undefined,
|
|
69
|
+
/* heritageClauses */ undefined,
|
|
70
|
+
/* members */ (subType as TypeLiteralNode).members,
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
debug(`${root} done`, "ts", performance.now() - rootT);
|
|
74
|
+
} else {
|
|
75
|
+
type.push(emptyObj);
|
|
76
|
+
debug(`${root} done (skipped)`, "ts", 0);
|
|
77
|
+
}
|
|
78
|
+
} else if (ts.isTypeAliasDeclaration(subType)) {
|
|
79
|
+
type.push(subType);
|
|
80
|
+
} else {
|
|
81
|
+
type.push(emptyObj);
|
|
82
|
+
debug(`${root} done (skipped)`, "ts", 0);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
type.push(emptyObj);
|
|
87
|
+
debug(`${root} done (skipped)`, "ts", 0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// inject
|
|
92
|
+
let hasOperations = false;
|
|
93
|
+
for (const injectedType of ctx.injectFooter) {
|
|
94
|
+
if (!hasOperations && (injectedType as InterfaceDeclaration)?.name?.escapedText === "operations") {
|
|
95
|
+
hasOperations = true;
|
|
96
|
+
}
|
|
97
|
+
type.push(injectedType);
|
|
98
|
+
}
|
|
99
|
+
if (!hasOperations) {
|
|
100
|
+
// if no operations created, inject empty operations type
|
|
101
|
+
type.push(
|
|
102
|
+
ts.factory.createTypeAliasDeclaration(
|
|
103
|
+
/* modifiers */ tsModifiers({ export: true }),
|
|
104
|
+
/* name */ "operations",
|
|
105
|
+
/* typeParameters */ undefined,
|
|
106
|
+
/* type */ tsRecord(STRING, NEVER),
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (ctx.makePathsEnum && schema.paths) {
|
|
112
|
+
type.push(makeApiPathsEnum(schema.paths));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return type;
|
|
116
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
import { UNKNOWN } from "../lib/ts.js";
|
|
3
|
+
import type { MediaTypeObject, TransformNodeOptions } from "../types.js";
|
|
4
|
+
import transformSchemaObject from "./schema-object.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Transform MediaTypeObject nodes (4.8.14)
|
|
8
|
+
* @see https://spec.openapis.org/oas/v3.1.0#media-type-object
|
|
9
|
+
*/
|
|
10
|
+
export default function transformMediaTypeObject(
|
|
11
|
+
mediaTypeObject: MediaTypeObject,
|
|
12
|
+
options: TransformNodeOptions,
|
|
13
|
+
): ts.TypeNode {
|
|
14
|
+
if (!mediaTypeObject.schema) {
|
|
15
|
+
return UNKNOWN;
|
|
16
|
+
}
|
|
17
|
+
return transformSchemaObject(mediaTypeObject.schema, options);
|
|
18
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { addJSDocComment, NEVER, oapiRef, QUESTION_TOKEN, tsModifiers, tsPropertyIndex } from "../lib/ts.js";
|
|
3
|
+
import { createRef } from "../lib/utils.js";
|
|
4
|
+
import type { OperationObject, RequestBodyObject, TransformNodeOptions } from "../types.js";
|
|
5
|
+
import { transformParametersArray } from "./parameters-array.js";
|
|
6
|
+
import transformRequestBodyObject from "./request-body-object.js";
|
|
7
|
+
import transformResponsesObject from "./responses-object.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Transform OperationObject nodes (4.8.10)
|
|
11
|
+
* @see https://spec.openapis.org/oas/v3.1.0#operation-object
|
|
12
|
+
*/
|
|
13
|
+
export default function transformOperationObject(
|
|
14
|
+
operationObject: OperationObject,
|
|
15
|
+
options: TransformNodeOptions,
|
|
16
|
+
): ts.TypeElement[] {
|
|
17
|
+
const type: ts.TypeElement[] = [];
|
|
18
|
+
|
|
19
|
+
// parameters
|
|
20
|
+
type.push(...transformParametersArray(operationObject.parameters ?? [], options));
|
|
21
|
+
|
|
22
|
+
// requestBody
|
|
23
|
+
if (operationObject.requestBody) {
|
|
24
|
+
const requestBodyType =
|
|
25
|
+
"$ref" in operationObject.requestBody
|
|
26
|
+
? oapiRef(operationObject.requestBody.$ref)
|
|
27
|
+
: transformRequestBodyObject(operationObject.requestBody, {
|
|
28
|
+
...options,
|
|
29
|
+
path: createRef([options.path, "requestBody"]),
|
|
30
|
+
});
|
|
31
|
+
const required = !!(
|
|
32
|
+
"$ref" in operationObject.requestBody
|
|
33
|
+
? options.ctx.resolve<RequestBodyObject>(operationObject.requestBody.$ref)
|
|
34
|
+
: operationObject.requestBody
|
|
35
|
+
)?.required;
|
|
36
|
+
const property = ts.factory.createPropertySignature(
|
|
37
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
38
|
+
/* name */ tsPropertyIndex("requestBody"),
|
|
39
|
+
/* questionToken */ required ? undefined : QUESTION_TOKEN,
|
|
40
|
+
/* type */ requestBodyType,
|
|
41
|
+
);
|
|
42
|
+
addJSDocComment(operationObject.requestBody, property);
|
|
43
|
+
type.push(property);
|
|
44
|
+
} else {
|
|
45
|
+
type.push(
|
|
46
|
+
ts.factory.createPropertySignature(
|
|
47
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
48
|
+
/* name */ tsPropertyIndex("requestBody"),
|
|
49
|
+
/* questionToken */ QUESTION_TOKEN,
|
|
50
|
+
/* type */ NEVER,
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// responses
|
|
56
|
+
type.push(
|
|
57
|
+
ts.factory.createPropertySignature(
|
|
58
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
59
|
+
/* name */ tsPropertyIndex("responses"),
|
|
60
|
+
/* questionToken */ undefined,
|
|
61
|
+
/* type */ transformResponsesObject(operationObject.responses ?? {}, options),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return type;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** inject an operation at the top level */
|
|
69
|
+
export function injectOperationObject(
|
|
70
|
+
operationId: string,
|
|
71
|
+
operationObject: OperationObject,
|
|
72
|
+
options: TransformNodeOptions,
|
|
73
|
+
): void {
|
|
74
|
+
// find or create top-level operations interface
|
|
75
|
+
let operations = options.ctx.injectFooter.find(
|
|
76
|
+
(node) => ts.isInterfaceDeclaration(node) && (node as ts.InterfaceDeclaration).name.text === "operations",
|
|
77
|
+
) as unknown as ts.InterfaceDeclaration;
|
|
78
|
+
if (!operations) {
|
|
79
|
+
operations = ts.factory.createInterfaceDeclaration(
|
|
80
|
+
/* modifiers */ tsModifiers({
|
|
81
|
+
export: true,
|
|
82
|
+
// important: do NOT make this immutable
|
|
83
|
+
}),
|
|
84
|
+
/* name */ ts.factory.createIdentifier("operations"),
|
|
85
|
+
/* typeParameters */ undefined,
|
|
86
|
+
/* heritageClauses */ undefined,
|
|
87
|
+
/* members */ [],
|
|
88
|
+
);
|
|
89
|
+
options.ctx.injectFooter.push(operations);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// inject operation object
|
|
93
|
+
const type = transformOperationObject(operationObject, options);
|
|
94
|
+
// @ts-expect-error this is OK to mutate
|
|
95
|
+
operations.members = ts.factory.createNodeArray([
|
|
96
|
+
...operations.members,
|
|
97
|
+
ts.factory.createPropertySignature(
|
|
98
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
99
|
+
/* name */ tsPropertyIndex(operationId),
|
|
100
|
+
/* questionToken */ undefined,
|
|
101
|
+
/* type */ ts.factory.createTypeLiteralNode(type),
|
|
102
|
+
),
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
import { STRING } from "../lib/ts.js";
|
|
3
|
+
import type { ParameterObject, TransformNodeOptions } from "../types.js";
|
|
4
|
+
import transformSchemaObject from "./schema-object.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Transform ParameterObject nodes (4.8.12)
|
|
8
|
+
* @see https://spec.openapis.org/oas/v3.1.0#parameter-object
|
|
9
|
+
*/
|
|
10
|
+
export default function transformParameterObject(
|
|
11
|
+
parameterObject: ParameterObject,
|
|
12
|
+
options: TransformNodeOptions,
|
|
13
|
+
): ts.TypeNode {
|
|
14
|
+
return parameterObject.schema ? transformSchemaObject(parameterObject.schema, options) : STRING; // assume a parameter is a string by default rather than "unknown"
|
|
15
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { addJSDocComment, NEVER, oapiRef, QUESTION_TOKEN, tsModifiers, tsPropertyIndex } from "../lib/ts.js";
|
|
3
|
+
import { createRef } from "../lib/utils.js";
|
|
4
|
+
import type { ParameterObject, ReferenceObject, TransformNodeOptions } from "../types.js";
|
|
5
|
+
import transformParameterObject from "./parameter-object.js";
|
|
6
|
+
|
|
7
|
+
// Regex to match path parameters in URL
|
|
8
|
+
const PATH_PARAM_RE = /\{([^}]+)\}/g;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a synthetic path parameter object from a parameter name
|
|
12
|
+
*/
|
|
13
|
+
function createPathParameter(paramName: string): ParameterObject {
|
|
14
|
+
return {
|
|
15
|
+
name: paramName,
|
|
16
|
+
in: "path",
|
|
17
|
+
required: true,
|
|
18
|
+
schema: { type: "string" },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract path parameters from a URL
|
|
24
|
+
*/
|
|
25
|
+
function extractPathParamsFromUrl(path: string): ParameterObject[] {
|
|
26
|
+
const params: ParameterObject[] = [];
|
|
27
|
+
const matches = path.match(PATH_PARAM_RE);
|
|
28
|
+
if (matches) {
|
|
29
|
+
for (const match of matches) {
|
|
30
|
+
const paramName = match.slice(1, -1);
|
|
31
|
+
params.push(createPathParameter(paramName));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return params;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Synthetic type. Array of (ParameterObject | ReferenceObject)s found in OperationObject and PathItemObject.
|
|
39
|
+
*/
|
|
40
|
+
export function transformParametersArray(
|
|
41
|
+
parametersArray: (ParameterObject | ReferenceObject)[],
|
|
42
|
+
options: TransformNodeOptions,
|
|
43
|
+
): ts.TypeElement[] {
|
|
44
|
+
const type: ts.TypeElement[] = [];
|
|
45
|
+
|
|
46
|
+
// Create a working copy of parameters array
|
|
47
|
+
const workingParameters = [...parametersArray];
|
|
48
|
+
|
|
49
|
+
// Generate path parameters if enabled
|
|
50
|
+
if (options.ctx.generatePathParams && options.path) {
|
|
51
|
+
const pathString = Array.isArray(options.path) ? options.path[0] : options.path;
|
|
52
|
+
if (typeof pathString === "string") {
|
|
53
|
+
const pathParams = extractPathParamsFromUrl(pathString);
|
|
54
|
+
// Only add path parameters that aren't already defined
|
|
55
|
+
for (const param of pathParams) {
|
|
56
|
+
const exists = workingParameters.some((p) => {
|
|
57
|
+
const resolved = "$ref" in p ? options.ctx.resolve<ParameterObject>(p.$ref) : p;
|
|
58
|
+
return resolved?.in === "path" && resolved?.name === param.name;
|
|
59
|
+
});
|
|
60
|
+
if (!exists) {
|
|
61
|
+
workingParameters.push(param);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// parameters
|
|
68
|
+
const paramType: ts.TypeElement[] = [];
|
|
69
|
+
for (const paramIn of ["query", "header", "path", "cookie"] as ParameterObject["in"][]) {
|
|
70
|
+
const paramLocType: ts.TypeElement[] = [];
|
|
71
|
+
let operationParameters = workingParameters.map((param) => ({
|
|
72
|
+
original: param,
|
|
73
|
+
resolved: "$ref" in param ? options.ctx.resolve<ParameterObject>(param.$ref) : param,
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// this is the only array type in the spec, so we have to one-off sort here
|
|
77
|
+
if (options.ctx.alphabetize) {
|
|
78
|
+
operationParameters.sort((a, b) => (a.resolved?.name ?? "").localeCompare(b.resolved?.name ?? ""));
|
|
79
|
+
}
|
|
80
|
+
if (options.ctx.excludeDeprecated) {
|
|
81
|
+
operationParameters = operationParameters.filter(
|
|
82
|
+
({ resolved }) => !resolved?.deprecated && !resolved?.schema?.deprecated,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
for (const { original, resolved } of operationParameters) {
|
|
86
|
+
if (resolved?.in !== paramIn) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
let optional: ts.QuestionToken | undefined;
|
|
90
|
+
if (paramIn !== "path" && !(resolved as ParameterObject).required) {
|
|
91
|
+
optional = QUESTION_TOKEN;
|
|
92
|
+
}
|
|
93
|
+
const subType =
|
|
94
|
+
"$ref" in original
|
|
95
|
+
? oapiRef(original.$ref, resolved)
|
|
96
|
+
: transformParameterObject(resolved as ParameterObject, {
|
|
97
|
+
...options,
|
|
98
|
+
path: createRef([options.path, "parameters", resolved.in, resolved.name]),
|
|
99
|
+
});
|
|
100
|
+
const property = ts.factory.createPropertySignature(
|
|
101
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
102
|
+
/* name */ tsPropertyIndex(resolved?.name),
|
|
103
|
+
/* questionToken */ optional,
|
|
104
|
+
/* type */ subType,
|
|
105
|
+
);
|
|
106
|
+
addJSDocComment(resolved, property);
|
|
107
|
+
paramLocType.push(property);
|
|
108
|
+
}
|
|
109
|
+
const allOptional = paramLocType.every((node) => !!node.questionToken);
|
|
110
|
+
paramType.push(
|
|
111
|
+
ts.factory.createPropertySignature(
|
|
112
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
113
|
+
/* name */ tsPropertyIndex(paramIn),
|
|
114
|
+
/* questionToken */ allOptional || !paramLocType.length ? QUESTION_TOKEN : undefined,
|
|
115
|
+
/* type */ paramLocType.length ? ts.factory.createTypeLiteralNode(paramLocType) : NEVER,
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
type.push(
|
|
120
|
+
ts.factory.createPropertySignature(
|
|
121
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
122
|
+
/* name */ tsPropertyIndex("parameters"),
|
|
123
|
+
/* questionToken */ !paramType.length ? QUESTION_TOKEN : undefined,
|
|
124
|
+
/* type */ paramType.length ? ts.factory.createTypeLiteralNode(paramType) : NEVER,
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return type;
|
|
129
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { addJSDocComment, NEVER, oapiRef, QUESTION_TOKEN, tsModifiers, tsPropertyIndex } from "../lib/ts.js";
|
|
3
|
+
import { createRef } from "../lib/utils.js";
|
|
4
|
+
import type {
|
|
5
|
+
OperationObject,
|
|
6
|
+
ParameterObject,
|
|
7
|
+
PathItemObject,
|
|
8
|
+
ReferenceObject,
|
|
9
|
+
TransformNodeOptions,
|
|
10
|
+
} from "../types.js";
|
|
11
|
+
import transformOperationObject, { injectOperationObject } from "./operation-object.js";
|
|
12
|
+
import { transformParametersArray } from "./parameters-array.js";
|
|
13
|
+
|
|
14
|
+
export type Method = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Transform PathItem nodes (4.8.9)
|
|
18
|
+
* @see https://spec.openapis.org/oas/v3.1.0#path-item-object
|
|
19
|
+
*/
|
|
20
|
+
export default function transformPathItemObject(pathItem: PathItemObject, options: TransformNodeOptions): ts.TypeNode {
|
|
21
|
+
const type: ts.TypeElement[] = [];
|
|
22
|
+
|
|
23
|
+
// parameters
|
|
24
|
+
type.push(
|
|
25
|
+
...transformParametersArray(pathItem.parameters ?? [], {
|
|
26
|
+
...options,
|
|
27
|
+
path: createRef([options.path, "parameters"]),
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// methods
|
|
32
|
+
for (const method of ["get", "put", "post", "delete", "options", "head", "patch", "trace"] as Method[]) {
|
|
33
|
+
const operationObject = pathItem[method];
|
|
34
|
+
if (
|
|
35
|
+
!operationObject ||
|
|
36
|
+
(options.ctx.excludeDeprecated &&
|
|
37
|
+
("$ref" in operationObject ? options.ctx.resolve<OperationObject>(operationObject.$ref) : operationObject)
|
|
38
|
+
?.deprecated)
|
|
39
|
+
) {
|
|
40
|
+
type.push(
|
|
41
|
+
ts.factory.createPropertySignature(
|
|
42
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
43
|
+
/* name */ tsPropertyIndex(method),
|
|
44
|
+
/* questionToken */ QUESTION_TOKEN,
|
|
45
|
+
/* type */ NEVER,
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// fold top-level PathItem parameters into method-level, with the latter overriding the former
|
|
52
|
+
const keyedParameters: Record<string, ParameterObject | ReferenceObject> = {};
|
|
53
|
+
if (!("$ref" in operationObject)) {
|
|
54
|
+
// important: OperationObject parameters come last, and will override any conflicts with PathItem parameters
|
|
55
|
+
for (const parameter of [...(pathItem.parameters ?? []), ...(operationObject.parameters ?? [])]) {
|
|
56
|
+
// fix: #1798, use unique key
|
|
57
|
+
const name =
|
|
58
|
+
"$ref" in parameter
|
|
59
|
+
? `${options.ctx.resolve<ParameterObject>(parameter.$ref)?.in}-${options.ctx.resolve<ParameterObject>(parameter.$ref)?.name}`
|
|
60
|
+
: `${parameter.in}-${parameter.name}`;
|
|
61
|
+
if (name) {
|
|
62
|
+
keyedParameters[name] = parameter;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let operationType: ts.TypeNode;
|
|
68
|
+
if ("$ref" in operationObject) {
|
|
69
|
+
operationType = oapiRef(operationObject.$ref);
|
|
70
|
+
}
|
|
71
|
+
// if operationId exists, move into an `operations` export and pass the reference in here
|
|
72
|
+
else if (operationObject.operationId) {
|
|
73
|
+
// workaround for issue caused by redocly ref parsing: https://github.com/openapi-ts/openapi-typescript/issues/1542
|
|
74
|
+
const operationId = operationObject.operationId.replace(HASH_RE, "/");
|
|
75
|
+
operationType = oapiRef(createRef(["operations", operationId]));
|
|
76
|
+
injectOperationObject(
|
|
77
|
+
operationId,
|
|
78
|
+
{ ...operationObject, parameters: Object.values(keyedParameters) },
|
|
79
|
+
{ ...options, path: createRef([options.path, method]) },
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
operationType = ts.factory.createTypeLiteralNode(
|
|
83
|
+
transformOperationObject(
|
|
84
|
+
{ ...operationObject, parameters: Object.values(keyedParameters) },
|
|
85
|
+
{ ...options, path: createRef([options.path, method]) },
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const property = ts.factory.createPropertySignature(
|
|
90
|
+
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
|
|
91
|
+
/* name */ tsPropertyIndex(method),
|
|
92
|
+
/* questionToken */ undefined,
|
|
93
|
+
/* type */ operationType,
|
|
94
|
+
);
|
|
95
|
+
addJSDocComment(operationObject, property);
|
|
96
|
+
type.push(property);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return ts.factory.createTypeLiteralNode(type);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const HASH_RE = /#/g;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
import { tsEnum } from "../lib/ts.js";
|
|
3
|
+
import { getEntries } from "../lib/utils.js";
|
|
4
|
+
import type { PathsObject } from "../types.js";
|
|
5
|
+
|
|
6
|
+
export default function makeApiPathsEnum(pathsObject: PathsObject): ts.EnumDeclaration {
|
|
7
|
+
const enumKeys = [];
|
|
8
|
+
const enumMetaData = [];
|
|
9
|
+
|
|
10
|
+
for (const [url, pathItemObject] of getEntries(pathsObject)) {
|
|
11
|
+
for (const [method, operation] of Object.entries(pathItemObject)) {
|
|
12
|
+
if (!["get", "put", "post", "delete", "options", "head", "patch", "trace"].includes(method)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Generate a name from the operation ID
|
|
17
|
+
let pathName: string;
|
|
18
|
+
if (operation.operationId) {
|
|
19
|
+
pathName = operation.operationId;
|
|
20
|
+
} else {
|
|
21
|
+
// If the operation ID is not present, construct a name from the method and path
|
|
22
|
+
pathName = (method + url)
|
|
23
|
+
.split("/")
|
|
24
|
+
.map((part) => {
|
|
25
|
+
const capitalised = part.charAt(0).toUpperCase() + part.slice(1);
|
|
26
|
+
|
|
27
|
+
// Remove any characters not allowed as enum keys, and attempt to remove
|
|
28
|
+
// named parameters.
|
|
29
|
+
return capitalised.replace(/{.*}|:.*|[^a-zA-Z\d_]+/, "");
|
|
30
|
+
})
|
|
31
|
+
.join("");
|
|
32
|
+
}
|
|
33
|
+
enumKeys.push(url);
|
|
34
|
+
enumMetaData.push({
|
|
35
|
+
name: pathName,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return tsEnum("ApiPaths", enumKeys, enumMetaData, {
|
|
41
|
+
export: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { addJSDocComment, oapiRef, stringToAST, tsModifiers, tsPropertyIndex } from "../lib/ts.js";
|
|
4
|
+
import { createRef, debug, getEntries } from "../lib/utils.js";
|
|
5
|
+
import type {
|
|
6
|
+
GlobalContext,
|
|
7
|
+
OperationObject,
|
|
8
|
+
ParameterObject,
|
|
9
|
+
PathItemObject,
|
|
10
|
+
PathsObject,
|
|
11
|
+
ReferenceObject,
|
|
12
|
+
} from "../types.js";
|
|
13
|
+
import transformPathItemObject, { type Method } from "./path-item-object.js";
|
|
14
|
+
|
|
15
|
+
const PATH_PARAM_RE = /\{[^}]+\}/g;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Transform the PathsObject node (4.8.8)
|
|
19
|
+
* @see https://spec.openapis.org/oas/v3.1.0#operation-object
|
|
20
|
+
*/
|
|
21
|
+
export default function transformPathsObject(pathsObject: PathsObject, ctx: GlobalContext): ts.TypeNode {
|
|
22
|
+
const type: ts.TypeElement[] = [];
|
|
23
|
+
for (const [url, pathItemObject] of getEntries(pathsObject, ctx)) {
|
|
24
|
+
if (!pathItemObject || typeof pathItemObject !== "object") {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const pathT = performance.now();
|
|
29
|
+
|
|
30
|
+
// handle $ref
|
|
31
|
+
if ("$ref" in pathItemObject) {
|
|
32
|
+
const property = ts.factory.createPropertySignature(
|
|
33
|
+
/* modifiers */ tsModifiers({ readonly: ctx.immutable }),
|
|
34
|
+
/* name */ tsPropertyIndex(url),
|
|
35
|
+
/* questionToken */ undefined,
|
|
36
|
+
/* type */ oapiRef(pathItemObject.$ref),
|
|
37
|
+
);
|
|
38
|
+
addJSDocComment(pathItemObject, property);
|
|
39
|
+
type.push(property);
|
|
40
|
+
} else {
|
|
41
|
+
const pathItemType = transformPathItemObject(pathItemObject, {
|
|
42
|
+
path: createRef(["paths", url]),
|
|
43
|
+
ctx,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// pathParamsAsTypes
|
|
47
|
+
if (ctx.pathParamsAsTypes && url.includes("{")) {
|
|
48
|
+
const pathParams = extractPathParams(pathItemObject, ctx);
|
|
49
|
+
const matches = url.match(PATH_PARAM_RE);
|
|
50
|
+
let rawPath = `\`${url}\``;
|
|
51
|
+
if (matches) {
|
|
52
|
+
for (const match of matches) {
|
|
53
|
+
const paramName = match.slice(1, -1);
|
|
54
|
+
const param = pathParams[paramName];
|
|
55
|
+
switch (param?.schema?.type) {
|
|
56
|
+
case "number":
|
|
57
|
+
case "integer":
|
|
58
|
+
rawPath = rawPath.replace(match, "${number}");
|
|
59
|
+
break;
|
|
60
|
+
case "boolean":
|
|
61
|
+
rawPath = rawPath.replace(match, "${boolean}");
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
rawPath = rawPath.replace(match, "${string}");
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// note: creating a string template literal’s AST manually is hard!
|
|
69
|
+
// just pass an arbitrary string to TS
|
|
70
|
+
const pathType = (stringToAST(rawPath)[0] as any)?.expression;
|
|
71
|
+
if (pathType) {
|
|
72
|
+
type.push(
|
|
73
|
+
ts.factory.createIndexSignature(
|
|
74
|
+
/* modifiers */ tsModifiers({ readonly: ctx.immutable }),
|
|
75
|
+
/* parameters */ [
|
|
76
|
+
ts.factory.createParameterDeclaration(
|
|
77
|
+
/* modifiers */ undefined,
|
|
78
|
+
/* dotDotDotToken */ undefined,
|
|
79
|
+
/* name */ "path",
|
|
80
|
+
/* questionToken */ undefined,
|
|
81
|
+
/* type */ pathType,
|
|
82
|
+
/* initializer */ undefined,
|
|
83
|
+
),
|
|
84
|
+
],
|
|
85
|
+
/* type */ pathItemType,
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type.push(
|
|
94
|
+
ts.factory.createPropertySignature(
|
|
95
|
+
/* modifiers */ tsModifiers({ readonly: ctx.immutable }),
|
|
96
|
+
/* name */ tsPropertyIndex(url),
|
|
97
|
+
/* questionToken */ undefined,
|
|
98
|
+
/* type */ pathItemType,
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
debug(`Transformed path "${url}"`, "ts", performance.now() - pathT);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return ts.factory.createTypeLiteralNode(type);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function extractPathParams(pathItemObject: PathItemObject, ctx: GlobalContext) {
|
|
110
|
+
const params: Record<string, ParameterObject> = {};
|
|
111
|
+
for (const p of pathItemObject.parameters ?? []) {
|
|
112
|
+
const resolved = "$ref" in p && p.$ref ? ctx.resolve<ParameterObject>(p.$ref) : (p as ParameterObject);
|
|
113
|
+
if (resolved && resolved.in === "path") {
|
|
114
|
+
params[resolved.name] = resolved;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const method of ["get", "put", "post", "delete", "options", "head", "patch", "trace"] as Method[]) {
|
|
118
|
+
if (!(method in pathItemObject)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const resolvedMethod = (pathItemObject[method] as ReferenceObject).$ref
|
|
122
|
+
? ctx.resolve<OperationObject>((pathItemObject[method] as ReferenceObject).$ref)
|
|
123
|
+
: (pathItemObject[method] as OperationObject);
|
|
124
|
+
if (resolvedMethod?.parameters) {
|
|
125
|
+
for (const p of resolvedMethod.parameters) {
|
|
126
|
+
const resolvedParam = "$ref" in p && p.$ref ? ctx.resolve<ParameterObject>(p.$ref) : (p as ParameterObject);
|
|
127
|
+
if (resolvedParam && resolvedParam.in === "path") {
|
|
128
|
+
params[resolvedParam.name] = resolvedParam;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return params;
|
|
134
|
+
}
|