openapi-typescript
Version:
Convert OpenAPI 3.0 & 3.1 schemas to TypeScript
213 lines (212 loc) • 10.1 kB
JavaScript
import { URL } from "node:url";
import load, { resolveSchema, VIRTUAL_JSON_URL } from "./load.js";
import { transformSchema } from "./transform/index.js";
import transformMediaTypeObject from "./transform/media-type-object.js";
import transformOperationObject from "./transform/operation-object.js";
import transformParameterObject from "./transform/parameter-object.js";
import transformParameterObjectArray from "./transform/parameter-object-array.js";
import transformRequestBodyObject from "./transform/request-body-object.js";
import transformResponseObject from "./transform/response-object.js";
import transformSchemaObject from "./transform/schema-object.js";
import transformSchemaObjectMap from "./transform/schema-object-map.js";
import { error, escObjKey, getDefaultFetch, getEntries, getSchemaObjectComment, indent } from "./utils.js";
export * from "./types.js";
const EMPTY_OBJECT_RE = /^\s*\{?\s*\}?\s*$/;
export const COMMENT_HEADER = `/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
`;
async function openapiTS(schema, options = {}) {
const ctx = {
additionalProperties: options.additionalProperties ?? false,
alphabetize: options.alphabetize ?? false,
cwd: options.cwd ?? new URL(`file://${process.cwd()}/`),
defaultNonNullable: options.defaultNonNullable ?? false,
discriminators: {},
transform: typeof options.transform === "function" ? options.transform : undefined,
postTransform: typeof options.postTransform === "function" ? options.postTransform : undefined,
immutableTypes: options.immutableTypes ?? false,
emptyObjectsUnknown: options.emptyObjectsUnknown ?? false,
indentLv: 0,
operations: {},
pathParamsAsTypes: options.pathParamsAsTypes ?? false,
parameters: {},
silent: options.silent ?? false,
supportArrayLength: options.supportArrayLength ?? false,
excludeDeprecated: options.excludeDeprecated ?? false,
};
const allSchemas = {};
const schemaURL = typeof schema === "string" ? resolveSchema(schema) : schema;
let rootURL = schemaURL;
const isInlineSchema = typeof schema !== "string" && schema instanceof URL === false;
if (isInlineSchema) {
if (ctx.cwd) {
if (ctx.cwd instanceof URL) {
rootURL = ctx.cwd;
}
else if (typeof ctx.cwd === "string") {
rootURL = new URL(ctx.cwd, `file://${process.cwd()}/`);
}
rootURL = new URL("root.yaml", rootURL);
}
else {
rootURL = new URL(VIRTUAL_JSON_URL);
}
}
await load(schemaURL, {
...ctx,
auth: options.auth,
schemas: allSchemas,
rootURL,
urlCache: new Set(),
httpHeaders: options.httpHeaders,
httpMethod: options.httpMethod,
fetch: options.fetch ?? getDefaultFetch(),
});
for (const k of Object.keys(allSchemas)) {
const subschema = allSchemas[k];
if (typeof subschema.schema.swagger === "string") {
error("Swagger 2.0 and older no longer supported. Please use v5.");
process.exit(1);
}
if (subschema.hint === "OpenAPI3" && typeof subschema.schema.openapi === "string") {
if (parseInt(subschema.schema.openapi) !== 3) {
error(`Unsupported OpenAPI version "${subschema.schema.openapi}". Only 3.x is supported.`);
process.exit(1);
}
}
}
const output = [];
if ("commentHeader" in options) {
if (options.commentHeader)
output.push(options.commentHeader);
}
else {
output.push(COMMENT_HEADER);
}
if (options.inject)
output.push(options.inject);
const rootTypes = transformSchema(allSchemas["."].schema, ctx);
for (const k of Object.keys(rootTypes)) {
if (rootTypes[k] && !EMPTY_OBJECT_RE.test(rootTypes[k])) {
output.push(options.exportType ? `export type ${k} = ${rootTypes[k]};` : `export interface ${k} ${rootTypes[k]}`, "");
}
else {
output.push(`export type ${k} = Record<string, never>;`, "");
}
delete rootTypes[k];
delete allSchemas["."];
}
const externalKeys = Object.keys(allSchemas);
if (externalKeys.length) {
let indentLv = 0;
output.push(options.exportType ? "export type external = {" : "export interface external {");
externalKeys.sort((a, b) => a.localeCompare(b, "en", { numeric: true }));
indentLv++;
for (const subschemaID of externalKeys) {
const subschema = allSchemas[subschemaID];
const key = escObjKey(subschemaID);
const path = `${subschemaID}#`;
let subschemaOutput = "";
let comment;
switch (subschema.hint) {
case "OpenAPI3": {
const subschemaTypes = transformSchema(subschema.schema, { ...ctx, indentLv: indentLv + 1 });
if (!Object.keys(subschemaTypes).length)
break;
output.push(indent(`${key}: {`, indentLv));
indentLv++;
for (const [k, v] of getEntries(subschemaTypes, options.alphabetize, options.excludeDeprecated)) {
if (EMPTY_OBJECT_RE.test(v))
output.push(indent(`${escObjKey(k)}: Record<string, never>;`, indentLv));
else
output.push(indent(`${escObjKey(k)}: ${v};`, indentLv));
}
indentLv--;
output.push(indent("};", indentLv));
break;
}
case "MediaTypeObject": {
subschemaOutput = transformMediaTypeObject(subschema.schema, { path, ctx: { ...ctx, indentLv } });
break;
}
case "OperationObject": {
comment = getSchemaObjectComment(subschema.schema, indentLv);
subschemaOutput = transformOperationObject(subschema.schema, { path, ctx: { ...ctx, indentLv } });
break;
}
case "ParameterObject": {
subschemaOutput = transformParameterObject(subschema.schema, { path, ctx: { ...ctx, indentLv } });
break;
}
case "ParameterObject[]": {
if (typeof subschema.schema === "object" && ("schema" in subschema.schema || "type" in subschema.schema)) {
subschemaOutput = transformSchemaObject(subschema.schema, { path, ctx: { ...ctx, indentLv } });
}
else {
subschemaOutput += "{\n";
indentLv++;
subschemaOutput += transformParameterObjectArray(subschema.schema, { path, ctx: { ...ctx, indentLv } });
subschemaOutput += "\n";
indentLv--;
subschemaOutput += indent("};", indentLv);
}
break;
}
case "RequestBodyObject": {
subschemaOutput = `${transformRequestBodyObject(subschema.schema, { path, ctx: { ...ctx, indentLv } })};`;
break;
}
case "ResponseObject": {
subschemaOutput = `${transformResponseObject(subschema.schema, { path, ctx: { ...ctx, indentLv } })};`;
break;
}
case "SchemaMap": {
subschemaOutput = `${transformSchemaObjectMap(subschema.schema, { path, ctx: { ...ctx, indentLv } })};`;
break;
}
case "SchemaObject": {
subschemaOutput = `${transformSchemaObject(subschema.schema, { path, ctx: { ...ctx, indentLv } })};`;
break;
}
default: {
error(`Could not resolve subschema ${subschemaID}. Unknown type "${subschema.hint}".`);
process.exit(1);
}
}
if (subschemaOutput && !EMPTY_OBJECT_RE.test(subschemaOutput)) {
if (comment)
output.push(indent(comment, indentLv));
output.push(indent(`${key}: ${subschemaOutput}`, indentLv));
}
delete allSchemas[subschemaID];
}
indentLv--;
output.push(indent(`}${options.exportType ? ";" : ""}`, indentLv), "");
}
else {
output.push(`export type external = Record<string, never>;`, "");
}
if (Object.keys(ctx.operations).length) {
output.push(options.exportType ? "export type operations = {" : "export interface operations {", "");
for (const [key, { operationType, comment }] of Object.entries(ctx.operations)) {
if (comment)
output.push(indent(comment, 1));
output.push(indent(`${escObjKey(key)}: ${operationType};`, 1));
}
output.push(`}${options.exportType ? ";" : ""}`, "");
}
else {
output.push(`export type operations = Record<string, never>;`, "");
}
if (output.join("\n").includes("OneOf")) {
output.splice(1, 0, "/** OneOf type helpers */", "type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };", "type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;", "type OneOf<T extends any[]> = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR<A, B>, ...Rest]> : never;", "");
}
if (output.join("\n").includes("WithRequired")) {
output.splice(1, 0, "/** WithRequired type helpers */", "type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };", "");
}
return output.join("\n");
}
export default openapiTS;
//# sourceMappingURL=index.js.map