You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP

openapi-typescript

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

openapi-typescript - npm Package Compare versions

Comparing version

to
7.0.0-next.0

#!/usr/bin/env node
import { loadConfig, findConfig, createConfig } from "@redocly/openapi-core";
import fs from "node:fs";
import path from "node:path";
import { URL } from "node:url";
import glob from "fast-glob";
import parser from "yargs-parser";
import openapiTS from "../dist/index.js";
import { c, error } from "../dist/utils.js";
import openapiTS, {
astToString,
c,
COMMENT_HEADER,
error,
formatTime,
warn,
} from "../dist/index.js";
/* eslint-disable no-console */
const HELP = `Usage

@@ -15,18 +22,16 @@ $ openapi-typescript [input] [options]

Options
--help Display this
--version Display the version
--output, -o Specify output file (default: stdout)
--auth (optional) Provide an authentication token for private URL
--headersObject, -h (optional) Provide a JSON object as string of HTTP headers for remote schema request
--header, -x (optional) Provide an array of or singular headers as an alternative to a JSON object. Each header must follow the key: value pattern
--httpMethod, -m (optional) Provide the HTTP Verb/Method for fetching a schema from a remote URL
--export-type, -t (optional) Export "type" instead of "interface"
--immutable-types (optional) Generates immutable types (readonly properties and readonly array)
--additional-properties (optional) Allow arbitrary properties for all schema objects without "additionalProperties: false"
--empty-objects-unknown (optional) Allow arbitrary properties for schema objects with no specified properties, and no specified "additionalProperties"
--default-non-nullable (optional) If a schema object has a default value set, don’t mark it as nullable
--support-array-length (optional) Generate tuples using array minItems / maxItems
--path-params-as-types (optional) Substitute path parameter names with their respective types
--alphabetize (optional) Sort types alphabetically
--exclude-deprecated (optional) Exclude deprecated fields from types
--help Display this
--version Display the version
--redoc [path], -c Specify path to Redocly config (default: redocly.yaml)
--output, -o Specify output file (if not specified in redocly.yaml)
--enum Export true TS enums instead of unions
--export-type, -t Export top-level \`type\` instead of \`interface\`
--immutable Generate readonly types
--additional-properties Treat schema objects as if \`additionalProperties: true\` is set
--empty-objects-unknown Generate \`unknown\` instead of \`Record<string, never>\` for empty objects
--default-non-nullable Set to \`false\` to ignore default values when generating non-nullable types
--array-length Generate tuples using array minItems / maxItems
--path-params-as-types Convert paths to template literal types
--alphabetize Sort object keys alphabetically
--exclude-deprecated Exclude deprecated types
`;

@@ -37,105 +42,85 @@

const CWD = new URL(`file://${process.cwd()}/`);
const EXT_RE = /\.[^.]+$/i;
const HTTP_RE = /^https?:\/\//;
const timeStart = process.hrtime();
const timeStart = performance.now();
const [, , ...args] = process.argv;
if (args.includes("-ap")) errorAndExit(`The -ap alias has been deprecated. Use "--additional-properties" instead.`);
if (args.includes("-it")) errorAndExit(`The -it alias has been deprecated. Use "--immutable-types" instead.`);
if (args.includes("-ap")) {
errorAndExit(
`The -ap alias has been deprecated. Use "--additional-properties" instead.`,
);
}
if (args.includes("--immutable-types")) {
errorAndExit(`The --immutable-types flag has been renamed to "--immutable".`);
}
if (args.includes("--support-array-length")) {
errorAndExit(
`The --support-array-length flag has been renamed to "--array-length".`,
);
}
if (args.includes("-it")) {
errorAndExit(
`The -it alias has been deprecated. Use "--immutable-types" instead.`,
);
}
const flags = parser(args, {
array: ["header"],
boolean: [
"help",
"version",
"additionalProperties",
"alphabetize",
"arrayLength",
"contentNever",
"defaultNonNullable",
"emptyObjectsUnknown",
"immutableTypes",
"contentNever",
"enum",
"excludeDeprecated",
"exportType",
"supportArrayLength",
"help",
"immutable",
"pathParamsAsTypes",
"alphabetize",
"excludeDeprecated",
],
string: ["auth", "header", "headersObject", "httpMethod"],
string: ["output", "redoc"],
alias: {
header: ["x"],
redoc: ["c"],
exportType: ["t"],
headersObject: ["h"],
httpMethod: ["m"],
output: ["o"],
},
default: {
httpMethod: "GET",
},
});
async function generateSchema(pathToSpec) {
const output = flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
/**
* @param {string | URL} schema
* @param {@type import('@redocly/openapi-core').Config} redoc
*/
async function generateSchema(schema, { redoc, silent = false }) {
return `${COMMENT_HEADER}${astToString(
await openapiTS(schema, {
additionalProperties: flags.additionalProperties,
alphabetize: flags.alphabetize,
arrayLength: flags.arrayLength,
contentNever: flags.contentNever,
defaultNonNullable: flags.defaultNonNullable,
emptyObjectsUnknown: flags.emptyObjectsUnknown,
enum: flags.enum,
excludeDeprecated: flags.excludeDeprecated,
exportType: flags.exportType,
immutable: flags.immutable,
pathParamsAsTypes: flags.pathParamsAsTypes,
redoc,
silent,
}),
)}`;
}
// Parse incoming headers from CLI flags
let httpHeaders = {};
/** pretty-format error message but also throw */
function errorAndExit(message) {
error(message);
throw new Error(message);
}
// prefer --headersObject if specified
if (flags.headersObject) {
httpHeaders = JSON.parse(flags.headersObject); // note: this will generate a recognizable error for the user to act on
}
// otherwise, parse --header
else if (Array.isArray(flags.header)) {
flags.header.forEach((header) => {
const firstColon = header.indexOf(":");
const k = header.substring(0, firstColon).trim();
const v = header.substring(firstColon + 1).trim();
httpHeaders[k] = v;
});
}
// generate schema
const result = await openapiTS(pathToSpec, {
additionalProperties: flags.additionalProperties,
emptyObjectsUnknown: flags.emptyObjectsUnknown,
auth: flags.auth,
defaultNonNullable: flags.defaultNonNullable,
immutableTypes: flags.immutableTypes,
contentNever: flags.contentNever,
silent: output === OUTPUT_STDOUT,
version: flags.version,
httpHeaders,
httpMethod: flags.httpMethod,
exportType: flags.exportType,
supportArrayLength: flags.supportArrayLength,
pathParamsAsTypes: flags.pathParamsAsTypes,
alphabetize: flags.alphabetize,
excludeDeprecated: flags.excludeDeprecated,
});
// output
if (output === OUTPUT_FILE) {
let outputFilePath = new URL(flags.output, CWD); // note: may be directory
const isDir = fs.existsSync(outputFilePath) && fs.lstatSync(outputFilePath).isDirectory();
if (isDir) {
if (typeof flags.output === "string" && !flags.output.endsWith("/")) {
outputFilePath = new URL(`${flags.output}/`, CWD);
}
const filename = pathToSpec.replace(EXT_RE, ".ts");
const originalOutputFilePath = outputFilePath;
outputFilePath = new URL(filename, originalOutputFilePath);
if (outputFilePath.protocol !== "file:") {
outputFilePath = new URL(outputFilePath.host.replace(EXT_RE, ".ts"), originalOutputFilePath);
}
}
fs.writeFileSync(outputFilePath, result, "utf8");
const timeEnd = process.hrtime(timeStart);
const time = timeEnd[0] + Math.round(timeEnd[1] / 1e6);
console.log(`🚀 ${c.green(`${pathToSpec} → ${c.bold(outputFilePath)}`)} ${c.dim(`[${time}ms]`)}`);
} else {
process.stdout.write(result);
// if stdout, (still) don’t log anything to console!
}
return result;
function done(input, output, time) {
// final console output
console.log(
`🚀 ${c.green(`${input} → ${c.bold(output)}`)} ${c.dim(
`[${formatTime(time)}]`,
)}`,
);
}

@@ -148,3 +133,5 @@

}
const packageJSON = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
const packageJSON = JSON.parse(
fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
);
if ("version" in flags) {

@@ -155,57 +142,87 @@ console.info(`v${packageJSON.version}`);

let output = flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOUT
let outputFile = new URL(flags.output, CWD);
let outputDir = new URL(".", outputFile);
const outputType = flags.output ? OUTPUT_FILE : OUTPUT_STDOUT; // FILE or STDOU
if (outputType !== OUTPUT_STDOUT) {
console.info(`✨ ${c.bold(`openapi-typescript ${packageJSON.version}`)}`);
}
if (output === OUTPUT_FILE) console.info(`✨ ${c.bold(`openapi-typescript ${packageJSON.version}`)}`); // only log if we’re NOT writing to stdout
const input = flags._[0];
const pathToSpec = flags._[0];
// load Redocly config
const maybeRedoc = findConfig(
flags.redoc ? path.dirname(flags.redoc) : undefined,
);
const redoc = maybeRedoc
? await loadConfig({ configPath: maybeRedoc })
: createConfig({}, { extends: ["minimal"] });
// handle stdin schema, exit
if (!pathToSpec) {
if (output !== "." && output === OUTPUT_FILE) fs.mkdirSync(outputDir, { recursive: true });
await generateSchema(process.stdin);
return;
// handle Redoc APIs
const hasRedoclyApis = Object.keys(redoc?.apis ?? {}).length > 0;
if (hasRedoclyApis) {
if (input) {
warn(
"APIs are specified both in Redocly Config and CLI argument. Only using Redocly config.",
);
}
await Promise.all(
Object.entries(redoc.apis).map(async ([name, api]) => {
const configRoot = redoc?.configFile
? new URL(`file://${redoc.configFile}`)
: CWD;
if (!api["openapi-ts"]?.output) {
errorAndExit(
`API ${name} is missing an \`openapi-ts.output\` key. See https://openapi-ts.pages.dev/cli/#multiple-schemas.`,
);
}
const result = await generateSchema(new URL(api.root, configRoot), {
redoc, // TODO: merge API overrides better?
});
const outFile = new URL(api["openapi-ts"].output, configRoot);
fs.mkdirSync(new URL(".", outFile), { recursive: true });
fs.writeFileSync(outFile, result, "utf8");
done(name, api.root, performance.now() - timeStart);
}),
);
}
// handle remote schema, exit
if (HTTP_RE.test(pathToSpec)) {
if (output !== "." && output === OUTPUT_FILE) fs.mkdirSync(outputDir, { recursive: true });
await generateSchema(pathToSpec);
return;
// handle stdin
else if (!input) {
const result = await generateSchema(process.stdin, {
redoc,
silent: outputType === OUTPUT_STDOUT,
});
if (outputType === OUTPUT_STDOUT) {
// if stdout, (still) don’t log anything to console!
process.stdout.write(result);
} else {
const outFile = new URL(flags.output, CWD);
fs.mkdirSync(new URL(".", outFile), { recursive: true });
fs.writeFileSync(outFile, result, "utf8");
done("stdin", flags.output, performance.now() - timeStart);
}
}
// handle local schema(s)
const inputSpecPaths = await glob(pathToSpec);
const isGlob = inputSpecPaths.length > 1;
const isDirUrl = outputDir.pathname === outputFile.pathname;
const isFile = fs.existsSync(outputDir) && fs.lstatSync(outputDir).isFile();
// error: no matches for glob
if (inputSpecPaths.length === 0) {
error(`Could not find any specs matching "${pathToSpec}". Please check that the path is correct.`);
process.exit(1);
// handle single file
else {
// throw error on glob
if (input.includes("*")) {
errorAndExit(
`Globbing has been deprecated in favor of redocly.yaml’s \`apis\` keys. See https://openapi-ts.pages.dev/cli/#multiple-schemas`,
);
}
const result = await generateSchema(new URL(input, CWD), {
redoc,
silent: outputType === OUTPUT_STDOUT,
});
if (outputType === OUTPUT_STDOUT) {
// if stdout, (still) don’t log anything to console!
process.stdout.write(result);
} else {
const outFile = new URL(flags.output, CWD);
fs.mkdirSync(new URL(".", outFile), { recursive: true });
fs.writeFileSync(outFile, result, "utf8");
done(input, flags.output, performance.now() - timeStart);
}
}
// error: tried to glob output to single file
if (isGlob && output === OUTPUT_FILE && (isFile || !isDirUrl)) {
error(`Expected directory for --output if using glob patterns. Received "${flags.output}".`);
process.exit(1);
}
// generate schema(s) in parallel
await Promise.all(
inputSpecPaths.map(async (specPath) => {
if (flags.output !== "." && output === OUTPUT_FILE) {
if (isGlob || isDirUrl) {
fs.mkdirSync(new URL(path.dirname(specPath), outputDir), { recursive: true }); // recursively make parent dirs
} else {
fs.mkdirSync(outputDir, { recursive: true }); // recursively make parent dirs
}
}
await generateSchema(specPath);
}),
);
}
main();
# openapi-typescript
## 7.0.0
### Major Changes
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ **Breaking**: The Node.js API now returns the TypeScript AST for the main method as well as `transform()` and `postTransform()`. To migrate, you’ll have to use the `typescript` compiler API:
```diff
+ import ts from "typescript";
+ const DATE = ts.factory.createIdentifier("Date");
+ const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
const ast = await openapiTS(mySchema, {
transform(schemaObject, metadata) {
if (schemaObject.format === "date-time") {
- return schemaObject.nullable ? "Date | null" : "Date";
+ return schemaObject.nullable
+ ? ts.factory.createUnionTypeNode([DATE, NULL])
+ : DATE;
}
},
};
```
Though it’s more verbose, it’s also more powerful, as now you have access to additional properties of the generated code you didn’t before (such as injecting comments).
For example syntax, search this codebae to see how the TypeScript AST is used.
Also see [AST Explorer](https://astexplorer.net/)’s `typescript` parser to inspect how TypeScript is interpreted as an AST.
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ **Breaking**: Changing of several CLI flags and Node.js API options
- The `--auth`, `--httpHeaders`, `--httpMethod`, and `fetch` (Node.js-only) options were all removed from the CLI and Node.js API
- To migrate, you’ll need to create a [redocly.yaml config](https://redocly.com/docs/cli/configuration/) that specifies your auth options [in the http setting](https://redocly.com/docs/cli/configuration/#resolve-non-public-or-non-remote-urls)
- You can also set your fetch client in redocly.yaml as well.
- `--immutable-types` has been renamed to `--immutable`
- `--support-array-length` has been renamed to `--array-length`
- [`fbaf96d`](https://github.com/drwpow/openapi-typescript/commit/fbaf96d33181a2fabd3d4748e54c0f111ed6756e) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ **Breaking**: Remove globbing schemas in favor of `redocly.yaml` config. Specify multiple schemas with outputs in there instead. See [Multiple schemas](https://openapi-ts.pages.dev/docs/cli/#multiple-schemas) for more info.
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ **Breaking**: Most optional objects are now always present in types, just typed as `:never`. This includes keys of the Components Object as well as HTTP methods.
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ **Breaking**: No more `external` export in schemas anymore. Everything gets flattened into the `components` object instead (if referencing a schema object from a remote partial, note it may have had a minor name change to avoid conflict).
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ **Breaking** `defaultNonNullable` option now defaults to `true`. You’ll now need to manually set `false` to return to old behavior.
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ⚠️ **Breaking**: additionalProperties no longer have `| undefined` automatically appended
### Minor Changes
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ✨ **Feature**: automatically validate schemas with Redocly CLI ([docs](https://redocly.com/docs/cli/)). No more need for external tools to report errors! 🎉
- By default, it will only throw on actual schema errors (uses Redocly’s default settings)
- For stricter linting or custom rules, you can create a [redocly.yaml config](https://redocly.com/docs/cli/configuration/)
- [`312b7ba`](https://github.com/drwpow/openapi-typescript/commit/312b7ba03fc0334153d4eeb51d6159f3fc63934e) Thanks [@drwpow](https://github.com/drwpow)! - ✨ **Feature:** allow configuration of schemas via `apis` key in redocly.config.yaml. [See docs](https://openapi-ts.pages.dev/cli/) for more info.
- Any options passed into your [redocly.yaml config](https://redocly.com/docs/cli/configuration/) are respected
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ✨ **Feature**: add `enum` option to export top-level enums from schemas
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ✨ **Feature**: add `formatOptions` to allow formatting TS output
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ✨ **Feature**: header responses add `[key: string]: unknown` index type to allow for additional untyped headers
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - ✨ **Feature**: bundle schemas with Redocly CLI
### Patch Changes
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - Refactor internals to use TypeScript AST rather than string mashing
- [`6d1eb32`](https://github.com/drwpow/openapi-typescript/commit/6d1eb32e610cb62effbd1a817ae8fc93337126a6) Thanks [@drwpow](https://github.com/drwpow)! - 🧹 Cleaned up and reorganized all tests
## 6.7.0

@@ -4,0 +77,0 @@

@@ -41,8 +41,14 @@ # Contributing

### Writing the PR
#### Tip: use ASTExplorer.net!
**Please fill out the template!** It’s a very lightweight template 🙂.
Working with the TypeScript AST can be daunting. Luckly, there’s [astexplorer.net](https://astexplorer.net) which makes it much more accessible. Rather than trying to build an AST from scratch (which is near impossible), instead:
### Use Test-driven Development!
1. Switch to the **typescript** parser in the top menu
2. Type out code in the left-hand panel
3. Inspect the right-hand panel to see what the desired AST is.
From there, you can refer to existing examples in the codebase. There may even be helper utilities in `src/lib/ts.ts` to make life easier.
#### Tip: Use Test-driven Development!
Contributing to this library is hard-bordering-on-impossible without a [test-driven development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development) strategy. If you’re new to this, the basic workflow is:

@@ -64,3 +70,3 @@

It may be surprising to hear, but _generating TypeScript types from OpenAPI is opinionated!_ Even though TypeScript and OpenAPI are very close relatives, both being JavaScript/JSON-based, they are nonetheless 2 different languages and thus there is always some room for interpretation. Likewise, some parts of the OpenAPI specification can be ambiguous on how they’re used, and what the expected type outcomes may be (though this is generally for more advanced usecasees, such as specific implementations of `anyOf` as well as [discriminator](https://spec.openapis.org/oas/latest.html#discriminatorObject) and complex polymorphism).
It may be surprising to hear, but generating TypeScript types from OpenAPI is opinionated. Even though TypeScript and OpenAPI are close relatives—both JavaScript/JSON-based—they are nonetheless 2 different languages and thus there is room for interpretation. Further, some parts of the OpenAPI specification can be ambiguous on how they’re used, and what the expected type outcomes may be (though this is generally for more advanced usecasees, such as specific implementations of `anyOf` as well as [discriminator](https://spec.openapis.org/oas/latest.html#discriminatorObject) and complex polymorphism).

@@ -136,3 +142,2 @@ All that said, this library should strive to generate _the most predictable_ TypeScript output for a given schema. And to achieve that, it always helps to open an [issue](https://github.com/drwpow/openapi-typescript/issues) or [discussion](https://github.com/drwpow/openapi-typescript/discussions) to gather feedback.

- Fixing bugs that deal with multiple schemas with remote `$ref`s
- Fixing Node.js or OS-related bugs

@@ -147,6 +152,2 @@ - Adding a CLI option that changes the entire output

Be sure to run `pnpm run build` to build the project. Most tests actually test the **compiled JS**, not the source TypeScript. It’s recommended to run `pnpm run dev` as you work so changes are always up-to-date.
### I get an obscure error when testing against my schema
Be sure your schema passes [Redocly lint](https://redocly.com/docs/cli/commands/lint/). Remember this library requires already-validated OpenAPI schemas, so even subtle errors will throw.
Some tests import the **built package** and not the source file. Be sure to run `pnpm run build` to build the project. You can also run `pnpm run dev` as you work so changes are always up-to-date.
/// <reference types="node" />
/// <reference types="node" />
import { Readable } from "node:stream";
import ts from "typescript";
import type { OpenAPI3, OpenAPITSOptions } from "./types.js";
import type { Readable } from "node:stream";
import { URL } from "node:url";
export * from "./lib/ts.js";
export * from "./lib/utils.js";
export * from "./transform/index.js";
export * from "./transform/components-object.js";
export * from "./transform/header-object.js";
export * from "./transform/media-type-object.js";
export * from "./transform/operation-object.js";
export * from "./transform/parameter-object.js";
export * from "./transform/path-item-object.js";
export * from "./transform/paths-object.js";
export * from "./transform/request-body-object.js";
export * from "./transform/response-object.js";
export * from "./transform/responses-object.js";
export * from "./transform/schema-object.js";
export * from "./types.js";
export declare const COMMENT_HEADER = "/**\n * This file was auto-generated by openapi-typescript.\n * Do not make direct changes to the file.\n */\n\n";
declare function openapiTS(schema: string | URL | OpenAPI3 | Readable, options?: OpenAPITSOptions): Promise<string>;
export default openapiTS;
export default function openapiTS(source: string | URL | OpenAPI3 | Buffer | Readable, options?: OpenAPITSOptions): Promise<ts.Node[]>;

@@ -1,15 +0,20 @@

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";
import { createConfig } from "@redocly/openapi-core";
import { validateAndBundle } from "./lib/redoc.js";
import { debug, resolveRef, scanDiscriminators } from "./lib/utils.js";
import transformSchema from "./transform/index.js";
export * from "./lib/ts.js";
export * from "./lib/utils.js";
export * from "./transform/index.js";
export * from "./transform/components-object.js";
export * from "./transform/header-object.js";
export * from "./transform/media-type-object.js";
export * from "./transform/operation-object.js";
export * from "./transform/parameter-object.js";
export * from "./transform/path-item-object.js";
export * from "./transform/paths-object.js";
export * from "./transform/request-body-object.js";
export * from "./transform/response-object.js";
export * from "./transform/responses-object.js";
export * from "./transform/schema-object.js";
export * from "./types.js";
const EMPTY_OBJECT_RE = /^\s*\{?\s*\}?\s*$/;
export const COMMENT_HEADER = `/**

@@ -21,194 +26,42 @@ * This file was auto-generated by openapi-typescript.

`;
async function openapiTS(schema, options = {}) {
export default async function openapiTS(source, options = {}) {
if (!source) {
throw new Error("Empty schema. Please specify a URL, file path, or Redocly Config");
}
const redoc = options.redocly ?? (await createConfig({}, { extends: ["minimal"] }));
const schema = await validateAndBundle(source, {
redoc,
cwd: options.cwd instanceof URL
? options.cwd
: new URL(`file://${options.cwd ?? process.cwd()}/`),
silent: options.silent ?? false,
});
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,
defaultNonNullable: options.defaultNonNullable ?? true,
discriminators: scanDiscriminators(schema),
emptyObjectsUnknown: options.emptyObjectsUnknown ?? false,
indentLv: 0,
operations: {},
enum: options.enum ?? false,
excludeDeprecated: options.excludeDeprecated ?? false,
exportType: options.exportType ?? false,
immutable: options.immutable ?? false,
injectFooter: [],
pathParamsAsTypes: options.pathParamsAsTypes ?? false,
parameters: {},
postTransform: typeof options.postTransform === "function"
? options.postTransform
: undefined,
redoc,
silent: options.silent ?? false,
supportArrayLength: options.supportArrayLength ?? false,
excludeDeprecated: options.excludeDeprecated ?? false,
arrayLength: options.arrayLength ?? false,
transform: typeof options.transform === "function" ? options.transform : undefined,
resolve(ref) {
return resolveRef(schema, ref, { silent: options.silent ?? 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");
const transformT = performance.now();
const result = transformSchema(schema, ctx);
debug("Completed AST transformation for entire document", "ts", performance.now() - transformT);
return result;
}
export default openapiTS;
//# sourceMappingURL=index.js.map

@@ -1,2 +0,3 @@

import type { ComponentsObject, GlobalContext } from "../types.js";
export default function transformComponentsObject(components: ComponentsObject, ctx: GlobalContext): string;
import ts from "typescript";
import { ComponentsObject, GlobalContext } from "../types.js";
export default function transformComponentsObject(componentsObject: ComponentsObject, ctx: GlobalContext): ts.TypeNode;

@@ -1,2 +0,4 @@

import { escObjKey, getEntries, getSchemaObjectComment, indent, tsOptionalProperty, tsReadonly } from "../utils.js";
import ts from "typescript";
import { NEVER, addJSDocComment, tsModifiers, tsPropertyIndex, } from "../lib/ts.js";
import { createRef, debug, getEntries } from "../lib/utils.js";
import transformHeaderObject from "./header-object.js";

@@ -7,163 +9,34 @@ import transformParameterObject from "./parameter-object.js";

import transformResponseObject from "./response-object.js";
import transformSchemaObjectMap from "./schema-object-map.js";
import transformSchemaObject from "./schema-object.js";
export default function transformComponentsObject(components, ctx) {
let { indentLv } = ctx;
const output = ["{"];
indentLv++;
if (components.schemas) {
const schemas = transformSchemaObjectMap(components.schemas, { path: "#/components/schemas/", ctx: { ...ctx, indentLv } });
output.push(indent(`schemas: ${schemas};`, indentLv));
}
else {
output.push(indent("schemas: never;", indentLv));
}
if (components.responses) {
output.push(indent("responses: {", indentLv));
indentLv++;
for (const [name, responseObject] of getEntries(components.responses, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(responseObject, indentLv);
if (c)
output.push(indent(c, indentLv));
let key = escObjKey(name);
if (ctx.immutableTypes)
key = tsReadonly(key);
if ("$ref" in responseObject) {
output.push(indent(`${key}: ${transformSchemaObject(responseObject, { path: `#/components/responses/${name}`, ctx })};`, indentLv));
}
else {
const responseType = transformResponseObject(responseObject, {
path: `#/components/responses/${name}`,
ctx: { ...ctx, indentLv },
const transformers = {
schemas: transformSchemaObject,
responses: transformResponseObject,
parameters: transformParameterObject,
requestBodies: transformRequestBodyObject,
headers: transformHeaderObject,
pathItems: transformPathItemObject,
};
export default function transformComponentsObject(componentsObject, ctx) {
const type = [];
for (const key of Object.keys(transformers)) {
const componentT = performance.now();
const items = [];
if (componentsObject[key]) {
for (const [name, item] of getEntries(componentsObject[key], ctx)) {
const subType = transformers[key](item, {
path: createRef(["components", key, name]),
ctx,
});
output.push(indent(`${key}: ${responseType};`, indentLv));
const property = ts.factory.createPropertySignature(tsModifiers({ readonly: ctx.immutable }), tsPropertyIndex(name), undefined, subType);
addJSDocComment(item, property);
items.push(property);
}
}
indentLv--;
output.push(indent("};", indentLv));
type.push(ts.factory.createPropertySignature(undefined, tsPropertyIndex(key), undefined, items.length
? ts.factory.createTypeLiteralNode(items)
: NEVER));
debug(`Transformed components → ${key}`, "ts", performance.now() - componentT);
}
else {
output.push(indent("responses: never;", indentLv));
}
if (components.parameters) {
const parameters = [];
indentLv++;
for (const [name, parameterObject] of getEntries(components.parameters, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(parameterObject, indentLv);
if (c)
parameters.push(indent(c, indentLv));
let key = escObjKey(name);
if (ctx.immutableTypes)
key = tsReadonly(key);
if ("$ref" in parameterObject) {
parameters.push(indent(`${key}: ${transformSchemaObject(parameterObject, { path: `#/components/parameters/${name}`, ctx })};`, indentLv));
}
else {
if (parameterObject.in !== "path" && !parameterObject.required) {
key = tsOptionalProperty(key);
}
const parameterType = transformParameterObject(parameterObject, {
path: `#/components/parameters/${name}`,
ctx: { ...ctx, indentLv },
});
parameters.push(indent(`${key}: ${parameterType};`, indentLv));
}
}
indentLv--;
output.push(indent(`parameters: {`, indentLv), ...parameters, indent("};", indentLv));
}
else {
output.push(indent("parameters: never;", indentLv));
}
if (components.requestBodies) {
output.push(indent("requestBodies: {", indentLv));
indentLv++;
for (const [name, requestBodyObject] of getEntries(components.requestBodies, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(requestBodyObject, indentLv);
if (c)
output.push(indent(c, indentLv));
let key = escObjKey(name);
if ("$ref" in requestBodyObject) {
if (ctx.immutableTypes)
key = tsReadonly(key);
output.push(indent(`${key}: ${transformSchemaObject(requestBodyObject, {
path: `#/components/requestBodies/${name}`,
ctx: { ...ctx, indentLv },
})};`, indentLv));
}
else {
if (!requestBodyObject.required)
key = tsOptionalProperty(key);
if (ctx.immutableTypes)
key = tsReadonly(key);
const requestBodyType = transformRequestBodyObject(requestBodyObject, {
path: `#/components/requestBodies/${name}`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${requestBodyType};`, indentLv));
}
}
indentLv--;
output.push(indent("};", indentLv));
}
else {
output.push(indent("requestBodies: never;", indentLv));
}
if (components.headers) {
output.push(indent("headers: {", indentLv));
indentLv++;
for (const [name, headerObject] of getEntries(components.headers, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(headerObject, indentLv);
if (c)
output.push(indent(c, indentLv));
let key = escObjKey(name);
if (ctx.immutableTypes)
key = tsReadonly(key);
if ("$ref" in headerObject) {
output.push(indent(`${key}: ${transformSchemaObject(headerObject, { path: `#/components/headers/${name}`, ctx })};`, indentLv));
}
else {
const headerType = transformHeaderObject(headerObject, {
path: `#/components/headers/${name}`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${headerType};`, indentLv));
}
}
indentLv--;
output.push(indent("};", indentLv));
}
else {
output.push(indent("headers: never;", indentLv));
}
if (components.pathItems) {
output.push(indent("pathItems: {", indentLv));
indentLv++;
for (const [name, pathItemObject] of getEntries(components.pathItems, ctx.alphabetize, ctx.excludeDeprecated)) {
let key = escObjKey(name);
if (ctx.immutableTypes)
key = tsReadonly(key);
if ("$ref" in pathItemObject) {
const c = getSchemaObjectComment(pathItemObject, indentLv);
if (c)
output.push(indent(c, indentLv));
output.push(indent(`${key}: ${transformSchemaObject(pathItemObject, { path: `#/components/pathItems/${name}`, ctx })};`, indentLv));
}
else {
output.push(indent(`${key}: ${transformPathItemObject(pathItemObject, {
path: `#/components/pathItems/${name}`,
ctx: { ...ctx, indentLv },
})};`, indentLv));
}
}
indentLv--;
output.push(indent("};", indentLv));
}
else {
output.push(indent("pathItems: never;", indentLv));
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}
//# sourceMappingURL=components-object.js.map

@@ -1,6 +0,3 @@

import type { GlobalContext, HeaderObject } from "../types.js";
export interface TransformHeaderObjectOptions {
path: string;
ctx: GlobalContext;
}
export default function transformHeaderObject(headerObject: HeaderObject, { path, ctx }: TransformHeaderObjectOptions): string;
import ts from "typescript";
import { HeaderObject, TransformNodeOptions } from "../types.js";
export default function transformHeaderObject(headerObject: HeaderObject, options: TransformNodeOptions): ts.TypeNode;

@@ -1,32 +0,32 @@

import { escStr, getEntries, getSchemaObjectComment, indent, tsReadonly } from "../utils.js";
import { escapePointer } from "@redocly/openapi-core/lib/ref-utils.js";
import ts from "typescript";
import { addJSDocComment, tsModifiers, tsPropertyIndex, UNKNOWN, } from "../lib/ts.js";
import { getEntries } from "../lib/utils.js";
import transformMediaTypeObject from "./media-type-object.js";
import transformSchemaObject from "./schema-object.js";
export default function transformHeaderObject(headerObject, { path, ctx }) {
if (headerObject.schema)
return transformSchemaObject(headerObject.schema, { path, ctx });
export default function transformHeaderObject(headerObject, options) {
if (headerObject.schema) {
return transformSchemaObject(headerObject.schema, options);
}
if (headerObject.content) {
let { indentLv } = ctx;
const output = ["{"];
indentLv++;
for (const [contentType, mediaTypeObject] of getEntries(headerObject.content, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(mediaTypeObject, indentLv);
if (c)
output.push(indent(c, indentLv));
let key = escStr(contentType);
if (ctx.immutableTypes)
key = tsReadonly(key);
if ("$ref" in mediaTypeObject) {
output.push(indent(`${key}: ${transformSchemaObject(mediaTypeObject, { path: `${path}/${contentType}`, ctx })};`, indentLv));
}
else {
const mediaType = transformMediaTypeObject(mediaTypeObject, { path: `${path}/${contentType}`, ctx });
output.push(indent(`${key}: ${mediaType};`, indentLv));
}
const type = [];
for (const [contentType, mediaTypeObject] of getEntries(headerObject.content, options.ctx)) {
const nextPath = `${options.path ?? "#"}/${escapePointer(contentType)}`;
const mediaType = "$ref" in mediaTypeObject
? transformSchemaObject(mediaTypeObject, {
...options,
path: nextPath,
})
: transformMediaTypeObject(mediaTypeObject, {
...options,
path: nextPath,
});
const property = ts.factory.createPropertySignature(tsModifiers({ readonly: options.ctx.immutable }), tsPropertyIndex(contentType), undefined, mediaType);
addJSDocComment(mediaTypeObject, property);
type.push(property);
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}
return "unknown";
return UNKNOWN;
}
//# sourceMappingURL=header-object.js.map

@@ -1,2 +0,3 @@

import type { GlobalContext, OpenAPI3 } from "../types.js";
export declare function transformSchema(schema: OpenAPI3, ctx: GlobalContext): Record<string, string>;
import ts from "typescript";
import { GlobalContext, OpenAPI3 } from "../types.js";
export default function transformSchema(schema: OpenAPI3, ctx: GlobalContext): ts.Node[];

@@ -0,27 +1,62 @@

import ts from "typescript";
import { NEVER, STRING, tsModifiers, tsRecord } from "../lib/ts.js";
import { createRef, debug } from "../lib/utils.js";
import transformComponentsObject from "./components-object.js";
import transformPathsObject from "./paths-object.js";
import transformSchemaObjectMap from "./schema-object-map.js";
import transformSchemaObject from "./schema-object.js";
import transformWebhooksObject from "./webhooks-object.js";
export function transformSchema(schema, ctx) {
if (!schema)
return {};
const output = {};
if (schema.paths)
output.paths = transformPathsObject(schema.paths, ctx);
else
output.paths = "";
if (schema.webhooks)
output.webhooks = transformWebhooksObject(schema.webhooks, ctx);
else
output.webhooks = "";
if (schema.components)
output.components = transformComponentsObject(schema.components, ctx);
else
output.components = "";
if (schema.$defs)
output.$defs = transformSchemaObjectMap(schema.$defs, { path: "$defs/", ctx });
else
output.$defs = "";
return output;
const transformers = {
paths: transformPathsObject,
webhooks: transformWebhooksObject,
components: transformComponentsObject,
$defs: (node, options) => transformSchemaObject(node, { path: createRef(["$defs"]), ctx: options }),
};
export default function transformSchema(schema, ctx) {
const type = [];
for (const root of Object.keys(transformers)) {
const emptyObj = ts.factory.createTypeAliasDeclaration(tsModifiers({
export: true,
readonly: ctx.immutable,
}), root, undefined, tsRecord(STRING, NEVER));
if (schema[root] && typeof schema[root] === "object") {
const rootT = performance.now();
const subType = transformers[root](schema[root], ctx);
if (subType.members?.length) {
type.push(ctx.exportType
? ts.factory.createTypeAliasDeclaration(tsModifiers({
export: true,
readonly: ctx.immutable,
}), root, undefined, subType)
: ts.factory.createInterfaceDeclaration(tsModifiers({
export: true,
readonly: ctx.immutable,
}), root, undefined, undefined, subType.members));
debug(`${root} done`, "ts", performance.now() - rootT);
}
else {
type.push(emptyObj);
debug(`${root} done (skipped)`, "ts", 0);
}
}
else {
type.push(emptyObj);
debug(`${root} done (skipped)`, "ts", 0);
}
}
let hasOperations = false;
for (const injectedType of ctx.injectFooter) {
if (!hasOperations &&
injectedType?.name?.escapedText === "operations") {
hasOperations = true;
}
type.push(injectedType);
}
if (!hasOperations) {
type.push(ts.factory.createTypeAliasDeclaration(tsModifiers({
export: true,
readonly: ctx.immutable,
}), "operations", undefined, tsRecord(STRING, NEVER)));
}
return type;
}
//# sourceMappingURL=index.js.map

@@ -1,6 +0,3 @@

import type { GlobalContext, MediaTypeObject } from "../types.js";
export interface TransformMediaTypeObjectOptions {
path: string;
ctx: GlobalContext;
}
export default function transformMediaTypeObject(mediaTypeObject: MediaTypeObject, { path, ctx }: TransformMediaTypeObjectOptions): string;
import ts from "typescript";
import { MediaTypeObject, TransformNodeOptions } from "../types.js";
export default function transformMediaTypeObject(mediaTypeObject: MediaTypeObject, options: TransformNodeOptions): ts.TypeNode;

@@ -0,7 +1,9 @@

import { UNKNOWN } from "../lib/ts.js";
import transformSchemaObject from "./schema-object.js";
export default function transformMediaTypeObject(mediaTypeObject, { path, ctx }) {
if (!mediaTypeObject.schema)
return "unknown";
return transformSchemaObject(mediaTypeObject.schema, { path, ctx });
export default function transformMediaTypeObject(mediaTypeObject, options) {
if (!mediaTypeObject.schema) {
return UNKNOWN;
}
return transformSchemaObject(mediaTypeObject.schema, options);
}
//# sourceMappingURL=media-type-object.js.map

@@ -1,7 +0,4 @@

import type { GlobalContext, OperationObject } from "../types.js";
export interface TransformOperationObjectOptions {
path: string;
ctx: GlobalContext;
wrapObject?: boolean;
}
export default function transformOperationObject(operationObject: OperationObject, { path, ctx, wrapObject }: TransformOperationObjectOptions): string;
import ts from "typescript";
import { OperationObject, TransformNodeOptions } from "../types.js";
export default function transformOperationObject(operationObject: OperationObject, options: TransformNodeOptions): ts.TypeElement[];
export declare function injectOperationObject(operationId: string, operationObject: OperationObject, options: TransformNodeOptions): void;

@@ -1,112 +0,46 @@

import { escObjKey, getEntries, getSchemaObjectComment, indent, tsOptionalProperty, tsReadonly } from "../utils.js";
import transformParameterObject from "./parameter-object.js";
import ts from "typescript";
import { NEVER, QUESTION_TOKEN, addJSDocComment, oapiRef, tsModifiers, tsPropertyIndex, } from "../lib/ts.js";
import { createRef } from "../lib/utils.js";
import { transformParametersArray } from "./parameters-array.js";
import transformRequestBodyObject from "./request-body-object.js";
import transformResponseObject from "./response-object.js";
import transformSchemaObject from "./schema-object.js";
export default function transformOperationObject(operationObject, { path, ctx, wrapObject = true }) {
let { indentLv } = ctx;
const output = wrapObject ? ["{"] : [];
indentLv++;
{
if (operationObject.parameters) {
const parameterOutput = [];
indentLv++;
for (const paramIn of ["query", "header", "path", "cookie"]) {
const paramInternalOutput = [];
indentLv++;
let paramInOptional = true;
for (const param of operationObject.parameters ?? []) {
const node = "$ref" in param ? ctx.parameters[param.$ref] : param;
if (node?.in !== paramIn)
continue;
let key = escObjKey(node.name);
const isRequired = paramIn === "path" || !!node.required;
if (isRequired) {
paramInOptional = false;
}
else {
key = tsOptionalProperty(key);
}
const c = getSchemaObjectComment(param, indentLv);
if (c)
paramInternalOutput.push(indent(c, indentLv));
const parameterType = "$ref" in param
? param.$ref
: transformParameterObject(param, {
path: `${path}/parameters/${param.name}`,
ctx: { ...ctx, indentLv },
});
paramInternalOutput.push(indent(`${key}: ${parameterType};`, indentLv));
}
indentLv--;
if (paramInternalOutput.length) {
const key = paramInOptional ? tsOptionalProperty(paramIn) : paramIn;
parameterOutput.push(indent(`${key}: {`, indentLv));
parameterOutput.push(...paramInternalOutput);
parameterOutput.push(indent(`};`, indentLv));
}
}
indentLv--;
if (parameterOutput.length) {
output.push(indent(`parameters: {`, indentLv));
output.push(parameterOutput.join("\n"));
output.push(indent("};", indentLv));
}
}
import transformResponsesObject from "./responses-object.js";
export default function transformOperationObject(operationObject, options) {
const type = [];
type.push(...transformParametersArray(operationObject.parameters ?? [], options));
if (operationObject.requestBody) {
const requestBodyType = "$ref" in operationObject.requestBody
? oapiRef(operationObject.requestBody.$ref)
: transformRequestBodyObject(operationObject.requestBody, {
...options,
path: createRef([options.path, "requestBody"]),
});
const required = !!("$ref" in operationObject.requestBody
? options.ctx.resolve(operationObject.requestBody.$ref)
: operationObject.requestBody)?.required;
const property = ts.factory.createPropertySignature(tsModifiers({ readonly: options.ctx.immutable }), tsPropertyIndex("requestBody"), required ? undefined : QUESTION_TOKEN, requestBodyType);
addJSDocComment(operationObject.requestBody, property);
type.push(property);
}
{
if (operationObject.requestBody) {
const c = getSchemaObjectComment(operationObject.requestBody, indentLv);
if (c)
output.push(indent(c, indentLv));
let key = "requestBody";
if (ctx.immutableTypes)
key = tsReadonly(key);
if ("$ref" in operationObject.requestBody) {
output.push(indent(`${key}: ${transformSchemaObject(operationObject.requestBody, { path, ctx })};`, indentLv));
}
else {
if (!operationObject.requestBody.required)
key = tsOptionalProperty(key);
const requestBody = transformRequestBodyObject(operationObject.requestBody, {
path: `${path}/requestBody`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${requestBody};`, indentLv));
}
}
else {
type.push(ts.factory.createPropertySignature(undefined, tsPropertyIndex("requestBody"), QUESTION_TOKEN, NEVER));
}
{
if (operationObject.responses) {
output.push(indent(`responses: {`, indentLv));
indentLv++;
for (const [responseCode, responseObject] of getEntries(operationObject.responses, ctx.alphabetize, ctx.excludeDeprecated)) {
const key = escObjKey(responseCode);
const c = getSchemaObjectComment(responseObject, indentLv);
if (c)
output.push(indent(c, indentLv));
if ("$ref" in responseObject) {
output.push(indent(`${key}: ${transformSchemaObject(responseObject, {
path: `${path}/responses/${responseCode}`,
ctx,
})};`, indentLv));
}
else {
const responseType = transformResponseObject(responseObject, {
path: `${path}/responses/${responseCode}`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${responseType};`, indentLv));
}
}
indentLv--;
output.push(indent(`};`, indentLv));
}
type.push(ts.factory.createPropertySignature(undefined, tsPropertyIndex("responses"), undefined, transformResponsesObject(operationObject.responses ?? {}, options)));
return type;
}
export function injectOperationObject(operationId, operationObject, options) {
let operations = options.ctx.injectFooter.find((node) => ts.isInterfaceDeclaration(node) &&
node.name.text === "operations");
if (!operations) {
operations = ts.factory.createInterfaceDeclaration(tsModifiers({
export: true,
readonly: options.ctx.immutable,
}), ts.factory.createIdentifier("operations"), undefined, undefined, []);
options.ctx.injectFooter.push(operations);
}
indentLv--;
if (wrapObject) {
output.push(indent("}", indentLv));
}
return output.join("\n");
const type = transformOperationObject(operationObject, options);
operations.members = ts.factory.createNodeArray([
...operations.members,
ts.factory.createPropertySignature(tsModifiers({ readonly: options.ctx.immutable }), tsPropertyIndex(operationId), undefined, ts.factory.createTypeLiteralNode(type)),
]);
}
//# sourceMappingURL=operation-object.js.map

@@ -1,6 +0,3 @@

import type { GlobalContext, ParameterObject } from "../types.js";
export interface TransformParameterObjectOptions {
path: string;
ctx: GlobalContext;
}
export default function transformParameterObject(parameterObject: ParameterObject, { path, ctx }: TransformParameterObjectOptions): string;
import ts from "typescript";
import { ParameterObject, TransformNodeOptions } from "../types.js";
export default function transformParameterObject(parameterObject: ParameterObject, options: TransformNodeOptions): ts.TypeNode;

@@ -0,5 +1,8 @@

import { STRING } from "../lib/ts.js";
import transformSchemaObject from "./schema-object.js";
export default function transformParameterObject(parameterObject, { path, ctx }) {
return parameterObject.schema ? transformSchemaObject(parameterObject.schema, { path, ctx }) : "string";
export default function transformParameterObject(parameterObject, options) {
return parameterObject.schema
? transformSchemaObject(parameterObject.schema, options)
: STRING;
}
//# sourceMappingURL=parameter-object.js.map

@@ -1,7 +0,4 @@

import type { GlobalContext, PathItemObject } from "../types.js";
export interface TransformPathItemObjectOptions {
path: string;
ctx: GlobalContext;
}
import ts from "typescript";
import { PathItemObject, TransformNodeOptions } from "../types.js";
export type Method = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
export default function transformPathItemObject(pathItem: PathItemObject, { path, ctx }: TransformPathItemObjectOptions): string;
export default function transformPathItemObject(pathItem: PathItemObject, options: TransformNodeOptions): ts.TypeNode;

@@ -1,44 +0,58 @@

import { escStr, getSchemaObjectComment, indent } from "../utils.js";
import transformOperationObject from "./operation-object.js";
export default function transformPathItemObject(pathItem, { path, ctx }) {
let { indentLv } = ctx;
const output = [];
output.push("{");
indentLv++;
for (const method of ["get", "put", "post", "delete", "options", "head", "patch", "trace"]) {
import ts from "typescript";
import { NEVER, addJSDocComment, oapiRef, tsPropertyIndex } from "../lib/ts.js";
import { createRef } from "../lib/utils.js";
import transformOperationObject, { injectOperationObject, } from "./operation-object.js";
import { transformParametersArray } from "./parameters-array.js";
export default function transformPathItemObject(pathItem, options) {
const type = [];
type.push(...transformParametersArray(pathItem.parameters ?? [], {
...options,
path: createRef([options.path, "parameters"]),
}));
for (const method of [
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
]) {
const operationObject = pathItem[method];
if (!operationObject)
if (!operationObject ||
(options.ctx.excludeDeprecated &&
("$ref" in operationObject
? options.ctx.resolve(operationObject.$ref)
: operationObject)?.deprecated)) {
type.push(ts.factory.createPropertySignature(undefined, tsPropertyIndex(method), undefined, NEVER));
continue;
const c = getSchemaObjectComment(operationObject, indentLv);
if (c)
output.push(indent(c, indentLv));
}
const keyedParameters = {};
if (!("$ref" in operationObject)) {
for (const parameter of [...(pathItem.parameters ?? []), ...(operationObject.parameters ?? [])]) {
keyedParameters["$ref" in parameter ? parameter.$ref : parameter.name] = parameter;
for (const parameter of [
...(pathItem.parameters ?? []),
...(operationObject.parameters ?? []),
]) {
keyedParameters["$ref" in parameter ? parameter.$ref : parameter.name] =
parameter;
}
}
let operationType;
if ("$ref" in operationObject) {
output.push(indent(`${method}: ${operationObject.$ref}`, indentLv));
operationType = oapiRef(operationObject.$ref);
}
else if (operationObject.operationId) {
const operationType = transformOperationObject({ ...operationObject, parameters: Object.values(keyedParameters) }, { path, ctx: { ...ctx, indentLv: 1 } });
ctx.operations[operationObject.operationId] = {
operationType,
comment: getSchemaObjectComment(operationObject, 1),
};
output.push(indent(`${method}: operations[${escStr(operationObject.operationId)}];`, indentLv));
operationType = oapiRef(createRef(["operations", operationObject.operationId]));
injectOperationObject(operationObject.operationId, { ...operationObject, parameters: Object.values(keyedParameters) }, { ...options, path: createRef([options.path, method]) });
}
else {
const operationType = transformOperationObject({ ...operationObject, parameters: Object.values(keyedParameters) }, { path, ctx: { ...ctx, indentLv } });
output.push(indent(`${method}: ${operationType};`, indentLv));
operationType = ts.factory.createTypeLiteralNode(transformOperationObject({ ...operationObject, parameters: Object.values(keyedParameters) }, { ...options, path: createRef([options.path, method]) }));
}
const property = ts.factory.createPropertySignature(undefined, tsPropertyIndex(method), undefined, operationType);
addJSDocComment(operationObject, property);
type.push(property);
}
if (pathItem.parameters?.length) {
output.push(indent(transformOperationObject({ parameters: pathItem.parameters }, { path, ctx, wrapObject: false }).trim(), indentLv));
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}
//# sourceMappingURL=path-item-object.js.map

@@ -1,2 +0,3 @@

import type { GlobalContext, PathsObject } from "../types.js";
export default function transformPathsObject(pathsObject: PathsObject, ctx: GlobalContext): string;
import ts from "typescript";
import { GlobalContext, PathsObject } from "../types.js";
export default function transformPathsObject(pathsObject: PathsObject, ctx: GlobalContext): ts.TypeNode;

@@ -1,50 +0,91 @@

import { escStr, getEntries, getSchemaObjectComment, indent } from "../utils.js";
import transformParameterObject from "./parameter-object.js";
import ts from "typescript";
import { addJSDocComment, oapiRef, stringToAST, tsPropertyIndex, } from "../lib/ts.js";
import { createRef, debug, getEntries } from "../lib/utils.js";
import transformPathItemObject from "./path-item-object.js";
const OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch", "trace"];
function extractPathParams(obj) {
const params = new Map();
if (obj && "parameters" in obj) {
for (const p of obj.parameters ?? []) {
if ("in" in p && p.in === "path")
params.set(p.name, p);
}
}
return params;
}
const PATH_PARAM_RE = /\{[^}]+\}/g;
export default function transformPathsObject(pathsObject, ctx) {
let { indentLv } = ctx;
const output = ["{"];
indentLv++;
for (const [url, pathItemObject] of getEntries(pathsObject, ctx.alphabetize, ctx.excludeDeprecated)) {
if (!pathItemObject || typeof pathItemObject !== "object")
const type = [];
for (const [url, pathItemObject] of getEntries(pathsObject, ctx)) {
if (!pathItemObject || typeof pathItemObject !== "object") {
continue;
let path = url;
}
const pathT = performance.now();
if ("$ref" in pathItemObject) {
const c = getSchemaObjectComment(pathItemObject, indentLv);
if (c)
output.push(indent(c, indentLv));
output.push(indent(`${escStr(path)}: ${pathItemObject.$ref};`, indentLv));
continue;
const property = ts.factory.createPropertySignature(undefined, tsPropertyIndex(url), undefined, oapiRef(pathItemObject.$ref));
addJSDocComment(pathItemObject, property);
}
const pathParams = new Map([...extractPathParams(pathItemObject), ...OPERATIONS.flatMap((op) => Array.from(extractPathParams(pathItemObject[op])))]);
if (ctx.pathParamsAsTypes && pathParams.size) {
for (const p of pathParams.values()) {
const paramType = transformParameterObject(p, { path: `#/paths/${url}/parameters/${p.name}`, ctx });
path = path.replace(`{${p.name}}`, `\${${paramType}}`);
else {
const pathItemType = transformPathItemObject(pathItemObject, {
path: createRef(["paths", url]),
ctx,
});
if (ctx.pathParamsAsTypes && url.includes("{")) {
const pathParams = extractPathParams(pathItemObject, ctx);
const matches = url.match(PATH_PARAM_RE);
let rawPath = `\`${url}\``;
if (matches) {
for (const match of matches) {
const paramName = match.slice(1, -1);
const param = pathParams[paramName];
if (!param) {
rawPath = rawPath.replace(match, "${string}");
}
else {
rawPath = rawPath.replace(match, `$\{${param.schema?.type ?? "string"}}`);
}
}
const pathType = stringToAST(rawPath)[0]?.expression;
if (pathType) {
type.push(ts.factory.createIndexSignature(undefined, [
ts.factory.createParameterDeclaration(undefined, undefined, "path", undefined, pathType, undefined),
], pathItemType));
continue;
}
}
}
path = `[path: \`${path}\`]`;
type.push(ts.factory.createPropertySignature(undefined, tsPropertyIndex(url), undefined, pathItemType));
debug(`Transformed path "${url}"`, "ts", performance.now() - pathT);
}
else {
path = escStr(path);
}
return ts.factory.createTypeLiteralNode(type);
}
function extractPathParams(pathItemObject, ctx) {
const params = {};
for (const p of pathItemObject.parameters ?? []) {
const resolved = "$ref" in p && p.$ref
? ctx.resolve(p.$ref)
: p;
if (resolved && resolved.in === "path") {
params[resolved.name] = resolved;
}
output.push(indent(`${path}: ${transformPathItemObject(pathItemObject, {
path: `#/paths/${url}`,
ctx: { ...ctx, indentLv },
})};`, indentLv));
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
for (const method of [
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
]) {
if (!(method in pathItemObject)) {
continue;
}
const resolvedMethod = pathItemObject[method].$ref
? ctx.resolve(pathItemObject[method].$ref)
: pathItemObject[method];
if (resolvedMethod?.parameters) {
for (const p of resolvedMethod.parameters) {
const resolvedParam = "$ref" in p && p.$ref
? ctx.resolve(p.$ref)
: p;
if (resolvedParam && resolvedParam.in === "path") {
params[resolvedParam.name] = resolvedParam;
}
}
}
}
return params;
}
//# sourceMappingURL=paths-object.js.map

@@ -1,6 +0,3 @@

import type { GlobalContext, RequestBodyObject } from "../types.js";
export interface TransformRequestBodyObjectOptions {
path: string;
ctx: GlobalContext;
}
export default function transformRequestBodyObject(requestBodyObject: RequestBodyObject, { path, ctx }: TransformRequestBodyObjectOptions): string;
import ts from "typescript";
import { RequestBodyObject, TransformNodeOptions } from "../types.js";
export default function transformRequestBodyObject(requestBodyObject: RequestBodyObject, options: TransformNodeOptions): ts.TypeNode;

@@ -1,40 +0,32 @@

import { escStr, getEntries, getSchemaObjectComment, indent, tsReadonly } from "../utils.js";
import ts from "typescript";
import { NEVER, QUESTION_TOKEN, addJSDocComment, tsModifiers, tsPropertyIndex, } from "../lib/ts.js";
import { createRef, getEntries } from "../lib/utils.js";
import transformMediaTypeObject from "./media-type-object.js";
import transformSchemaObject from "./schema-object.js";
export default function transformRequestBodyObject(requestBodyObject, { path, ctx }) {
let { indentLv } = ctx;
const output = ["{"];
indentLv++;
output.push(indent(ctx.immutableTypes ? tsReadonly("content: {") : "content: {", indentLv));
indentLv++;
if (!Object.keys(requestBodyObject.content).length) {
output.push(indent(`${escStr("*/*")}: never;`, indentLv));
}
for (const [contentType, mediaTypeObject] of getEntries(requestBodyObject.content, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(mediaTypeObject, indentLv);
if (c)
output.push(indent(c, indentLv));
let key = escStr(contentType);
if (ctx.immutableTypes)
key = tsReadonly(key);
if ("$ref" in mediaTypeObject) {
output.push(indent(`${key}: ${transformSchemaObject(mediaTypeObject, {
path: `${path}/${contentType}`,
ctx: { ...ctx, indentLv },
})};`, indentLv));
}
else {
const mediaType = transformMediaTypeObject(mediaTypeObject, {
path: `${path}/${contentType}`,
ctx: { ...ctx, indentLv },
export default function transformRequestBodyObject(requestBodyObject, options) {
const type = [];
for (const [contentType, mediaTypeObject] of getEntries(requestBodyObject.content, options.ctx)) {
const nextPath = createRef([options.path, contentType]);
const mediaType = "$ref" in mediaTypeObject
? transformSchemaObject(mediaTypeObject, {
...options,
path: nextPath,
})
: transformMediaTypeObject(mediaTypeObject, {
...options,
path: nextPath,
});
output.push(indent(`${key}: ${mediaType};`, indentLv));
}
const property = ts.factory.createPropertySignature(tsModifiers({ readonly: options.ctx.immutable }), tsPropertyIndex(contentType), undefined, mediaType);
addJSDocComment(mediaTypeObject, property);
type.push(property);
}
indentLv--;
output.push(indent("};", indentLv));
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode([
ts.factory.createPropertySignature(tsModifiers({ readonly: options.ctx.immutable }), tsPropertyIndex("content"), undefined, ts.factory.createTypeLiteralNode(type.length
? type
:
[
ts.factory.createPropertySignature(undefined, tsPropertyIndex("*/*"), QUESTION_TOKEN, NEVER),
])),
]);
}
//# sourceMappingURL=request-body-object.js.map

@@ -1,6 +0,3 @@

import type { GlobalContext, ResponseObject } from "../types.js";
export interface TransformResponseObjectOptions {
path: string;
ctx: GlobalContext;
}
export default function transformResponseObject(responseObject: ResponseObject, { path, ctx }: TransformResponseObjectOptions): string;
import ts from "typescript";
import { ResponseObject, TransformNodeOptions } from "../types.js";
export default function transformResponseObject(responseObject: ResponseObject, options: TransformNodeOptions): ts.TypeNode;

@@ -1,59 +0,47 @@

import { escObjKey, escStr, getEntries, getSchemaObjectComment, indent, tsOptionalProperty, tsReadonly } from "../utils.js";
import ts from "typescript";
import { NEVER, QUESTION_TOKEN, STRING, UNKNOWN, addJSDocComment, oapiRef, tsModifiers, tsPropertyIndex, } from "../lib/ts.js";
import { createRef, getEntries } from "../lib/utils.js";
import transformHeaderObject from "./header-object.js";
import transformMediaTypeObject from "./media-type-object.js";
export default function transformResponseObject(responseObject, { path, ctx }) {
const output = ["{"];
let { indentLv } = ctx;
export default function transformResponseObject(responseObject, options) {
const type = [];
const headersObject = [];
if (responseObject.headers) {
indentLv++;
output.push(indent(`headers: {`, indentLv));
indentLv++;
for (const [name, headerObject] of getEntries(responseObject.headers, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(headerObject, indentLv);
if (c)
output.push(indent(c, indentLv));
let key = escObjKey(name);
if (ctx.immutableTypes)
key = tsReadonly(key);
if ("$ref" in headerObject) {
output.push(indent(`${key}: ${headerObject.$ref};`, indentLv));
}
else {
if (!headerObject.required)
key = tsOptionalProperty(key);
output.push(indent(`${key}: ${transformHeaderObject(headerObject, {
path: `${path}/headers/${name}`,
ctx: { ...ctx, indentLv },
})};`, indentLv));
}
for (const [name, headerObject] of getEntries(responseObject.headers, options.ctx)) {
const optional = "$ref" in headerObject || headerObject.required
? undefined
: QUESTION_TOKEN;
const subType = "$ref" in headerObject
? oapiRef(headerObject.$ref)
: transformHeaderObject(headerObject, {
...options,
path: createRef([options.path ?? "", "headers", name]),
});
const property = ts.factory.createPropertySignature(tsModifiers({ readonly: options.ctx.immutable }), tsPropertyIndex(name), optional, subType);
addJSDocComment(headerObject, property);
headersObject.push(property);
}
indentLv--;
output.push(indent(`};`, indentLv));
indentLv--;
}
headersObject.push(ts.factory.createIndexSignature(tsModifiers({ readonly: options.ctx.immutable }), [
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("name"), undefined, STRING),
], UNKNOWN));
type.push(ts.factory.createPropertySignature(undefined, tsPropertyIndex("headers"), undefined, ts.factory.createTypeLiteralNode(headersObject)));
const contentObject = [];
if (responseObject.content) {
indentLv++;
output.push(indent("content: {", indentLv));
indentLv++;
for (const [contentType, mediaTypeObject] of getEntries(responseObject.content, ctx.alphabetize, ctx.excludeDeprecated)) {
let key = escStr(contentType);
if (ctx.immutableTypes)
key = tsReadonly(key);
output.push(indent(`${key}: ${transformMediaTypeObject(mediaTypeObject, {
path: `${path}/content/${contentType}`,
ctx: { ...ctx, indentLv: indentLv },
})};`, indentLv));
for (const [contentType, mediaTypeObject] of getEntries(responseObject.content, options.ctx)) {
const property = ts.factory.createPropertySignature(tsModifiers({ readonly: options.ctx.immutable }), tsPropertyIndex(contentType), undefined, transformMediaTypeObject(mediaTypeObject, {
...options,
path: createRef([options.path ?? "", "content", contentType]),
}));
contentObject.push(property);
}
indentLv--;
output.push(indent("};", indentLv));
indentLv--;
}
if (contentObject.length) {
type.push(ts.factory.createPropertySignature(undefined, tsPropertyIndex("content"), undefined, ts.factory.createTypeLiteralNode(contentObject)));
}
else {
indentLv++;
output.push(indent("content: never;", indentLv));
indentLv--;
type.push(ts.factory.createPropertySignature(undefined, tsPropertyIndex("content"), QUESTION_TOKEN, NEVER));
}
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}
//# sourceMappingURL=response-object.js.map

@@ -1,8 +0,4 @@

import type { DiscriminatorObject, GlobalContext, ReferenceObject, SchemaObject } from "../types.js";
export interface TransformSchemaObjectOptions {
path: string;
ctx: GlobalContext;
}
export default function transformSchemaObject(schemaObject: SchemaObject | ReferenceObject, options: TransformSchemaObjectOptions): string;
export declare function defaultSchemaObjectTransform(schemaObject: SchemaObject | ReferenceObject, { path, ctx }: TransformSchemaObjectOptions): string;
export declare function getDiscriminatorPropertyName(path: string, discriminator: DiscriminatorObject): string;
import ts from "typescript";
import { ReferenceObject, SchemaObject, TransformNodeOptions } from "../types.js";
export default function transformSchemaObject(schemaObject: SchemaObject | ReferenceObject, options: TransformNodeOptions): ts.TypeNode;
export declare function transformSchemaObjectWithComposition(schemaObject: SchemaObject | ReferenceObject, options: TransformNodeOptions): ts.TypeNode;

@@ -1,223 +0,287 @@

import { escObjKey, escStr, getEntries, getSchemaObjectComment, indent, parseRef, tsArrayOf, tsIntersectionOf, tsOmit, tsOneOf, tsOptionalProperty, tsReadonly, tsTupleOf, tsUnionOf, tsWithRequired } from "../utils.js";
import transformSchemaObjectMap from "./schema-object-map.js";
import { parseRef } from "@redocly/openapi-core/lib/ref-utils.js";
import ts from "typescript";
import { BOOLEAN, NEVER, NULL, NUMBER, QUESTION_TOKEN, STRING, UNKNOWN, addJSDocComment, oapiRef, tsEnum, tsIntersection, tsIsPrimitive, tsLiteral, tsModifiers, tsNullable, tsOmit, tsPropertyIndex, tsRecord, tsUnion, tsWithRequired, } from "../lib/ts.js";
import { createDiscriminatorProperty, createRef, getEntries, } from "../lib/utils.js";
export default function transformSchemaObject(schemaObject, options) {
const result = defaultSchemaObjectTransform(schemaObject, options);
const type = transformSchemaObjectWithComposition(schemaObject, options);
if (typeof options.ctx.postTransform === "function") {
const postResult = options.ctx.postTransform(result, options);
if (postResult)
return postResult;
const postTransformResult = options.ctx.postTransform(type, options);
if (postTransformResult) {
return postTransformResult;
}
}
return result;
return type;
}
export function defaultSchemaObjectTransform(schemaObject, { path, ctx }) {
let { indentLv } = ctx;
if (typeof schemaObject === "boolean") {
return schemaObject ? "unknown" : "never";
export function transformSchemaObjectWithComposition(schemaObject, options) {
if (!schemaObject) {
return NEVER;
}
if (!schemaObject || typeof schemaObject !== "object")
return schemaObject;
if (Array.isArray(schemaObject)) {
const finalType = tsTupleOf(...schemaObject);
return ctx.immutableTypes ? tsReadonly(finalType) : finalType;
if (schemaObject === true) {
return UNKNOWN;
}
if (Array.isArray(schemaObject) || typeof schemaObject !== "object") {
throw new Error(`Expected SchemaObject, received ${Array.isArray(schemaObject) ? "Array" : typeof schemaObject}`);
}
if ("$ref" in schemaObject) {
return schemaObject.$ref;
return oapiRef(schemaObject.$ref);
}
if (typeof ctx.transform === "function") {
const result = ctx.transform(schemaObject, { path, ctx });
if (result)
if (typeof options.ctx.transform === "function") {
const result = options.ctx.transform(schemaObject, options);
if (result !== undefined && result !== null) {
return result;
}
}
if (schemaObject.const !== null && schemaObject.const !== undefined) {
return transformSchemaObject(escStr(schemaObject.const), {
path,
ctx: { ...ctx, immutableTypes: false, indentLv: indentLv + 1 },
});
return tsLiteral(schemaObject.const);
}
if (typeof schemaObject === "object" && !!schemaObject.enum && schemaObject.type !== "object") {
let items = schemaObject.enum;
if ("type" in schemaObject) {
if (schemaObject.type === "string" || (Array.isArray(schemaObject.type) && schemaObject.type.includes("string"))) {
items = items.map((t) => escStr(t));
if (Array.isArray(schemaObject.enum) &&
(!("type" in schemaObject) || schemaObject.type !== "object") &&
!("properties" in schemaObject) &&
!("additionalProperties" in schemaObject)) {
if (options.ctx.enum &&
schemaObject.enum.every((v) => typeof v === "string" || typeof v === "number")) {
let enumName = parseRef(options.path ?? "").pointer.join("/");
enumName = enumName.replace("components/schemas", "");
const enumType = tsEnum(enumName, schemaObject.enum, { export: true, readonly: options.ctx.immutable });
options.ctx.injectFooter.push(enumType);
return ts.factory.createTypeReferenceNode(enumType.name);
}
return tsUnion(schemaObject.enum.map(tsLiteral));
}
function collectCompositions(items) {
const output = [];
for (const item of items) {
const itemType = transformSchemaObject(item, options);
const discriminator = ("$ref" in item && options.ctx.discriminators[item.$ref]) ||
item.discriminator;
if (discriminator) {
output.push(tsOmit(itemType, [discriminator.propertyName]));
}
else {
output.push(itemType);
}
}
return output;
}
let finalType = undefined;
const coreObjectType = transformSchemaObjectCore(schemaObject, options);
const allOfType = collectCompositions(schemaObject.allOf ?? []);
if (coreObjectType || allOfType.length) {
let allOf = allOfType.length
? tsIntersection(allOfType)
: undefined;
if (allOf &&
"required" in schemaObject &&
Array.isArray(schemaObject.required)) {
allOf = tsWithRequired(allOf, schemaObject.required, options.ctx.injectFooter);
}
finalType = tsIntersection([
...(coreObjectType ? [coreObjectType] : []),
...(allOf ? [allOf] : []),
]);
}
const anyOfType = collectCompositions(schemaObject.anyOf ?? []);
if (anyOfType.length) {
finalType = tsUnion([...(finalType ? [finalType] : []), ...anyOfType]);
}
const oneOfType = collectCompositions(schemaObject.oneOf ||
("type" in schemaObject &&
schemaObject.type === "object" &&
schemaObject.enum) ||
[]);
if (oneOfType.length) {
if (oneOfType.every(tsIsPrimitive)) {
finalType = tsUnion([...(finalType ? [finalType] : []), ...oneOfType]);
}
else {
items = items.map((t) => escStr(t || ""));
finalType = tsIntersection([
...(finalType ? [finalType] : []),
tsUnion(oneOfType),
]);
}
return tsUnionOf(...items, ...(schemaObject.nullable ? ["null"] : []));
}
const oneOf = ((typeof schemaObject === "object" && !schemaObject.discriminator && schemaObject.oneOf) || schemaObject.enum || undefined);
if (oneOf && !oneOf.some((t) => "$ref" in t && ctx.discriminators[t.$ref])) {
const oneOfNormalized = oneOf.map((item) => transformSchemaObject(item, { path, ctx }));
if (schemaObject.nullable)
oneOfNormalized.push("null");
if ("type" in schemaObject && Array.isArray(schemaObject.type)) {
const coreTypes = schemaObject.type.map((t) => transformSchemaObject({ ...schemaObject, oneOf: undefined, type: t }, { path, ctx }));
return tsUnionOf(...oneOfNormalized, ...coreTypes);
if (finalType) {
if (schemaObject.nullable) {
return tsNullable([finalType]);
}
const oneOfTypes = oneOfNormalized.some((t) => typeof t === "string" && t.includes("{")) ? tsOneOf(...oneOfNormalized) : tsUnionOf(...oneOfNormalized);
if ("type" in schemaObject && schemaObject.type === "object" && (schemaObject.properties || schemaObject.additionalProperties)) {
return tsIntersectionOf(transformSchemaObject({ ...schemaObject, oneOf: undefined, enum: undefined }, { path, ctx }), oneOfTypes);
return finalType;
}
else {
if (!("type" in schemaObject)) {
return UNKNOWN;
}
return oneOfTypes;
return tsRecord(STRING, options.ctx.emptyObjectsUnknown ? UNKNOWN : NEVER);
}
if ("type" in schemaObject) {
if (schemaObject.type === "null")
return "null";
if (schemaObject.type === "string" || schemaObject.type === "boolean") {
return schemaObject.nullable ? tsUnionOf(schemaObject.type, "null") : schemaObject.type;
}
function transformSchemaObjectCore(schemaObject, options) {
if ("type" in schemaObject && schemaObject.type) {
if (schemaObject.type === "null") {
return NULL;
}
if (schemaObject.type === "string") {
return STRING;
}
if (schemaObject.type === "number" || schemaObject.type === "integer") {
return schemaObject.nullable ? tsUnionOf("number", "null") : "number";
return NUMBER;
}
if (schemaObject.type === "boolean") {
return BOOLEAN;
}
if (schemaObject.type === "array") {
indentLv++;
let itemType = "unknown";
let isTupleType = false;
let itemType = UNKNOWN;
if (schemaObject.prefixItems || Array.isArray(schemaObject.items)) {
isTupleType = true;
const result = [];
for (const item of schemaObject.prefixItems ?? schemaObject.items) {
result.push(transformSchemaObject(item, { path, ctx: { ...ctx, indentLv } }));
}
itemType = `[${result.join(", ")}]`;
const prefixItems = schemaObject.prefixItems ??
schemaObject.items;
itemType = ts.factory.createTupleTypeNode(prefixItems.map((item) => transformSchemaObject(item, options)));
}
else if (schemaObject.items) {
itemType = transformSchemaObject(schemaObject.items, { path, ctx: { ...ctx, indentLv } });
itemType = transformSchemaObject(schemaObject.items, options);
if (options.ctx.immutable) {
itemType = ts.factory.createTypeOperatorNode(ts.SyntaxKind.ReadonlyKeyword, itemType);
}
}
const min = typeof schemaObject.minItems === "number" && schemaObject.minItems >= 0 ? schemaObject.minItems : 0;
const max = typeof schemaObject.maxItems === "number" && schemaObject.maxItems >= 0 && min <= schemaObject.maxItems ? schemaObject.maxItems : undefined;
const min = typeof schemaObject.minItems === "number" && schemaObject.minItems >= 0
? schemaObject.minItems
: 0;
const max = typeof schemaObject.maxItems === "number" &&
schemaObject.maxItems >= 0 &&
min <= schemaObject.maxItems
? schemaObject.maxItems
: undefined;
const estimateCodeSize = typeof max !== "number" ? min : (max * (max + 1) - min * (min - 1)) / 2;
if (ctx.supportArrayLength && (min !== 0 || max !== undefined) && estimateCodeSize < 30) {
if (typeof schemaObject.maxItems !== "number") {
itemType = tsTupleOf(...Array.from({ length: min }).map(() => itemType), `...${tsArrayOf(itemType)}`);
return ctx.immutableTypes || schemaObject.readOnly ? tsReadonly(itemType) : itemType;
if (options.ctx.arrayLength &&
(min !== 0 || max !== undefined) &&
estimateCodeSize < 30) {
if (schemaObject.maxItems > 0) {
const members = [];
for (let i = 0; i <= (max ?? 0) - min; i++) {
const elements = [];
for (let j = min; j < i + min; j++) {
elements.push(itemType);
}
members.push(ts.factory.createTupleTypeNode(elements));
}
return tsUnion(members);
}
else {
return tsUnionOf(...Array.from({ length: (max ?? 0) - min + 1 })
.map((_, i) => i + min)
.map((n) => {
const t = tsTupleOf(...Array.from({ length: n }).map(() => itemType));
return ctx.immutableTypes || schemaObject.readOnly ? tsReadonly(t) : t;
}));
const elements = [];
for (let i = 0; i < min; i++) {
elements.push(itemType);
}
elements.push(ts.factory.createRestTypeNode(ts.factory.createArrayTypeNode(itemType)));
return ts.factory.createTupleTypeNode(elements);
}
}
if (!isTupleType) {
itemType = tsArrayOf(itemType);
return ts.isTupleTypeNode(itemType)
? itemType
: ts.factory.createArrayTypeNode(itemType);
}
if (Array.isArray(schemaObject.type) && !Array.isArray(schemaObject)) {
let uniqueTypes = [];
if (Array.isArray(schemaObject.oneOf)) {
for (const t of schemaObject.type) {
if ((t === "boolean" ||
t === "string" ||
t === "number" ||
t === "integer" ||
t === "null") &&
schemaObject.oneOf.find((o) => typeof o === "object" && "type" in o && o.type === t)) {
continue;
}
uniqueTypes.push(t === "null" || t === null
? NULL
: transformSchemaObject({ ...schemaObject, type: t, oneOf: undefined }, options));
}
}
itemType = ctx.immutableTypes || schemaObject.readOnly ? tsReadonly(itemType) : itemType;
return schemaObject.nullable ? tsUnionOf(itemType, "null") : itemType;
else {
uniqueTypes = schemaObject.type.map((t) => t === "null" || t === null
? NULL
: transformSchemaObject({ ...schemaObject, type: t }, options));
}
return tsUnion(uniqueTypes);
}
if (Array.isArray(schemaObject.type)) {
return tsUnionOf(...schemaObject.type.map((t) => transformSchemaObject({ ...schemaObject, type: t }, { path, ctx })));
}
}
const coreType = [];
const coreObjectType = [];
for (const k of ["oneOf", "allOf", "anyOf"]) {
if (!schemaObject[k])
if (!schemaObject[k]) {
continue;
const discriminatorRef = schemaObject[k].find((t) => "$ref" in t &&
(ctx.discriminators[t.$ref] ||
Object.values(ctx.discriminators).find((d) => d.oneOf?.includes(path))));
if (discriminatorRef && ctx.discriminators[discriminatorRef.$ref]) {
coreType.unshift(indent(getDiscriminatorPropertyName(path, ctx.discriminators[discriminatorRef.$ref]), indentLv + 1));
break;
}
}
for (const d of Object.values(ctx.discriminators)) {
if (d.oneOf?.includes(path)) {
coreType.unshift(indent(getDiscriminatorPropertyName(path, d), indentLv + 1));
const discriminator = !schemaObject.discriminator && options.ctx.discriminators[options.path];
if (discriminator) {
coreObjectType.unshift(createDiscriminatorProperty(discriminator, {
path: options.path,
readonly: options.ctx.immutable,
}));
break;
}
}
if (("properties" in schemaObject && schemaObject.properties && Object.keys(schemaObject.properties).length) ||
("additionalProperties" in schemaObject && schemaObject.additionalProperties) ||
if (("properties" in schemaObject &&
schemaObject.properties &&
Object.keys(schemaObject.properties).length) ||
("additionalProperties" in schemaObject &&
schemaObject.additionalProperties) ||
("$defs" in schemaObject && schemaObject.$defs)) {
indentLv++;
for (const [k, v] of getEntries(schemaObject.properties ?? {}, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(v, indentLv);
if (c)
coreType.push(indent(c, indentLv));
let key = escObjKey(k);
let isOptional = !Array.isArray(schemaObject.required) || !schemaObject.required.includes(k);
if (isOptional && ctx.defaultNonNullable && "default" in v)
isOptional = false;
if (isOptional)
key = tsOptionalProperty(key);
if (ctx.immutableTypes || schemaObject.readOnly)
key = tsReadonly(key);
coreType.push(indent(`${key}: ${transformSchemaObject(v, { path, ctx: { ...ctx, indentLv } })};`, indentLv));
}
if (schemaObject.additionalProperties || ctx.additionalProperties) {
let addlType = "unknown";
if (typeof schemaObject.additionalProperties === "object") {
if (!Object.keys(schemaObject.additionalProperties).length) {
addlType = "unknown";
if (Object.keys(schemaObject.properties ?? {}).length) {
for (const [k, v] of getEntries(schemaObject.properties ?? {}, options.ctx)) {
if (typeof v !== "object" || Array.isArray(v)) {
throw new Error(`${options.path}: invalid property ${k}. Expected Schema Object, got ${Array.isArray(v) ? "Array" : typeof v}`);
}
else {
addlType = transformSchemaObject(schemaObject.additionalProperties, {
path,
ctx: { ...ctx, indentLv },
if (options.ctx.excludeDeprecated) {
const resolved = "$ref" in v ? options.ctx.resolve(v.$ref) : v;
if (resolved?.deprecated) {
continue;
}
}
const optional = schemaObject.required?.includes(k) ||
("default" in v && options.ctx.defaultNonNullable)
? undefined
: QUESTION_TOKEN;
const type = "$ref" in v
? oapiRef(v.$ref)
: transformSchemaObject(v, {
...options,
path: createRef([options.path ?? "", k]),
});
}
const property = ts.factory.createPropertySignature(tsModifiers({
readonly: options.ctx.immutable || ("readOnly" in v && !!v.readOnly),
}), tsPropertyIndex(k), optional, type);
addJSDocComment(v, property);
coreObjectType.push(property);
}
const numProperties = schemaObject.properties ? Object.keys(schemaObject.properties).length : 0;
if (schemaObject.properties && ((!schemaObject.required && numProperties) || (schemaObject.required && numProperties !== schemaObject.required.length))) {
coreType.push(indent(`[key: string]: ${tsUnionOf(addlType ? addlType : "unknown", "undefined")};`, indentLv));
}
else {
coreType.push(indent(`[key: string]: ${addlType ? addlType : "unknown"};`, indentLv));
}
}
if (schemaObject.$defs && typeof schemaObject.$defs === "object" && Object.keys(schemaObject.$defs).length) {
coreType.push(indent(`$defs: ${transformSchemaObjectMap(schemaObject.$defs, { path: `${path}$defs/`, ctx: { ...ctx, indentLv } })};`, indentLv));
}
indentLv--;
}
let finalType = coreType.length ? `{\n${coreType.join("\n")}\n${indent("}", indentLv)}` : "";
function collectCompositions(items) {
const output = [];
for (const item of items) {
const itemType = transformSchemaObject(item, { path, ctx: { ...ctx, indentLv } });
if ("$ref" in item && ctx.discriminators[item.$ref]) {
output.push(tsOmit(itemType, [ctx.discriminators[item.$ref].propertyName]));
continue;
if (schemaObject.$defs &&
typeof schemaObject.$defs === "object" &&
Object.keys(schemaObject.$defs).length) {
const defKeys = [];
for (const [k, v] of Object.entries(schemaObject.$defs)) {
const property = ts.factory.createPropertySignature(tsModifiers({
readonly: options.ctx.immutable || ("readonly" in v && !!v.readOnly),
}), tsPropertyIndex(k), undefined, transformSchemaObject(v, {
...options,
path: createRef([options.path ?? "", "$defs", k]),
}));
addJSDocComment(v, property);
defKeys.push(property);
}
output.push(itemType);
coreObjectType.push(ts.factory.createPropertySignature(tsModifiers({
readonly: options.ctx.immutable,
}), tsPropertyIndex("$defs"), undefined, ts.factory.createTypeLiteralNode(defKeys)));
}
return output;
}
if (Array.isArray(schemaObject.oneOf) && schemaObject.oneOf.length) {
const oneOfType = tsUnionOf(...collectCompositions(schemaObject.oneOf));
finalType = finalType ? tsIntersectionOf(finalType, oneOfType) : oneOfType;
}
else {
if (Array.isArray(schemaObject.allOf) && schemaObject.allOf.length) {
finalType = tsIntersectionOf(...(finalType ? [finalType] : []), ...collectCompositions(schemaObject.allOf));
if ("required" in schemaObject && Array.isArray(schemaObject.required)) {
finalType = tsWithRequired(finalType, schemaObject.required);
}
if (schemaObject.additionalProperties || options.ctx.additionalProperties) {
const hasExplicitAdditionalProperties = typeof schemaObject.additionalProperties === "object" &&
Object.keys(schemaObject.additionalProperties).length;
const addlType = hasExplicitAdditionalProperties
? transformSchemaObject(schemaObject.additionalProperties, options)
: UNKNOWN;
coreObjectType.push(ts.factory.createIndexSignature(tsModifiers({
readonly: options.ctx.immutable,
}), [
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("key"), undefined, STRING),
], addlType));
}
if (Array.isArray(schemaObject.anyOf) && schemaObject.anyOf.length) {
const anyOfTypes = tsUnionOf(...collectCompositions(schemaObject.anyOf));
finalType = finalType ? tsIntersectionOf(finalType, anyOfTypes) : anyOfTypes;
}
}
if (schemaObject.nullable)
finalType = tsUnionOf(finalType || "Record<string, unknown>", "null");
if (finalType)
return finalType;
if (!("type" in schemaObject))
return "unknown";
return ctx.emptyObjectsUnknown ? "Record<string, unknown>" : "Record<string, never>";
return coreObjectType.length
? ts.factory.createTypeLiteralNode(coreObjectType)
: undefined;
}
export function getDiscriminatorPropertyName(path, discriminator) {
let value = parseRef(path).path.pop();
if (discriminator.mapping) {
const matchedValue = Object.entries(discriminator.mapping).find(([, v]) => (!v.startsWith("#") && v === value) || (v.startsWith("#") && parseRef(v).path.pop() === value));
if (matchedValue)
value = matchedValue[0];
}
return `${escObjKey(discriminator.propertyName)}: ${escStr(value)};`;
}
//# sourceMappingURL=schema-object.js.map

@@ -0,2 +1,3 @@

import ts from "typescript";
import type { GlobalContext, WebhooksObject } from "../types.js";
export default function transformWebhooksObject(webhooksObject: WebhooksObject, ctx: GlobalContext): string;
export default function transformWebhooksObject(webhooksObject: WebhooksObject, options: GlobalContext): ts.TypeNode;

@@ -1,17 +0,17 @@

import { escStr, getEntries, indent } from "../utils.js";
import ts from "typescript";
import { tsModifiers, tsPropertyIndex } from "../lib/ts.js";
import { createRef, getEntries } from "../lib/utils.js";
import transformPathItemObject from "./path-item-object.js";
export default function transformWebhooksObject(webhooksObject, ctx) {
let { indentLv } = ctx;
const output = ["{"];
indentLv++;
for (const [name, pathItemObject] of getEntries(webhooksObject, ctx.alphabetize, ctx.excludeDeprecated)) {
output.push(indent(`${escStr(name)}: ${transformPathItemObject(pathItemObject, {
path: `#/webhooks/${name}`,
ctx: { ...ctx, indentLv },
})};`, indentLv));
export default function transformWebhooksObject(webhooksObject, options) {
const type = [];
for (const [name, pathItemObject] of getEntries(webhooksObject, options)) {
type.push(ts.factory.createPropertySignature(tsModifiers({
readonly: options.immutable,
}), tsPropertyIndex(name), undefined, transformPathItemObject(pathItemObject, {
path: createRef(["webhooks", name]),
ctx: options,
})));
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}
//# sourceMappingURL=webhooks-object.js.map
/// <reference types="node" />
import type { PathLike } from "node:fs";
import type { RequestInfo, RequestInit, Response } from "undici";
import type { TransformSchemaObjectOptions } from "./transform/schema-object.js";
import type { Config as RedoclyConfig } from "@redocly/openapi-core";
import { PathLike } from "node:fs";
import type ts from "typescript";
export interface Extensable {

@@ -217,7 +217,7 @@ [key: `x-${string}`]: any;

export interface StringSubtype {
type: "string";
type: "string" | ["string", "null"];
enum?: (string | ReferenceObject)[];
}
export interface NumberSubtype {
type: "number";
type: "number" | ["number", "null"];
minimum?: number;

@@ -228,3 +228,3 @@ maximum?: number;

export interface IntegerSubtype {
type: "integer";
type: "integer" | ["integer", "null"];
minimum?: number;

@@ -235,3 +235,3 @@ maximum?: number;

export interface ArraySubtype {
type: "array";
type: "array" | ["array", "null"];
prefixItems?: (SchemaObject | ReferenceObject)[];

@@ -244,3 +244,3 @@ items?: SchemaObject | ReferenceObject | (SchemaObject | ReferenceObject)[];

export interface BooleanSubtype {
type: "boolean";
type: "boolean" | ["boolean", "null"];
enum?: (boolean | ReferenceObject)[];

@@ -313,79 +313,40 @@ }

alphabetize?: boolean;
auth?: string;
emptyObjectsUnknown?: boolean;
cwd?: PathLike;
defaultNonNullable?: boolean;
transform?: (schemaObject: SchemaObject, options: TransformSchemaObjectOptions) => string | undefined;
postTransform?: (type: string, options: TransformSchemaObjectOptions) => string | undefined;
immutableTypes?: boolean;
transform?: (schemaObject: SchemaObject, options: TransformNodeOptions) => ts.TypeNode | undefined;
postTransform?: (type: ts.TypeNode, options: TransformNodeOptions) => ts.TypeNode | undefined;
immutable?: boolean;
silent?: boolean;
version?: number;
httpHeaders?: Record<string, any>;
httpMethod?: string;
exportType?: boolean;
supportArrayLength?: boolean;
enum?: boolean;
arrayLength?: boolean;
pathParamsAsTypes?: boolean;
commentHeader?: string;
inject?: string;
fetch?: Fetch;
excludeDeprecated?: boolean;
redocly?: RedoclyConfig;
}
export type Subschema = {
hint: "LinkObject";
schema: LinkObject;
} | {
hint: "HeaderObject";
schema: HeaderObject;
} | {
hint: "MediaTypeObject";
schema: MediaTypeObject;
} | {
hint: "OpenAPI3";
schema: OpenAPI3;
} | {
hint: "OperationObject";
schema: OperationObject;
} | {
hint: "ParameterObject";
schema: ParameterObject;
} | {
hint: "ParameterObject[]";
schema: (ParameterObject | ReferenceObject)[] | Record<string, ParameterObject | ReferenceObject>;
} | {
hint: "RequestBodyObject";
schema: RequestBodyObject;
} | {
hint: "ResponseObject";
schema: ResponseObject;
} | {
hint: "SchemaMap";
schema: Record<string, SchemaObject | ReferenceObject | PathItemObject>;
} | {
hint: "SchemaObject";
schema: SchemaObject;
};
export interface GlobalContext {
additionalProperties: boolean;
alphabetize: boolean;
cwd?: PathLike;
defaultNonNullable: boolean;
discriminators: Record<string, DiscriminatorObject>;
emptyObjectsUnknown: boolean;
defaultNonNullable: boolean;
discriminators: {
[$ref: string]: DiscriminatorObject;
};
transform: OpenAPITSOptions["transform"];
enum: boolean;
excludeDeprecated: boolean;
exportType: boolean;
immutable: boolean;
injectFooter: ts.Node[];
pathParamsAsTypes: boolean;
postTransform: OpenAPITSOptions["postTransform"];
immutableTypes: boolean;
indentLv: number;
operations: Record<string, {
comment?: string;
operationType: string;
}>;
parameters: Record<string, ParameterObject>;
pathParamsAsTypes: boolean;
redoc: RedoclyConfig;
silent: boolean;
supportArrayLength: boolean;
excludeDeprecated: boolean;
arrayLength: boolean;
transform: OpenAPITSOptions["transform"];
resolve<T>(ref: string): T | undefined;
}
export type $defs = Record<string, SchemaObject>;
export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise<Response>;
export interface TransformNodeOptions {
path?: string;
ctx: GlobalContext;
}
{
"name": "openapi-typescript",
"description": "Convert OpenAPI 3.0 & 3.1 schemas to TypeScript",
"version": "6.7.0",
"version": "7.0.0-next.0",
"author": {

@@ -42,22 +42,2 @@ "name": "Drew Powers",

},
"dependencies": {
"ansi-colors": "^4.1.3",
"fast-glob": "^3.3.1",
"js-yaml": "^4.1.0",
"supports-color": "^9.4.0",
"undici": "^5.23.0",
"yargs-parser": "^21.1.1"
},
"devDependencies": {
"@types/degit": "^2.8.3",
"@types/js-yaml": "^4.0.5",
"@types/node": "^20.5.9",
"degit": "^2.8.4",
"del-cli": "^5.1.0",
"esbuild": "^0.19.2",
"execa": "^7.2.0",
"vite": "^4.4.9",
"vite-node": "^0.34.3",
"vitest": "^0.34.3"
},
"scripts": {

@@ -67,3 +47,3 @@ "build": "run-s -s build:*",

"build:esm": "tsc -p tsconfig.build.json",
"build:cjs": "esbuild --bundle --platform=node --target=es2019 --outfile=dist/index.cjs --external:js-yaml --external:undici src/index.ts --footer:js=\"module.exports = module.exports.default;\"",
"build:cjs": "esbuild --bundle --platform=node --target=es2019 --outfile=dist/index.cjs --external:@redocly/ajv --external:@redocly/openapi-core --external:typescript src/index.ts --footer:js=\"module.exports = module.exports.default;\"",
"dev": "tsc -p tsconfig.build.json --watch",

@@ -75,2 +55,3 @@ "download:schemas": "vite-node ./scripts/download-schemas.ts",

"lint:prettier": "prettier --check \"src/**/*\"",
"prepare": "pnpm run build",
"test": "run-p -s test:*",

@@ -82,3 +63,22 @@ "test:js": "vitest run",

"version": "pnpm run build"
},
"dependencies": {
"@redocly/openapi-core": "^1.2.0",
"ansi-colors": "^4.1.3",
"supports-color": "^9.4.0",
"typescript": "^5.2.2",
"yargs-parser": "^21.1.1"
},
"devDependencies": {
"@types/degit": "^2.8.4",
"@types/js-yaml": "^4.0.6",
"@types/node": "^20.8.0",
"degit": "^2.8.4",
"del-cli": "^5.1.0",
"esbuild": "^0.19.4",
"execa": "^7.2.0",
"vite": "^4.4.9",
"vite-node": "^0.34.6",
"vitest": "^0.34.6"
}
}
}

@@ -1,20 +0,25 @@

import type { GlobalContext, OpenAPI3, OpenAPITSOptions, SchemaObject, Subschema } from "./types.js";
import type { Readable } from "node:stream";
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";
import { createConfig } from "@redocly/openapi-core";
import { Readable } from "node:stream";
import ts from "typescript";
import { validateAndBundle } from "./lib/redoc.js";
import { debug, resolveRef, scanDiscriminators } from "./lib/utils.js";
import transformSchema from "./transform/index.js";
import type { GlobalContext, OpenAPI3, OpenAPITSOptions } from "./types.js";
export * from "./types.js"; // expose all types to consumers
export * from "./lib/ts.js";
export * from "./lib/utils.js";
export * from "./transform/index.js";
export * from "./transform/components-object.js";
export * from "./transform/header-object.js";
export * from "./transform/media-type-object.js";
export * from "./transform/operation-object.js";
export * from "./transform/parameter-object.js";
export * from "./transform/path-item-object.js";
export * from "./transform/paths-object.js";
export * from "./transform/request-body-object.js";
export * from "./transform/response-object.js";
export * from "./transform/responses-object.js";
export * from "./transform/schema-object.js";
export * from "./types.js";
const EMPTY_OBJECT_RE = /^\s*\{?\s*\}?\s*$/;
export const COMMENT_HEADER = `/**

@@ -28,227 +33,66 @@ * This file was auto-generated by openapi-typescript.

/**
* This function is the entry to the program and allows the user to pass in a remote schema and/or local schema.
* The URL or schema and headers can be passed in either programmatically and/or via the CLI.
* Remote schemas are fetched from a server that supplies JSON or YAML format via an HTTP GET request. File based schemas
* are loaded in via file path, most commonly prefixed with the file:// format. Alternatively, the user can pass in
* OpenAPI2 or OpenAPI3 schema objects that can be parsed directly by the function without reading the file system.
*
* Function overloading is utilized for generating stronger types for our different schema types and option types.
*
* @param {string} schema Root Swagger Schema HTTP URL, File URL, and/or JSON or YAML schema
* @param {SwaggerToTSOptions<typeof schema>} [options] Options to specify to the parsing system
* @return {Promise<string>} {Promise<string>} Parsed file schema
* Convert an OpenAPI schema to TypesScript AST
* @param {string|URL|object|Readable} source OpenAPI schema source:
* - YAML: string
* - JSON: parsed object
* - URL: URL to a YAML or JSON file (local or remote)
* - Readable: Readable stream of YAML or JSON
*/
async function openapiTS(schema: string | URL | OpenAPI3 | Readable, options: OpenAPITSOptions = {} as Partial<OpenAPITSOptions>): Promise<string> {
export default async function openapiTS(
source: string | URL | OpenAPI3 | Buffer | Readable,
options: OpenAPITSOptions = {} as Partial<OpenAPITSOptions>,
): Promise<ts.Node[]> {
if (!source) {
throw new Error(
"Empty schema. Please specify a URL, file path, or Redocly Config",
);
}
const redoc =
options.redocly ?? (await createConfig({}, { extends: ["minimal"] }));
const schema = await validateAndBundle(source, {
redoc,
cwd:
options.cwd instanceof URL
? options.cwd
: new URL(`file://${options.cwd ?? process.cwd()}/`),
silent: options.silent ?? false,
});
const ctx: GlobalContext = {
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,
defaultNonNullable: options.defaultNonNullable ?? true,
discriminators: scanDiscriminators(schema),
emptyObjectsUnknown: options.emptyObjectsUnknown ?? false,
indentLv: 0,
operations: {},
enum: options.enum ?? false,
excludeDeprecated: options.excludeDeprecated ?? false,
exportType: options.exportType ?? false,
immutable: options.immutable ?? false,
injectFooter: [],
pathParamsAsTypes: options.pathParamsAsTypes ?? false,
parameters: {},
postTransform:
typeof options.postTransform === "function"
? options.postTransform
: undefined,
redoc,
silent: options.silent ?? false,
supportArrayLength: options.supportArrayLength ?? false,
excludeDeprecated: options.excludeDeprecated ?? false,
arrayLength: options.arrayLength ?? false,
transform:
typeof options.transform === "function" ? options.transform : undefined,
resolve(ref) {
return resolveRef(schema, ref, { silent: options.silent ?? false });
},
};
// 1. load schema (and subschemas)
const allSchemas: { [id: string]: Subschema } = {};
const schemaURL: URL = typeof schema === "string" ? resolveSchema(schema) : (schema as URL);
let rootURL: URL = schemaURL;
const transformT = performance.now();
const result = transformSchema(schema, ctx);
debug(
"Completed AST transformation for entire document",
"ts",
performance.now() - transformT,
);
// 1a. if passed as in-memory JSON, handle `cwd` option
const isInlineSchema = typeof schema !== "string" && schema instanceof URL === false; // eslint-disable-line @typescript-eslint/no-unnecessary-boolean-literal-compare
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); // give the root schema an arbitrary filename ("root.yaml")
} else {
rootURL = new URL(VIRTUAL_JSON_URL); // otherwise, set virtual filename (which prevents resolutions)
}
}
await load(schemaURL, {
...ctx,
auth: options.auth,
schemas: allSchemas,
rootURL,
urlCache: new Set(),
httpHeaders: options.httpHeaders,
httpMethod: options.httpMethod,
fetch: options.fetch ?? getDefaultFetch(),
});
// 1. basic validation
for (const k of Object.keys(allSchemas)) {
const subschema = allSchemas[k];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (typeof (subschema.schema as any).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);
}
}
}
// 2. generate raw output
const output: string[] = [];
// 2a. Start file, inject custom code (if any)
if ("commentHeader" in options) {
if (options.commentHeader) output.push(options.commentHeader);
} else {
output.push(COMMENT_HEADER);
}
// 2b. options.inject
if (options.inject) output.push(options.inject);
// 2c. root schema
const rootTypes = transformSchema(allSchemas["."].schema as OpenAPI3, 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["."]; // garbage collect, but also remove from next step (external)
}
// 2d. external schemas (subschemas)
const externalKeys = Object.keys(allSchemas); // root schema (".") should already be removed
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 })); // sort external keys because they may have resolved in a different order each time
indentLv++;
for (const subschemaID of externalKeys) {
const subschema = allSchemas[subschemaID];
const key = escObjKey(subschemaID);
const path = `${subschemaID}#`;
let subschemaOutput = "";
let comment: string | undefined;
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[]": {
// hack: sometimes subschemas contain only a single SchemaObject or ParameterObject and get incorrectly hinted
// currently unknown what the real fix is, but this is a bandaid
if (typeof subschema.schema === "object" && ("schema" in subschema.schema || "type" in subschema.schema)) {
subschemaOutput = transformSchemaObject(subschema.schema as SchemaObject, { 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>;`, "");
}
// 3. operations (only get fully built after all external schemas transformed)
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>;`, "");
}
// 4a. OneOf type helper (@see https://github.com/Microsoft/TypeScript/issues/14094#issuecomment-723571692)
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;",
"",
);
}
// 4b. WithRequired type helper (@see https://github.com/drwpow/openapi-typescript/issues/657#issuecomment-1399274607)
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");
return result;
}
export default openapiTS;

@@ -1,3 +0,14 @@

import type { ComponentsObject, GlobalContext } from "../types.js";
import { escObjKey, getEntries, getSchemaObjectComment, indent, tsOptionalProperty, tsReadonly } from "../utils.js";
import ts from "typescript";
import {
NEVER,
addJSDocComment,
tsModifiers,
tsPropertyIndex,
} from "../lib/ts.js";
import { createRef, debug, getEntries } from "../lib/utils.js";
import {
ComponentsObject,
GlobalContext,
TransformNodeOptions,
} from "../types.js";
import transformHeaderObject from "./header-object.js";

@@ -8,164 +19,70 @@ import transformParameterObject from "./parameter-object.js";

import transformResponseObject from "./response-object.js";
import transformSchemaObjectMap from "./schema-object-map.js";
import transformSchemaObject from "./schema-object.js";
export default function transformComponentsObject(components: ComponentsObject, ctx: GlobalContext): string {
let { indentLv } = ctx;
const output: string[] = ["{"];
indentLv++;
type ComponentTransforms = keyof Omit<
ComponentsObject,
"examples" | "securitySchemes" | "links" | "callbacks"
>;
// schemas
if (components.schemas) {
const schemas = transformSchemaObjectMap(components.schemas, { path: "#/components/schemas/", ctx: { ...ctx, indentLv } });
output.push(indent(`schemas: ${schemas};`, indentLv));
} else {
output.push(indent("schemas: never;", indentLv));
}
const transformers: Record<
ComponentTransforms,
(node: any, options: TransformNodeOptions) => ts.TypeNode // eslint-disable-line @typescript-eslint/no-explicit-any
> = {
schemas: transformSchemaObject,
responses: transformResponseObject,
parameters: transformParameterObject,
requestBodies: transformRequestBodyObject,
headers: transformHeaderObject,
pathItems: transformPathItemObject,
};
// responses
if (components.responses) {
output.push(indent("responses: {", indentLv));
indentLv++;
for (const [name, responseObject] of getEntries(components.responses, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(responseObject, indentLv);
if (c) output.push(indent(c, indentLv));
let key = escObjKey(name);
if (ctx.immutableTypes) key = tsReadonly(key);
if ("$ref" in responseObject) {
output.push(indent(`${key}: ${transformSchemaObject(responseObject, { path: `#/components/responses/${name}`, ctx })};`, indentLv));
} else {
const responseType = transformResponseObject(responseObject, {
path: `#/components/responses/${name}`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${responseType};`, indentLv));
}
}
indentLv--;
output.push(indent("};", indentLv));
} else {
output.push(indent("responses: never;", indentLv));
}
/**
* Transform the ComponentsObject (4.8.7)
* @see https://spec.openapis.org/oas/latest.html#components-object
*/
export default function transformComponentsObject(
componentsObject: ComponentsObject,
ctx: GlobalContext,
): ts.TypeNode {
const type: ts.TypeElement[] = [];
// parameters
if (components.parameters) {
const parameters: string[] = [];
indentLv++;
for (const key of Object.keys(transformers) as ComponentTransforms[]) {
const componentT = performance.now();
for (const [name, parameterObject] of getEntries(components.parameters, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(parameterObject, indentLv);
if (c) parameters.push(indent(c, indentLv));
let key = escObjKey(name);
if (ctx.immutableTypes) key = tsReadonly(key);
if ("$ref" in parameterObject) {
parameters.push(indent(`${key}: ${transformSchemaObject(parameterObject, { path: `#/components/parameters/${name}`, ctx })};`, indentLv));
} else {
if (parameterObject.in !== "path" && !parameterObject.required) {
key = tsOptionalProperty(key);
}
const parameterType = transformParameterObject(parameterObject, {
path: `#/components/parameters/${name}`,
ctx: { ...ctx, indentLv },
const items: ts.TypeElement[] = [];
if (componentsObject[key]) {
for (const [name, item] of getEntries(componentsObject[key], ctx)) {
const subType = transformers[key](item, {
path: createRef(["components", key, name]),
ctx,
});
parameters.push(indent(`${key}: ${parameterType};`, indentLv));
}
}
indentLv--;
output.push(indent(`parameters: {`, indentLv), ...parameters, indent("};", indentLv));
} else {
output.push(indent("parameters: never;", indentLv));
}
// requestBodies
if (components.requestBodies) {
output.push(indent("requestBodies: {", indentLv));
indentLv++;
for (const [name, requestBodyObject] of getEntries(components.requestBodies, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(requestBodyObject, indentLv);
if (c) output.push(indent(c, indentLv));
let key = escObjKey(name);
if ("$ref" in requestBodyObject) {
if (ctx.immutableTypes) key = tsReadonly(key);
output.push(
indent(
`${key}: ${transformSchemaObject(requestBodyObject, {
path: `#/components/requestBodies/${name}`,
ctx: { ...ctx, indentLv },
})};`,
indentLv,
),
const property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({ readonly: ctx.immutable }),
/* name */ tsPropertyIndex(name),
/* questionToken */ undefined,
/* type */ subType,
);
} else {
if (!requestBodyObject.required) key = tsOptionalProperty(key);
if (ctx.immutableTypes) key = tsReadonly(key);
const requestBodyType = transformRequestBodyObject(requestBodyObject, {
path: `#/components/requestBodies/${name}`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${requestBodyType};`, indentLv));
addJSDocComment(item as unknown as any, property); // eslint-disable-line @typescript-eslint/no-explicit-any
items.push(property);
}
}
indentLv--;
output.push(indent("};", indentLv));
} else {
output.push(indent("requestBodies: never;", indentLv));
}
type.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex(key),
/* questionToken */ undefined,
/* type */ items.length
? ts.factory.createTypeLiteralNode(items)
: NEVER,
),
);
// headers
if (components.headers) {
output.push(indent("headers: {", indentLv));
indentLv++;
for (const [name, headerObject] of getEntries(components.headers, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(headerObject, indentLv);
if (c) output.push(indent(c, indentLv));
let key = escObjKey(name);
if (ctx.immutableTypes) key = tsReadonly(key);
if ("$ref" in headerObject) {
output.push(indent(`${key}: ${transformSchemaObject(headerObject, { path: `#/components/headers/${name}`, ctx })};`, indentLv));
} else {
const headerType = transformHeaderObject(headerObject, {
path: `#/components/headers/${name}`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${headerType};`, indentLv));
}
}
indentLv--;
output.push(indent("};", indentLv));
} else {
output.push(indent("headers: never;", indentLv));
debug(
`Transformed components → ${key}`,
"ts",
performance.now() - componentT,
);
}
// pathItems
if (components.pathItems) {
output.push(indent("pathItems: {", indentLv));
indentLv++;
for (const [name, pathItemObject] of getEntries(components.pathItems, ctx.alphabetize, ctx.excludeDeprecated)) {
let key = escObjKey(name);
if (ctx.immutableTypes) key = tsReadonly(key);
if ("$ref" in pathItemObject) {
const c = getSchemaObjectComment(pathItemObject, indentLv);
if (c) output.push(indent(c, indentLv));
output.push(indent(`${key}: ${transformSchemaObject(pathItemObject, { path: `#/components/pathItems/${name}`, ctx })};`, indentLv));
} else {
output.push(
indent(
`${key}: ${transformPathItemObject(pathItemObject, {
path: `#/components/pathItems/${name}`,
ctx: { ...ctx, indentLv },
})};`,
indentLv,
),
);
}
}
indentLv--;
output.push(indent("};", indentLv));
} else {
output.push(indent("pathItems: never;", indentLv));
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}

@@ -1,34 +0,56 @@

import type { GlobalContext, HeaderObject } from "../types.js";
import { escStr, getEntries, getSchemaObjectComment, indent, tsReadonly } from "../utils.js";
import { escapePointer } from "@redocly/openapi-core/lib/ref-utils.js";
import ts from "typescript";
import {
addJSDocComment,
tsModifiers,
tsPropertyIndex,
UNKNOWN,
} from "../lib/ts.js";
import { getEntries } from "../lib/utils.js";
import { HeaderObject, TransformNodeOptions } from "../types.js";
import transformMediaTypeObject from "./media-type-object.js";
import transformSchemaObject from "./schema-object.js";
export interface TransformHeaderObjectOptions {
path: string;
ctx: GlobalContext;
}
/**
* Transform HeaderObject nodes (4.8.21)
* @see https://spec.openapis.org/oas/v3.1.0#header-object
*/
export default function transformHeaderObject(
headerObject: HeaderObject,
options: TransformNodeOptions,
): ts.TypeNode {
if (headerObject.schema) {
return transformSchemaObject(headerObject.schema, options);
}
export default function transformHeaderObject(headerObject: HeaderObject, { path, ctx }: TransformHeaderObjectOptions): string {
if (headerObject.schema) return transformSchemaObject(headerObject.schema, { path, ctx });
if (headerObject.content) {
let { indentLv } = ctx;
const output: string[] = ["{"];
indentLv++;
for (const [contentType, mediaTypeObject] of getEntries(headerObject.content, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(mediaTypeObject, indentLv);
if (c) output.push(indent(c, indentLv));
let key = escStr(contentType);
if (ctx.immutableTypes) key = tsReadonly(key);
if ("$ref" in mediaTypeObject) {
output.push(indent(`${key}: ${transformSchemaObject(mediaTypeObject, { path: `${path}/${contentType}`, ctx })};`, indentLv));
} else {
const mediaType = transformMediaTypeObject(mediaTypeObject, { path: `${path}/${contentType}`, ctx });
output.push(indent(`${key}: ${mediaType};`, indentLv));
}
const type: ts.TypeElement[] = [];
for (const [contentType, mediaTypeObject] of getEntries(
headerObject.content,
options.ctx,
)) {
const nextPath = `${options.path ?? "#"}/${escapePointer(contentType)}`;
const mediaType =
"$ref" in mediaTypeObject
? transformSchemaObject(mediaTypeObject, {
...options,
path: nextPath,
})
: transformMediaTypeObject(mediaTypeObject, {
...options,
path: nextPath,
});
const property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
/* name */ tsPropertyIndex(contentType),
/* questionToken */ undefined,
/* type */ mediaType,
);
addJSDocComment(mediaTypeObject, property);
type.push(property);
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}
return "unknown";
return UNKNOWN;
}

@@ -1,30 +0,104 @@

import type { GlobalContext, OpenAPI3 } from "../types.js";
import ts, { InterfaceDeclaration, TypeLiteralNode } from "typescript";
import { NEVER, STRING, tsModifiers, tsRecord } from "../lib/ts.js";
import { createRef, debug } from "../lib/utils.js";
import { GlobalContext, OpenAPI3 } from "../types.js";
import transformComponentsObject from "./components-object.js";
import transformPathsObject from "./paths-object.js";
import transformSchemaObjectMap from "./schema-object-map.js";
import transformSchemaObject from "./schema-object.js";
import transformWebhooksObject from "./webhooks-object.js";
/** transform top-level schema */
export function transformSchema(schema: OpenAPI3, ctx: GlobalContext): Record<string, string> {
if (!schema) return {};
type SchemaTransforms = keyof Pick<
OpenAPI3,
"paths" | "webhooks" | "components" | "$defs"
>;
const output: Record<string, string> = {};
const transformers: Record<
SchemaTransforms,
(node: any, options: GlobalContext) => ts.TypeNode // eslint-disable-line @typescript-eslint/no-explicit-any
> = {
paths: transformPathsObject,
webhooks: transformWebhooksObject,
components: transformComponentsObject,
$defs: (node, options) =>
transformSchemaObject(node, { path: createRef(["$defs"]), ctx: options }),
};
// paths
if (schema.paths) output.paths = transformPathsObject(schema.paths, ctx);
else output.paths = "";
export default function transformSchema(schema: OpenAPI3, ctx: GlobalContext) {
const type: ts.Node[] = [];
// webhooks
if (schema.webhooks) output.webhooks = transformWebhooksObject(schema.webhooks, ctx);
else output.webhooks = "";
for (const root of Object.keys(transformers) as SchemaTransforms[]) {
const emptyObj = ts.factory.createTypeAliasDeclaration(
/* modifiers */ tsModifiers({
export: true,
readonly: ctx.immutable,
}),
/* name */ root,
/* typeParameters */ undefined,
/* type */ tsRecord(STRING, NEVER),
);
// components
if (schema.components) output.components = transformComponentsObject(schema.components, ctx);
else output.components = "";
if (schema[root] && typeof schema[root] === "object") {
const rootT = performance.now();
const subType = transformers[root](schema[root], ctx);
if ((subType as ts.TypeLiteralNode).members?.length) {
type.push(
ctx.exportType
? ts.factory.createTypeAliasDeclaration(
/* modifiers */ tsModifiers({
export: true,
readonly: ctx.immutable,
}),
/* name */ root,
/* typeParameters */ undefined,
/* type */ subType,
)
: ts.factory.createInterfaceDeclaration(
/* modifiers */ tsModifiers({
export: true,
readonly: ctx.immutable,
}),
/* name */ root,
/* typeParameters */ undefined,
/* heritageClauses */ undefined,
/* members */ (subType as TypeLiteralNode).members,
),
);
debug(`${root} done`, "ts", performance.now() - rootT);
} else {
type.push(emptyObj);
debug(`${root} done (skipped)`, "ts", 0);
}
} else {
type.push(emptyObj);
debug(`${root} done (skipped)`, "ts", 0);
}
}
// $defs
if (schema.$defs) output.$defs = transformSchemaObjectMap(schema.$defs, { path: "$defs/", ctx });
else output.$defs = "";
// inject
let hasOperations = false;
for (const injectedType of ctx.injectFooter) {
if (
!hasOperations &&
(injectedType as InterfaceDeclaration)?.name?.escapedText === "operations"
) {
hasOperations = true;
}
type.push(injectedType);
}
if (!hasOperations) {
// if no operations created, inject empty operations type
type.push(
ts.factory.createTypeAliasDeclaration(
/* modifiers */ tsModifiers({
export: true,
readonly: ctx.immutable,
}),
/* name */ "operations",
/* typeParameters */ undefined,
/* type */ tsRecord(STRING, NEVER),
),
);
}
return output;
return type;
}

@@ -1,12 +0,18 @@

import type { GlobalContext, MediaTypeObject } from "../types.js";
import ts from "typescript";
import { UNKNOWN } from "../lib/ts.js";
import { MediaTypeObject, TransformNodeOptions } from "../types.js";
import transformSchemaObject from "./schema-object.js";
export interface TransformMediaTypeObjectOptions {
path: string;
ctx: GlobalContext;
/**
* Transform MediaTypeObject nodes (4.8.14)
* @see https://spec.openapis.org/oas/v3.1.0#media-type-object
*/
export default function transformMediaTypeObject(
mediaTypeObject: MediaTypeObject,
options: TransformNodeOptions,
): ts.TypeNode {
if (!mediaTypeObject.schema) {
return UNKNOWN;
}
return transformSchemaObject(mediaTypeObject.schema, options);
}
export default function transformMediaTypeObject(mediaTypeObject: MediaTypeObject, { path, ctx }: TransformMediaTypeObjectOptions): string {
if (!mediaTypeObject.schema) return "unknown";
return transformSchemaObject(mediaTypeObject.schema, { path, ctx });
}

@@ -1,124 +0,124 @@

import type { GlobalContext, OperationObject, ParameterObject } from "../types.js";
import { escObjKey, getEntries, getSchemaObjectComment, indent, tsOptionalProperty, tsReadonly } from "../utils.js";
import transformParameterObject from "./parameter-object.js";
import ts from "typescript";
import {
NEVER,
QUESTION_TOKEN,
addJSDocComment,
oapiRef,
tsModifiers,
tsPropertyIndex,
} from "../lib/ts.js";
import { createRef } from "../lib/utils.js";
import {
OperationObject,
RequestBodyObject,
TransformNodeOptions,
} from "../types.js";
import { transformParametersArray } from "./parameters-array.js";
import transformRequestBodyObject from "./request-body-object.js";
import transformResponseObject from "./response-object.js";
import transformSchemaObject from "./schema-object.js";
import transformResponsesObject from "./responses-object.js";
export interface TransformOperationObjectOptions {
path: string;
ctx: GlobalContext;
wrapObject?: boolean;
}
/**
* Transform OperationObject nodes (4.8.10)
* @see https://spec.openapis.org/oas/v3.1.0#operation-object
*/
export default function transformOperationObject(
operationObject: OperationObject,
options: TransformNodeOptions,
): ts.TypeElement[] {
const type: ts.TypeElement[] = [];
export default function transformOperationObject(operationObject: OperationObject, { path, ctx, wrapObject = true }: TransformOperationObjectOptions): string {
let { indentLv } = ctx;
const output: string[] = wrapObject ? ["{"] : [];
indentLv++;
// parameters
{
if (operationObject.parameters) {
const parameterOutput: string[] = [];
indentLv++;
for (const paramIn of ["query", "header", "path", "cookie"] as ParameterObject["in"][]) {
const paramInternalOutput: string[] = [];
indentLv++;
let paramInOptional = true;
for (const param of operationObject.parameters ?? []) {
const node: ParameterObject | undefined = "$ref" in param ? ctx.parameters[param.$ref] : param;
if (node?.in !== paramIn) continue;
let key = escObjKey(node.name);
const isRequired = paramIn === "path" || !!node.required;
if (isRequired) {
paramInOptional = false;
} else {
key = tsOptionalProperty(key);
}
const c = getSchemaObjectComment(param, indentLv);
if (c) paramInternalOutput.push(indent(c, indentLv));
const parameterType =
"$ref" in param
? param.$ref
: transformParameterObject(param, {
path: `${path}/parameters/${param.name}`,
ctx: { ...ctx, indentLv },
});
paramInternalOutput.push(indent(`${key}: ${parameterType};`, indentLv));
}
indentLv--;
if (paramInternalOutput.length) {
const key = paramInOptional ? tsOptionalProperty(paramIn) : paramIn;
parameterOutput.push(indent(`${key}: {`, indentLv));
parameterOutput.push(...paramInternalOutput);
parameterOutput.push(indent(`};`, indentLv));
}
}
indentLv--;
type.push(
...transformParametersArray(operationObject.parameters ?? [], options),
);
if (parameterOutput.length) {
output.push(indent(`parameters: {`, indentLv));
output.push(parameterOutput.join("\n"));
output.push(indent("};", indentLv));
}
}
}
// requestBody
{
if (operationObject.requestBody) {
const c = getSchemaObjectComment(operationObject.requestBody, indentLv);
if (c) output.push(indent(c, indentLv));
let key = "requestBody";
if (ctx.immutableTypes) key = tsReadonly(key);
if ("$ref" in operationObject.requestBody) {
output.push(indent(`${key}: ${transformSchemaObject(operationObject.requestBody, { path, ctx })};`, indentLv));
} else {
if (!operationObject.requestBody.required) key = tsOptionalProperty(key);
const requestBody = transformRequestBodyObject(operationObject.requestBody, {
path: `${path}/requestBody`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${requestBody};`, indentLv));
}
}
if (operationObject.requestBody) {
const requestBodyType =
"$ref" in operationObject.requestBody
? oapiRef(operationObject.requestBody.$ref)
: transformRequestBodyObject(operationObject.requestBody, {
...options,
path: createRef([options.path!, "requestBody"]),
});
const required = !!(
"$ref" in operationObject.requestBody
? options.ctx.resolve<RequestBodyObject>(
operationObject.requestBody.$ref,
)
: operationObject.requestBody
)?.required;
const property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
/* name */ tsPropertyIndex("requestBody"),
/* questionToken */ required ? undefined : QUESTION_TOKEN,
/* type */ requestBodyType,
);
addJSDocComment(operationObject.requestBody, property);
type.push(property);
} else {
type.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex("requestBody"),
/* questionToken */ QUESTION_TOKEN,
/* type */ NEVER,
),
);
}
// responses
{
if (operationObject.responses) {
output.push(indent(`responses: {`, indentLv));
indentLv++;
for (const [responseCode, responseObject] of getEntries(operationObject.responses, ctx.alphabetize, ctx.excludeDeprecated)) {
const key = escObjKey(responseCode);
const c = getSchemaObjectComment(responseObject, indentLv);
if (c) output.push(indent(c, indentLv));
if ("$ref" in responseObject) {
output.push(
indent(
`${key}: ${transformSchemaObject(responseObject, {
path: `${path}/responses/${responseCode}`,
ctx,
})};`,
indentLv,
),
);
} else {
const responseType = transformResponseObject(responseObject, {
path: `${path}/responses/${responseCode}`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${responseType};`, indentLv));
}
}
indentLv--;
output.push(indent(`};`, indentLv));
}
type.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex("responses"),
/* questionToken */ undefined,
/* type */ transformResponsesObject(
operationObject.responses ?? {},
options,
),
),
);
return type;
}
/** inject an operation at the top level */
export function injectOperationObject(
operationId: string,
operationObject: OperationObject,
options: TransformNodeOptions,
): void {
// find or create top-level operations interface
let operations = options.ctx.injectFooter.find(
(node) =>
ts.isInterfaceDeclaration(node) &&
(node as ts.InterfaceDeclaration).name.text === "operations",
) as unknown as ts.InterfaceDeclaration;
if (!operations) {
operations = ts.factory.createInterfaceDeclaration(
/* modifiers */ tsModifiers({
export: true,
readonly: options.ctx.immutable,
}),
/* name */ ts.factory.createIdentifier("operations"),
/* typeParameters */ undefined,
/* heritageClauses */ undefined,
/* members */ [],
);
options.ctx.injectFooter.push(operations);
}
indentLv--;
if (wrapObject) {
output.push(indent("}", indentLv));
}
return output.join("\n");
// inject operation object
const type = transformOperationObject(operationObject, options);
// @ts-expect-error this is OK to mutate
operations.members = ts.factory.createNodeArray([
...operations.members,
ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
/* name */ tsPropertyIndex(operationId),
/* questionToken */ undefined,
/* type */ ts.factory.createTypeLiteralNode(type),
),
]);
}

@@ -1,11 +0,17 @@

import type { GlobalContext, ParameterObject } from "../types.js";
import ts from "typescript";
import { STRING } from "../lib/ts.js";
import { ParameterObject, TransformNodeOptions } from "../types.js";
import transformSchemaObject from "./schema-object.js";
export interface TransformParameterObjectOptions {
path: string;
ctx: GlobalContext;
/**
* Transform ParameterObject nodes (4.8.12)
* @see https://spec.openapis.org/oas/v3.1.0#parameter-object
*/
export default function transformParameterObject(
parameterObject: ParameterObject,
options: TransformNodeOptions,
): ts.TypeNode {
return parameterObject.schema
? transformSchemaObject(parameterObject.schema, options)
: STRING; // assume a parameter is a string by default rather than "unknown"
}
export default function transformParameterObject(parameterObject: ParameterObject, { path, ctx }: TransformParameterObjectOptions): string {
return parameterObject.schema ? transformSchemaObject(parameterObject.schema, { path, ctx }) : "string"; // assume a parameter is a string by default rather than "unknown"
}

@@ -1,60 +0,123 @@

import type { GlobalContext, ParameterObject, PathItemObject, ReferenceObject } from "../types.js";
import { escStr, getSchemaObjectComment, indent } from "../utils.js";
import transformOperationObject from "./operation-object.js";
import ts from "typescript";
import { NEVER, addJSDocComment, oapiRef, tsPropertyIndex } from "../lib/ts.js";
import { createRef } from "../lib/utils.js";
import {
OperationObject,
ParameterObject,
PathItemObject,
ReferenceObject,
TransformNodeOptions,
} from "../types.js";
import transformOperationObject, {
injectOperationObject,
} from "./operation-object.js";
import { transformParametersArray } from "./parameters-array.js";
export interface TransformPathItemObjectOptions {
path: string;
ctx: GlobalContext;
}
export type Method =
| "get"
| "put"
| "post"
| "delete"
| "options"
| "head"
| "patch"
| "trace";
export type Method = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
/**
* Transform PathItem nodes (4.8.9)
* @see https://spec.openapis.org/oas/v3.1.0#path-item-object
*/
export default function transformPathItemObject(
pathItem: PathItemObject,
options: TransformNodeOptions,
): ts.TypeNode {
const type: ts.TypeElement[] = [];
export default function transformPathItemObject(pathItem: PathItemObject, { path, ctx }: TransformPathItemObjectOptions): string {
let { indentLv } = ctx;
const output: string[] = [];
output.push("{");
indentLv++;
// parameters
type.push(
...transformParametersArray(pathItem.parameters ?? [], {
...options,
path: createRef([options.path!, "parameters"]),
}),
);
// methods
for (const method of ["get", "put", "post", "delete", "options", "head", "patch", "trace"] as Method[]) {
for (const method of [
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
] as Method[]) {
const operationObject = pathItem[method];
if (!operationObject) continue;
const c = getSchemaObjectComment(operationObject, indentLv);
if (c) output.push(indent(c, indentLv));
if (
!operationObject ||
(options.ctx.excludeDeprecated &&
("$ref" in operationObject
? options.ctx.resolve<OperationObject>(operationObject.$ref)
: operationObject
)?.deprecated)
) {
type.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex(method),
/* questionToken */ undefined,
/* type */ NEVER,
),
);
continue;
}
// fold top-level PathItem parameters into method-level, with the latter overriding the former
const keyedParameters: Record<string, ParameterObject | ReferenceObject> = {};
const keyedParameters: Record<string, ParameterObject | ReferenceObject> =
{};
if (!("$ref" in operationObject)) {
// important: OperationObject parameters come last, and will override any conflicts with PathItem parameters
for (const parameter of [...(pathItem.parameters ?? []), ...(operationObject.parameters ?? [])]) {
for (const parameter of [
...(pathItem.parameters ?? []),
...(operationObject.parameters ?? []),
]) {
// note: the actual key doesn’t matter here, as long as it can match between PathItem and OperationObject
keyedParameters["$ref" in parameter ? parameter.$ref : parameter.name] = parameter;
keyedParameters["$ref" in parameter ? parameter.$ref : parameter.name] =
parameter;
}
}
let operationType: ts.TypeNode;
if ("$ref" in operationObject) {
output.push(indent(`${method}: ${operationObject.$ref}`, indentLv));
operationType = oapiRef(operationObject.$ref);
}
// if operationId exists, move into an `operations` export and pass the reference in here
else if (operationObject.operationId) {
const operationType = transformOperationObject({ ...operationObject, parameters: Object.values(keyedParameters) }, { path, ctx: { ...ctx, indentLv: 1 } });
ctx.operations[operationObject.operationId] = {
operationType,
comment: getSchemaObjectComment(operationObject, 1),
};
output.push(indent(`${method}: operations[${escStr(operationObject.operationId)}];`, indentLv));
operationType = oapiRef(
createRef(["operations", operationObject.operationId]),
);
injectOperationObject(
operationObject.operationId,
{ ...operationObject, parameters: Object.values(keyedParameters) },
{ ...options, path: createRef([options.path!, method]) },
);
} else {
const operationType = transformOperationObject({ ...operationObject, parameters: Object.values(keyedParameters) }, { path, ctx: { ...ctx, indentLv } });
output.push(indent(`${method}: ${operationType};`, indentLv));
operationType = ts.factory.createTypeLiteralNode(
transformOperationObject(
{ ...operationObject, parameters: Object.values(keyedParameters) },
{ ...options, path: createRef([options.path!, method]) },
),
);
}
const property = ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex(method),
/* questionToken */ undefined,
/* type */ operationType,
);
addJSDocComment(operationObject, property);
type.push(property);
}
// parameters
if (pathItem.parameters?.length) {
output.push(indent(transformOperationObject({ parameters: pathItem.parameters }, { path, ctx, wrapObject: false }).trim(), indentLv));
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}

@@ -1,60 +0,155 @@

import type { GlobalContext, PathsObject, PathItemObject, ParameterObject, ReferenceObject, OperationObject } from "../types.js";
import { escStr, getEntries, getSchemaObjectComment, indent } from "../utils.js";
import transformParameterObject from "./parameter-object.js";
import transformPathItemObject from "./path-item-object.js";
import ts from "typescript";
import {
addJSDocComment,
oapiRef,
stringToAST,
tsPropertyIndex,
} from "../lib/ts.js";
import { createRef, debug, getEntries } from "../lib/utils.js";
import {
GlobalContext,
OperationObject,
ParameterObject,
PathItemObject,
PathsObject,
ReferenceObject,
} from "../types.js";
import transformPathItemObject, { Method } from "./path-item-object.js";
const OPERATIONS = ["get", "post", "put", "delete", "options", "head", "patch", "trace"];
const PATH_PARAM_RE = /\{[^}]+\}/g;
function extractPathParams(obj?: ReferenceObject | PathItemObject | OperationObject | undefined): Map<string, ParameterObject> {
const params = new Map<string, ParameterObject>();
if (obj && "parameters" in obj) {
for (const p of obj.parameters ?? []) {
if ("in" in p && p.in === "path") params.set(p.name, p);
/**
* Transform the PathsObject node (4.8.8)
* @see https://spec.openapis.org/oas/v3.1.0#operation-object
*/
export default function transformPathsObject(
pathsObject: PathsObject,
ctx: GlobalContext,
): ts.TypeNode {
const type: ts.TypeElement[] = [];
for (const [url, pathItemObject] of getEntries(pathsObject, ctx)) {
if (!pathItemObject || typeof pathItemObject !== "object") {
continue;
}
}
return params;
}
export default function transformPathsObject(pathsObject: PathsObject, ctx: GlobalContext): string {
let { indentLv } = ctx;
const output: string[] = ["{"];
indentLv++;
for (const [url, pathItemObject] of getEntries(pathsObject, ctx.alphabetize, ctx.excludeDeprecated)) {
if (!pathItemObject || typeof pathItemObject !== "object") continue;
let path = url;
const pathT = performance.now();
// handle $ref
if ("$ref" in pathItemObject) {
const c = getSchemaObjectComment(pathItemObject, indentLv);
if (c) output.push(indent(c, indentLv));
output.push(indent(`${escStr(path)}: ${pathItemObject.$ref};`, indentLv));
continue;
const property = ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex(url),
/* questionToken */ undefined,
/* type */ oapiRef(pathItemObject.$ref),
);
addJSDocComment(pathItemObject, property);
} else {
const pathItemType = transformPathItemObject(pathItemObject, {
path: createRef(["paths", url]),
ctx,
});
// pathParamsAsTypes
if (ctx.pathParamsAsTypes && url.includes("{")) {
const pathParams = extractPathParams(pathItemObject, ctx);
const matches = url.match(PATH_PARAM_RE);
let rawPath = `\`${url}\``;
if (matches) {
/* eslint-disable @typescript-eslint/no-explicit-any */
for (const match of matches) {
const paramName = match.slice(1, -1);
const param = pathParams[paramName];
if (!param) {
rawPath = rawPath.replace(match, "${string}");
} else {
rawPath = rawPath.replace(
match,
`$\{${(param.schema as any)?.type ?? "string"}}`,
);
}
}
// note: creating a string template literal’s AST manually is hard!
// just pass an arbitrary string to TS
const pathType = (stringToAST(rawPath)[0] as any)?.expression;
if (pathType) {
type.push(
ts.factory.createIndexSignature(
/* modifiers */ undefined,
/* parameters */ [
ts.factory.createParameterDeclaration(
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
/* name */ "path",
/* questionToken */ undefined,
/* type */ pathType,
/* initializer */ undefined,
),
],
/* type */ pathItemType,
),
);
continue;
}
/* eslint-enable @typescript-eslint/no-explicit-any */
}
}
type.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex(url),
/* questionToken */ undefined,
/* type */ pathItemType,
),
);
debug(`Transformed path "${url}"`, "ts", performance.now() - pathT);
}
}
const pathParams = new Map([...extractPathParams(pathItemObject), ...OPERATIONS.flatMap((op) => Array.from(extractPathParams(pathItemObject[op as keyof PathItemObject])))]);
return ts.factory.createTypeLiteralNode(type);
}
// build dynamic string template literal index
if (ctx.pathParamsAsTypes && pathParams.size) {
for (const p of pathParams.values()) {
const paramType = transformParameterObject(p, { path: `#/paths/${url}/parameters/${p.name}`, ctx });
path = path.replace(`{${p.name}}`, `\${${paramType}}`);
function extractPathParams(pathItemObject: PathItemObject, ctx: GlobalContext) {
const params: Record<string, ParameterObject> = {};
for (const p of pathItemObject.parameters ?? []) {
const resolved =
"$ref" in p && p.$ref
? ctx.resolve<ParameterObject>(p.$ref)
: (p as ParameterObject);
if (resolved && resolved.in === "path") {
params[resolved.name] = resolved;
}
}
for (const method of [
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
] as Method[]) {
if (!(method in pathItemObject)) {
continue;
}
const resolvedMethod = (pathItemObject[method] as ReferenceObject).$ref
? ctx.resolve<OperationObject>(
(pathItemObject[method] as ReferenceObject).$ref,
)
: (pathItemObject[method] as OperationObject);
if (resolvedMethod?.parameters) {
for (const p of resolvedMethod.parameters) {
const resolvedParam =
"$ref" in p && p.$ref
? ctx.resolve<ParameterObject>(p.$ref)
: (p as ParameterObject);
if (resolvedParam && resolvedParam.in === "path") {
params[resolvedParam.name] = resolvedParam;
}
}
path = `[path: \`${path}\`]`;
} else {
path = escStr(path);
}
output.push(
indent(
`${path}: ${transformPathItemObject(pathItemObject, {
path: `#/paths/${url}`,
ctx: { ...ctx, indentLv },
})};`,
indentLv,
),
);
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return params;
}

@@ -1,50 +0,68 @@

import type { GlobalContext, RequestBodyObject } from "../types.js";
import { escStr, getEntries, getSchemaObjectComment, indent, tsReadonly } from "../utils.js";
import ts from "typescript";
import {
NEVER,
QUESTION_TOKEN,
addJSDocComment,
tsModifiers,
tsPropertyIndex,
} from "../lib/ts.js";
import { createRef, getEntries } from "../lib/utils.js";
import { RequestBodyObject, TransformNodeOptions } from "../types.js";
import transformMediaTypeObject from "./media-type-object.js";
import transformSchemaObject from "./schema-object.js";
export interface TransformRequestBodyObjectOptions {
path: string;
ctx: GlobalContext;
}
export default function transformRequestBodyObject(requestBodyObject: RequestBodyObject, { path, ctx }: TransformRequestBodyObjectOptions): string {
let { indentLv } = ctx;
const output: string[] = ["{"];
indentLv++;
output.push(indent(ctx.immutableTypes ? tsReadonly("content: {") : "content: {", indentLv));
indentLv++;
if (!Object.keys(requestBodyObject.content).length) {
output.push(indent(`${escStr("*/*")}: never;`, indentLv));
/**
* Transform RequestBodyObject nodes (4.8.13)
* @see https://spec.openapis.org/oas/v3.1.0#request-body-object
*/
export default function transformRequestBodyObject(
requestBodyObject: RequestBodyObject,
options: TransformNodeOptions,
): ts.TypeNode {
const type: ts.TypeElement[] = [];
for (const [contentType, mediaTypeObject] of getEntries(
requestBodyObject.content,
options.ctx,
)) {
const nextPath = createRef([options.path!, contentType]);
const mediaType =
"$ref" in mediaTypeObject
? transformSchemaObject(mediaTypeObject, {
...options,
path: nextPath,
})
: transformMediaTypeObject(mediaTypeObject, {
...options,
path: nextPath,
});
const property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
/* name */ tsPropertyIndex(contentType),
/* questionToken */ undefined,
/* type */ mediaType,
);
addJSDocComment(mediaTypeObject, property);
type.push(property);
}
for (const [contentType, mediaTypeObject] of getEntries(requestBodyObject.content, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(mediaTypeObject, indentLv);
if (c) output.push(indent(c, indentLv));
let key = escStr(contentType);
if (ctx.immutableTypes) key = tsReadonly(key);
if ("$ref" in mediaTypeObject) {
output.push(
indent(
`${key}: ${transformSchemaObject(mediaTypeObject, {
path: `${path}/${contentType}`,
ctx: { ...ctx, indentLv },
})};`,
indentLv,
),
);
} else {
const mediaType = transformMediaTypeObject(mediaTypeObject, {
path: `${path}/${contentType}`,
ctx: { ...ctx, indentLv },
});
output.push(indent(`${key}: ${mediaType};`, indentLv));
}
}
indentLv--;
output.push(indent("};", indentLv));
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode([
ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
/* name */ tsPropertyIndex("content"),
/* questionToken */ undefined,
/* type */ ts.factory.createTypeLiteralNode(
type.length
? type
: // add `"*/*": never` if no media types are defined
[
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex("*/*"),
/* questionToken */ QUESTION_TOKEN,
/* type */ NEVER,
),
],
),
),
]);
}

@@ -1,74 +0,120 @@

import type { GlobalContext, ResponseObject } from "../types.js";
import { escObjKey, escStr, getEntries, getSchemaObjectComment, indent, tsOptionalProperty, tsReadonly } from "../utils.js";
import ts from "typescript";
import {
NEVER,
QUESTION_TOKEN,
STRING,
UNKNOWN,
addJSDocComment,
oapiRef,
tsModifiers,
tsPropertyIndex,
} from "../lib/ts.js";
import { createRef, getEntries } from "../lib/utils.js";
import { ResponseObject, TransformNodeOptions } from "../types.js";
import transformHeaderObject from "./header-object.js";
import transformMediaTypeObject from "./media-type-object.js";
export interface TransformResponseObjectOptions {
path: string;
ctx: GlobalContext;
}
/**
* Transform ResponseObject nodes (4.8.17)
* @see https://spec.openapis.org/oas/v3.1.0#response-object
*/
export default function transformResponseObject(
responseObject: ResponseObject,
options: TransformNodeOptions,
): ts.TypeNode {
const type: ts.TypeElement[] = [];
export default function transformResponseObject(responseObject: ResponseObject, { path, ctx }: TransformResponseObjectOptions): string {
const output: string[] = ["{"];
let { indentLv } = ctx;
// headers
const headersObject: ts.TypeElement[] = [];
if (responseObject.headers) {
indentLv++;
output.push(indent(`headers: {`, indentLv));
indentLv++;
for (const [name, headerObject] of getEntries(responseObject.headers, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(headerObject, indentLv);
if (c) output.push(indent(c, indentLv));
let key = escObjKey(name);
if (ctx.immutableTypes) key = tsReadonly(key);
if ("$ref" in headerObject) {
output.push(indent(`${key}: ${headerObject.$ref};`, indentLv));
} else {
if (!headerObject.required) key = tsOptionalProperty(key);
output.push(
indent(
`${key}: ${transformHeaderObject(headerObject, {
path: `${path}/headers/${name}`,
ctx: { ...ctx, indentLv },
})};`,
indentLv,
),
);
}
for (const [name, headerObject] of getEntries(
responseObject.headers,
options.ctx,
)) {
const optional =
"$ref" in headerObject || headerObject.required
? undefined
: QUESTION_TOKEN;
const subType =
"$ref" in headerObject
? oapiRef(headerObject.$ref)
: transformHeaderObject(headerObject, {
...options,
path: createRef([options.path ?? "", "headers", name]),
});
const property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
/* name */ tsPropertyIndex(name),
/* questionToken */ optional,
/* type */ subType,
);
addJSDocComment(headerObject, property);
headersObject.push(property);
}
indentLv--;
output.push(indent(`};`, indentLv));
indentLv--;
}
// allow additional unknown headers
headersObject.push(
ts.factory.createIndexSignature(
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
/* parameters */ [
ts.factory.createParameterDeclaration(
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
/* name */ ts.factory.createIdentifier("name"),
/* questionToken */ undefined,
/* type */ STRING,
),
],
/* type */ UNKNOWN,
),
);
type.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex("headers"),
/* questionToken */ undefined,
/* type */ ts.factory.createTypeLiteralNode(headersObject),
),
);
// content
const contentObject: ts.TypeElement[] = [];
if (responseObject.content) {
indentLv++;
output.push(indent("content: {", indentLv));
indentLv++;
for (const [contentType, mediaTypeObject] of getEntries(responseObject.content, ctx.alphabetize, ctx.excludeDeprecated)) {
let key = escStr(contentType);
if (ctx.immutableTypes) key = tsReadonly(key);
output.push(
indent(
`${key}: ${transformMediaTypeObject(mediaTypeObject, {
path: `${path}/content/${contentType}`,
ctx: { ...ctx, indentLv: indentLv },
})};`,
indentLv,
),
for (const [contentType, mediaTypeObject] of getEntries(
responseObject.content,
options.ctx,
)) {
const property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({ readonly: options.ctx.immutable }),
/* name */ tsPropertyIndex(contentType),
/* questionToken */ undefined,
/* type */ transformMediaTypeObject(mediaTypeObject, {
...options,
path: createRef([options.path ?? "", "content", contentType]),
}),
);
contentObject.push(property);
}
indentLv--;
output.push(indent("};", indentLv));
indentLv--;
}
if (contentObject.length) {
type.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex("content"),
/* questionToken */ undefined,
/* type */ ts.factory.createTypeLiteralNode(contentObject),
),
);
} else {
indentLv++;
output.push(indent("content: never;", indentLv));
indentLv--;
type.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex("content"),
/* questionToken */ QUESTION_TOKEN,
/* type */ NEVER,
),
);
}
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}

@@ -1,183 +0,391 @@

import type { DiscriminatorObject, GlobalContext, ReferenceObject, SchemaObject } from "../types.js";
import { escObjKey, escStr, getEntries, getSchemaObjectComment, indent, parseRef, tsArrayOf, tsIntersectionOf, tsOmit, tsOneOf, tsOptionalProperty, tsReadonly, tsTupleOf, tsUnionOf, tsWithRequired } from "../utils.js";
import transformSchemaObjectMap from "./schema-object-map.js";
import { parseRef } from "@redocly/openapi-core/lib/ref-utils.js";
import ts from "typescript";
import {
BOOLEAN,
NEVER,
NULL,
NUMBER,
QUESTION_TOKEN,
STRING,
UNKNOWN,
addJSDocComment,
oapiRef,
tsEnum,
tsIntersection,
tsIsPrimitive,
tsLiteral,
tsModifiers,
tsNullable,
tsOmit,
tsPropertyIndex,
tsRecord,
tsUnion,
tsWithRequired,
} from "../lib/ts.js";
import {
createDiscriminatorProperty,
createRef,
getEntries,
} from "../lib/utils.js";
import {
ReferenceObject,
SchemaObject,
TransformNodeOptions,
} from "../types.js";
// There’s just no getting around some really complex type intersections that TS
// has trouble following
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface TransformSchemaObjectOptions {
/** The full ID for this object (mostly used in error messages) */
path: string;
/** Shared context */
ctx: GlobalContext;
}
export default function transformSchemaObject(schemaObject: SchemaObject | ReferenceObject, options: TransformSchemaObjectOptions): string {
const result = defaultSchemaObjectTransform(schemaObject, options);
/**
* Transform SchemaObject nodes (4.8.24)
* @see https://spec.openapis.org/oas/v3.1.0#schema-object
*/
export default function transformSchemaObject(
schemaObject: SchemaObject | ReferenceObject,
options: TransformNodeOptions,
): ts.TypeNode {
const type = transformSchemaObjectWithComposition(schemaObject, options);
if (typeof options.ctx.postTransform === "function") {
const postResult = options.ctx.postTransform(result, options);
if (postResult) return postResult;
const postTransformResult = options.ctx.postTransform(type, options);
if (postTransformResult) {
return postTransformResult;
}
}
return result;
return type;
}
export function defaultSchemaObjectTransform(schemaObject: SchemaObject | ReferenceObject, { path, ctx }: TransformSchemaObjectOptions): string {
let { indentLv } = ctx;
/**
* Transform SchemaObjects
*/
export function transformSchemaObjectWithComposition(
schemaObject: SchemaObject | ReferenceObject,
options: TransformNodeOptions,
): ts.TypeNode {
/**
* Unexpected types & edge cases
*/
// boolean schemas
if (typeof schemaObject === "boolean") {
return schemaObject ? "unknown" : "never";
// missing/falsy type returns `never`
if (!schemaObject) {
return NEVER;
}
// const fallback (primitives) return passed value
if (!schemaObject || typeof schemaObject !== "object") return schemaObject;
// const fallback (array) return tuple
if (Array.isArray(schemaObject)) {
const finalType = tsTupleOf(...schemaObject);
return ctx.immutableTypes ? tsReadonly(finalType) : finalType;
// `true` returns `unknown` (this exists, but is untyped)
if ((schemaObject as unknown) === true) {
return UNKNOWN;
}
// for any other unexpected type, throw error
if (Array.isArray(schemaObject) || typeof schemaObject !== "object") {
throw new Error(
`Expected SchemaObject, received ${
Array.isArray(schemaObject) ? "Array" : typeof schemaObject
}`,
);
}
// $ref
/**
* ReferenceObject
*/
if ("$ref" in schemaObject) {
return schemaObject.$ref;
return oapiRef(schemaObject.$ref);
}
// transform()
if (typeof ctx.transform === "function") {
const result = ctx.transform(schemaObject, { path, ctx });
if (result) return result;
/**
* transform()
*/
if (typeof options.ctx.transform === "function") {
const result = options.ctx.transform(schemaObject, options);
if (result !== undefined && result !== null) {
return result;
}
}
// const (valid for any type)
/**
* const (valid for any type)
*/
if (schemaObject.const !== null && schemaObject.const !== undefined) {
return transformSchemaObject(escStr(schemaObject.const) as any, {
path,
ctx: { ...ctx, immutableTypes: false, indentLv: indentLv + 1 }, // note: guarantee readonly happens once, here
});
return tsLiteral(schemaObject.const);
}
// enum (valid for any type, but for objects, treat as oneOf below)
if (typeof schemaObject === "object" && !!schemaObject.enum && (schemaObject as any).type !== "object") {
let items = schemaObject.enum as string[];
if ("type" in schemaObject) {
if (schemaObject.type === "string" || (Array.isArray(schemaObject.type) && (schemaObject.type as string[]).includes("string"))) {
items = items.map((t) => escStr(t));
}
/**
* enum (non-objects)
* note: enum is valid for any type, but for objects, handle in oneOf below
*/
if (
Array.isArray(schemaObject.enum) &&
(!("type" in schemaObject) || schemaObject.type !== "object") &&
!("properties" in schemaObject) &&
!("additionalProperties" in schemaObject)
) {
// hoist enum to top level if string/number enum and option is enabled
if (
options.ctx.enum &&
schemaObject.enum.every(
(v) => typeof v === "string" || typeof v === "number",
)
) {
let enumName = parseRef(options.path ?? "").pointer.join("/");
// allow #/components/schemas to have simpler names
enumName = enumName.replace("components/schemas", "");
const enumType = tsEnum(
enumName,
schemaObject.enum as (string | number)[],
{ export: true, readonly: options.ctx.immutable },
);
options.ctx.injectFooter.push(enumType);
return ts.factory.createTypeReferenceNode(enumType.name);
}
// if no type, assume "string"
else {
items = items.map((t) => escStr(t || ""));
}
return tsUnionOf(...items, ...(schemaObject.nullable ? ["null"] : []));
return tsUnion(schemaObject.enum.map(tsLiteral));
}
// oneOf (no discriminator)
const oneOf = ((typeof schemaObject === "object" && !schemaObject.discriminator && schemaObject.oneOf) || schemaObject.enum || undefined) as (SchemaObject | ReferenceObject)[] | undefined; // note: for objects, treat enum as oneOf
if (oneOf && !oneOf.some((t) => "$ref" in t && ctx.discriminators[t.$ref])) {
const oneOfNormalized = oneOf.map((item) => transformSchemaObject(item, { path, ctx }));
if (schemaObject.nullable) oneOfNormalized.push("null");
/**
* Object + composition (anyOf/allOf/oneOf) types
*/
// handle oneOf + polymorphic array type
if ("type" in schemaObject && Array.isArray(schemaObject.type)) {
const coreTypes = schemaObject.type.map((t) => transformSchemaObject({ ...schemaObject, oneOf: undefined, type: t }, { path, ctx }));
return tsUnionOf(...oneOfNormalized, ...coreTypes);
/** Collect oneOf/allOf/anyOf with Omit<> for discriminators */
function collectCompositions(
items: (SchemaObject | ReferenceObject)[],
): ts.TypeNode[] {
const output: ts.TypeNode[] = [];
for (const item of items) {
const itemType = transformSchemaObject(item, options);
const discriminator =
("$ref" in item && options.ctx.discriminators[item.$ref]) ||
(item as any).discriminator; // eslint-disable-line @typescript-eslint/no-explicit-any
if (discriminator) {
output.push(tsOmit(itemType, [discriminator.propertyName]));
} else {
output.push(itemType);
}
}
return output;
}
// OneOf<> helper needed if any objects present ("{")
const oneOfTypes = oneOfNormalized.some((t) => typeof t === "string" && t.includes("{")) ? tsOneOf(...oneOfNormalized) : tsUnionOf(...oneOfNormalized);
// compile final type
let finalType: ts.TypeNode | undefined = undefined;
// handle oneOf + object type
if ("type" in schemaObject && schemaObject.type === "object" && (schemaObject.properties || schemaObject.additionalProperties)) {
return tsIntersectionOf(transformSchemaObject({ ...schemaObject, oneOf: undefined, enum: undefined } as any, { path, ctx }), oneOfTypes);
// core + allOf: intersect
const coreObjectType = transformSchemaObjectCore(schemaObject, options);
const allOfType = collectCompositions(schemaObject.allOf ?? []);
if (coreObjectType || allOfType.length) {
let allOf: ts.TypeNode | undefined = allOfType.length
? tsIntersection(allOfType)
: undefined;
// add required props
if (
allOf &&
"required" in schemaObject &&
Array.isArray(schemaObject.required)
) {
allOf = tsWithRequired(
allOf,
schemaObject.required,
options.ctx.injectFooter,
);
}
finalType = tsIntersection([
...(coreObjectType ? [coreObjectType] : []),
...(allOf ? [allOf] : []),
]);
}
// anyOf: union
// (note: this may seem counterintuitive, but as TypeScript’s unions are not true XORs, they mimic behavior closer to anyOf than oneOf)
const anyOfType = collectCompositions(schemaObject.anyOf ?? []);
if (anyOfType.length) {
finalType = tsUnion([...(finalType ? [finalType] : []), ...anyOfType]);
}
// oneOf: union (within intersection with other types, if any)
const oneOfType = collectCompositions(
schemaObject.oneOf ||
("type" in schemaObject &&
schemaObject.type === "object" &&
(schemaObject.enum as (SchemaObject | ReferenceObject)[])) ||
[],
);
if (oneOfType.length) {
// note: oneOf is the only type that may include primitives
if (oneOfType.every(tsIsPrimitive)) {
finalType = tsUnion([...(finalType ? [finalType] : []), ...oneOfType]);
} else {
finalType = tsIntersection([
...(finalType ? [finalType] : []),
tsUnion(oneOfType),
]);
}
}
// default
return oneOfTypes;
// if final type could be generated, return intersection of all members
if (finalType) {
// deprecated nullable
if (schemaObject.nullable) {
return tsNullable([finalType]);
}
return finalType;
}
// otherwise fall back to unknown type (or related variants)
else {
// fallback: unknown
if (!("type" in schemaObject)) {
return UNKNOWN;
}
if ("type" in schemaObject) {
// "type": "null"
if (schemaObject.type === "null") return "null";
// if no type could be generated, fall back to “empty object” type
return tsRecord(STRING, options.ctx.emptyObjectsUnknown ? UNKNOWN : NEVER);
}
}
// "type": "string", "type": "boolean"
if (schemaObject.type === "string" || schemaObject.type === "boolean") {
return schemaObject.nullable ? tsUnionOf(schemaObject.type, "null") : schemaObject.type;
/**
* Handle SchemaObject minus composition (anyOf/allOf/oneOf)
*/
function transformSchemaObjectCore(
schemaObject: SchemaObject,
options: TransformNodeOptions,
): ts.TypeNode | undefined {
if ("type" in schemaObject && schemaObject.type) {
// primitives
// type: null
if (schemaObject.type === "null") {
return NULL;
}
// "type": "number", "type": "integer"
// type: string
if (schemaObject.type === "string") {
return STRING;
}
// type: number / type: integer
if (schemaObject.type === "number" || schemaObject.type === "integer") {
return schemaObject.nullable ? tsUnionOf("number", "null") : "number";
return NUMBER;
}
// type: boolean
if (schemaObject.type === "boolean") {
return BOOLEAN;
}
// "type": "array"
// type: array (with support for tuples)
if (schemaObject.type === "array") {
indentLv++;
let itemType = "unknown";
let isTupleType = false;
// default to `unknown[]`
let itemType: ts.TypeNode = UNKNOWN;
// tuple type
if (schemaObject.prefixItems || Array.isArray(schemaObject.items)) {
// tuple type support
isTupleType = true;
const result: string[] = [];
for (const item of schemaObject.prefixItems ?? (schemaObject.items as (SchemaObject | ReferenceObject)[])) {
result.push(transformSchemaObject(item, { path, ctx: { ...ctx, indentLv } }));
const prefixItems =
schemaObject.prefixItems ??
(schemaObject.items as (SchemaObject | ReferenceObject)[]);
itemType = ts.factory.createTupleTypeNode(
prefixItems.map((item) => transformSchemaObject(item, options)),
);
}
// standard array type
else if (schemaObject.items) {
itemType = transformSchemaObject(schemaObject.items, options);
if (options.ctx.immutable) {
itemType = ts.factory.createTypeOperatorNode(
ts.SyntaxKind.ReadonlyKeyword,
itemType,
);
}
itemType = `[${result.join(", ")}]`;
} else if (schemaObject.items) {
itemType = transformSchemaObject(schemaObject.items, { path, ctx: { ...ctx, indentLv } });
}
const min: number = typeof schemaObject.minItems === "number" && schemaObject.minItems >= 0 ? schemaObject.minItems : 0;
const max: number | undefined = typeof schemaObject.maxItems === "number" && schemaObject.maxItems >= 0 && min <= schemaObject.maxItems ? schemaObject.maxItems : undefined;
const estimateCodeSize = typeof max !== "number" ? min : (max * (max + 1) - min * (min - 1)) / 2;
// export types
if (ctx.supportArrayLength && (min !== 0 || max !== undefined) && estimateCodeSize < 30) {
if (typeof schemaObject.maxItems !== "number") {
itemType = tsTupleOf(...Array.from({ length: min }).map(() => itemType), `...${tsArrayOf(itemType)}`);
return ctx.immutableTypes || schemaObject.readOnly ? tsReadonly(itemType) : itemType;
} else {
return tsUnionOf(
...Array.from({ length: (max ?? 0) - min + 1 })
.map((_, i) => i + min)
.map((n) => {
const t = tsTupleOf(...Array.from({ length: n }).map(() => itemType));
return ctx.immutableTypes || schemaObject.readOnly ? tsReadonly(t) : t;
}),
const min: number =
typeof schemaObject.minItems === "number" && schemaObject.minItems >= 0
? schemaObject.minItems
: 0;
const max: number | undefined =
typeof schemaObject.maxItems === "number" &&
schemaObject.maxItems >= 0 &&
min <= schemaObject.maxItems
? schemaObject.maxItems
: undefined;
const estimateCodeSize =
typeof max !== "number" ? min : (max * (max + 1) - min * (min - 1)) / 2;
if (
options.ctx.arrayLength &&
(min !== 0 || max !== undefined) &&
estimateCodeSize < 30 // "30" is an arbitrary number but roughly around when TS starts to struggle with tuple inference in practice
) {
// if maxItems is set, then return a union of all permutations of possible tuple types
if ((schemaObject.maxItems as number) > 0) {
const members: ts.TypeNode[] = [];
// populate 1 short of min …
for (let i = 0; i <= (max ?? 0) - min; i++) {
const elements: ts.TypeNode[] = [];
for (let j = min; j < i + min; j++) {
elements.push(itemType);
}
members.push(ts.factory.createTupleTypeNode(elements));
}
return tsUnion(members);
}
// if maxItems not set, then return a simple tuple type the length of `min`
else {
const elements: ts.TypeNode[] = [];
for (let i = 0; i < min; i++) {
elements.push(itemType);
}
elements.push(
ts.factory.createRestTypeNode(
ts.factory.createArrayTypeNode(itemType),
),
);
return ts.factory.createTupleTypeNode(elements);
}
}
if (!isTupleType) {
// Do not use tsArrayOf when it is a tuple type
itemType = tsArrayOf(itemType);
}
itemType = ctx.immutableTypes || schemaObject.readOnly ? tsReadonly(itemType) : itemType;
return schemaObject.nullable ? tsUnionOf(itemType, "null") : itemType;
return ts.isTupleTypeNode(itemType)
? itemType
: ts.factory.createArrayTypeNode(itemType); // wrap itemType in array type, but only if not a tuple already
}
// polymorphic, or 3.1 nullable
if (Array.isArray(schemaObject.type)) {
return tsUnionOf(...schemaObject.type.map((t) => transformSchemaObject({ ...schemaObject, type: t }, { path, ctx })));
if (Array.isArray(schemaObject.type) && !Array.isArray(schemaObject)) {
// skip any primitive types that appear in oneOf as well
let uniqueTypes: ts.TypeNode[] = [];
if (Array.isArray(schemaObject.oneOf)) {
for (const t of schemaObject.type) {
if (
(t === "boolean" ||
t === "string" ||
t === "number" ||
t === "integer" ||
t === "null") &&
schemaObject.oneOf.find(
(o) => typeof o === "object" && "type" in o && o.type === t,
)
) {
continue;
}
uniqueTypes.push(
t === "null" || t === null
? NULL
: transformSchemaObject(
{ ...schemaObject, type: t, oneOf: undefined }, // don’t stack oneOf transforms
options,
),
);
}
} else {
uniqueTypes = schemaObject.type.map((t) =>
t === "null" || t === null
? NULL
: transformSchemaObject({ ...schemaObject, type: t }, options),
);
}
return tsUnion(uniqueTypes);
}
}
// core type: properties + additionalProperties
const coreType: string[] = [];
// type: object
const coreObjectType: ts.TypeElement[] = [];
// discriminators: explicit mapping on schema object
for (const k of ["oneOf", "allOf", "anyOf"] as ("oneOf" | "allOf" | "anyOf")[]) {
if (!(schemaObject as any)[k]) continue;
const discriminatorRef: ReferenceObject | undefined = (schemaObject as any)[k].find(
(t: SchemaObject | ReferenceObject) =>
"$ref" in t &&
(ctx.discriminators[t.$ref] || // explicit allOf from this node
Object.values(ctx.discriminators).find((d) => d.oneOf?.includes(path))), // implicit oneOf from parent
);
if (discriminatorRef && ctx.discriminators[discriminatorRef.$ref]) {
coreType.unshift(indent(getDiscriminatorPropertyName(path, ctx.discriminators[discriminatorRef.$ref]), indentLv + 1));
break;
// discriminatorss: explicit mapping on schema object
for (const k of ["oneOf", "allOf", "anyOf"] as const) {
if (!schemaObject[k]) {
continue;
}
}
// discriminators: implicit mapping from parent
for (const d of Object.values(ctx.discriminators)) {
if (d.oneOf?.includes(path)) {
coreType.unshift(indent(getDiscriminatorPropertyName(path, d), indentLv + 1));
// for all magic inheritance, we will have already gathered it into
// ctx.discriminators. But stop objects from referencing their own
// discriminator meant for children (!schemaObject.discriminator)
const discriminator =
!schemaObject.discriminator && options.ctx.discriminators[options.path!];
if (discriminator) {
coreObjectType.unshift(
createDiscriminatorProperty(discriminator, {
path: options.path!,
readonly: options.ctx.immutable,
}),
);
break;

@@ -187,106 +395,129 @@ }

// "type": "object" (explicit)
// "anyOf" / "allOf" (object type implied)
if (
("properties" in schemaObject && schemaObject.properties && Object.keys(schemaObject.properties).length) ||
("additionalProperties" in schemaObject && schemaObject.additionalProperties) ||
("properties" in schemaObject &&
schemaObject.properties &&
Object.keys(schemaObject.properties).length) ||
("additionalProperties" in schemaObject &&
schemaObject.additionalProperties) ||
("$defs" in schemaObject && schemaObject.$defs)
) {
indentLv++;
for (const [k, v] of getEntries(schemaObject.properties ?? {}, ctx.alphabetize, ctx.excludeDeprecated)) {
const c = getSchemaObjectComment(v, indentLv);
if (c) coreType.push(indent(c, indentLv));
let key = escObjKey(k);
let isOptional = !Array.isArray(schemaObject.required) || !schemaObject.required.includes(k);
if (isOptional && ctx.defaultNonNullable && "default" in v) isOptional = false; // if --default-non-nullable specified and this has a default, it’s no longer optional
if (isOptional) key = tsOptionalProperty(key);
if (ctx.immutableTypes || schemaObject.readOnly) key = tsReadonly(key);
coreType.push(indent(`${key}: ${transformSchemaObject(v, { path, ctx: { ...ctx, indentLv } })};`, indentLv));
}
if (schemaObject.additionalProperties || ctx.additionalProperties) {
let addlType = "unknown";
if (typeof schemaObject.additionalProperties === "object") {
if (!Object.keys(schemaObject.additionalProperties).length) {
addlType = "unknown";
} else {
addlType = transformSchemaObject(schemaObject.additionalProperties as SchemaObject, {
path,
ctx: { ...ctx, indentLv },
});
// properties
if (Object.keys(schemaObject.properties ?? {}).length) {
for (const [k, v] of getEntries(
schemaObject.properties ?? {},
options.ctx,
)) {
if (typeof v !== "object" || Array.isArray(v)) {
throw new Error(
`${
options.path
}: invalid property ${k}. Expected Schema Object, got ${
Array.isArray(v) ? "Array" : typeof v
}`,
);
}
}
// We need to add undefined when there are other optional properties in the schema.properties object
// that is the case when either schemaObject.required is empty and there are defined properties, or
// schemaObject.required is only contains a part of the schemaObject.properties
const numProperties = schemaObject.properties ? Object.keys(schemaObject.properties).length : 0;
if (schemaObject.properties && ((!schemaObject.required && numProperties) || (schemaObject.required && numProperties !== schemaObject.required.length))) {
coreType.push(indent(`[key: string]: ${tsUnionOf(addlType ? addlType : "unknown", "undefined")};`, indentLv));
} else {
coreType.push(indent(`[key: string]: ${addlType ? addlType : "unknown"};`, indentLv));
// handle excludeDeprecated option
if (options.ctx.excludeDeprecated) {
const resolved =
"$ref" in v ? options.ctx.resolve<SchemaObject>(v.$ref) : v;
if (resolved?.deprecated) {
continue;
}
}
const optional =
schemaObject.required?.includes(k) ||
("default" in v && options.ctx.defaultNonNullable)
? undefined
: QUESTION_TOKEN;
const type =
"$ref" in v
? oapiRef(v.$ref)
: transformSchemaObject(v, {
...options,
path: createRef([options.path ?? "", k]),
});
const property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({
readonly:
options.ctx.immutable || ("readOnly" in v && !!v.readOnly),
}),
/* name */ tsPropertyIndex(k),
/* questionToken */ optional,
/* type */ type,
);
addJSDocComment(v, property);
coreObjectType.push(property);
}
}
if (schemaObject.$defs && typeof schemaObject.$defs === "object" && Object.keys(schemaObject.$defs).length) {
coreType.push(indent(`$defs: ${transformSchemaObjectMap(schemaObject.$defs, { path: `${path}$defs/`, ctx: { ...ctx, indentLv } })};`, indentLv));
}
indentLv--;
}
// close coreType
let finalType = coreType.length ? `{\n${coreType.join("\n")}\n${indent("}", indentLv)}` : "";
/** collect oneOf/allOf/anyOf with Omit<> for discriminators */
function collectCompositions(items: (SchemaObject | ReferenceObject)[]): string[] {
const output: string[] = [];
for (const item of items) {
const itemType = transformSchemaObject(item, { path, ctx: { ...ctx, indentLv } });
if ("$ref" in item && ctx.discriminators[item.$ref]) {
output.push(tsOmit(itemType, [ctx.discriminators[item.$ref].propertyName]));
continue;
// $defs
if (
schemaObject.$defs &&
typeof schemaObject.$defs === "object" &&
Object.keys(schemaObject.$defs).length
) {
const defKeys: ts.TypeElement[] = [];
for (const [k, v] of Object.entries(schemaObject.$defs)) {
const property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({
readonly:
options.ctx.immutable || ("readonly" in v && !!v.readOnly),
}),
/* name */ tsPropertyIndex(k),
/* questionToken */ undefined,
/* type */ transformSchemaObject(v, {
...options,
path: createRef([options.path ?? "", "$defs", k]),
}),
);
addJSDocComment(v, property);
defKeys.push(property);
}
output.push(itemType);
coreObjectType.push(
ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({
readonly: options.ctx.immutable,
}),
/* name */ tsPropertyIndex("$defs"),
/* questionToken */ undefined,
/* type */ ts.factory.createTypeLiteralNode(defKeys),
),
);
}
return output;
}
// oneOf (discriminator)
if (Array.isArray(schemaObject.oneOf) && schemaObject.oneOf.length) {
const oneOfType = tsUnionOf(...collectCompositions(schemaObject.oneOf));
finalType = finalType ? tsIntersectionOf(finalType, oneOfType) : oneOfType;
} else {
// allOf
if (Array.isArray((schemaObject as any).allOf) && schemaObject.allOf!.length) {
finalType = tsIntersectionOf(...(finalType ? [finalType] : []), ...collectCompositions(schemaObject.allOf!));
if ("required" in schemaObject && Array.isArray(schemaObject.required)) {
finalType = tsWithRequired(finalType, schemaObject.required);
}
// additionalProperties
if (schemaObject.additionalProperties || options.ctx.additionalProperties) {
const hasExplicitAdditionalProperties =
typeof schemaObject.additionalProperties === "object" &&
Object.keys(schemaObject.additionalProperties).length;
const addlType = hasExplicitAdditionalProperties
? transformSchemaObject(
schemaObject.additionalProperties as SchemaObject,
options,
)
: UNKNOWN;
coreObjectType.push(
ts.factory.createIndexSignature(
/* modifiers */ tsModifiers({
readonly: options.ctx.immutable,
}),
/* parameters */ [
ts.factory.createParameterDeclaration(
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
/* name */ ts.factory.createIdentifier("key"),
/* questionToken */ undefined,
/* type */ STRING,
),
],
/* type */ addlType,
),
);
}
// anyOf
if (Array.isArray(schemaObject.anyOf) && schemaObject.anyOf.length) {
const anyOfTypes = tsUnionOf(...collectCompositions(schemaObject.anyOf));
finalType = finalType ? tsIntersectionOf(finalType, anyOfTypes) : anyOfTypes;
}
}
// nullable (3.0)
if (schemaObject.nullable) finalType = tsUnionOf(finalType || "Record<string, unknown>", "null");
if (finalType) return finalType;
// any type
if (!("type" in schemaObject)) return "unknown";
// if no type could be generated, fall back to “empty object” type
return ctx.emptyObjectsUnknown ? "Record<string, unknown>" : "Record<string, never>";
return coreObjectType.length
? ts.factory.createTypeLiteralNode(coreObjectType)
: undefined;
}
export function getDiscriminatorPropertyName(path: string, discriminator: DiscriminatorObject): string {
// get the inferred propertyName value from the last section of the path (as the spec suggests to do)
let value = parseRef(path).path.pop()!;
// if mapping, and there’s a match, use this rather than the inferred name
if (discriminator.mapping) {
// Mapping value can either be a fully-qualified ref (#/components/schemas/XYZ) or a schema name (XYZ)
const matchedValue = Object.entries(discriminator.mapping).find(([, v]) => (!v.startsWith("#") && v === value) || (v.startsWith("#") && parseRef(v).path.pop() === value));
if (matchedValue) value = matchedValue[0]; // why was this designed backwards!?
}
return `${escObjKey(discriminator.propertyName)}: ${escStr(value)};`;
}

@@ -0,23 +1,30 @@

import ts from "typescript";
import { tsModifiers, tsPropertyIndex } from "../lib/ts.js";
import { createRef, getEntries } from "../lib/utils.js";
import type { GlobalContext, WebhooksObject } from "../types.js";
import { escStr, getEntries, indent } from "../utils.js";
import transformPathItemObject from "./path-item-object.js";
export default function transformWebhooksObject(webhooksObject: WebhooksObject, ctx: GlobalContext): string {
let { indentLv } = ctx;
const output: string[] = ["{"];
indentLv++;
for (const [name, pathItemObject] of getEntries(webhooksObject, ctx.alphabetize, ctx.excludeDeprecated)) {
output.push(
indent(
`${escStr(name)}: ${transformPathItemObject(pathItemObject, {
path: `#/webhooks/${name}`,
ctx: { ...ctx, indentLv },
})};`,
indentLv,
export default function transformWebhooksObject(
webhooksObject: WebhooksObject,
options: GlobalContext,
): ts.TypeNode {
const type: ts.TypeElement[] = [];
for (const [name, pathItemObject] of getEntries(webhooksObject, options)) {
type.push(
ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({
readonly: options.immutable,
}),
/* name */ tsPropertyIndex(name),
/* questionToken */ undefined,
/* type */ transformPathItemObject(pathItemObject, {
path: createRef(["webhooks", name]),
ctx: options,
}),
),
);
}
indentLv--;
output.push(indent("}", indentLv));
return output.join("\n");
return ts.factory.createTypeLiteralNode(type);
}

@@ -1,6 +0,6 @@

import type { PathLike } from "node:fs";
import type { RequestInfo, RequestInit, Response } from "undici";
import type { TransformSchemaObjectOptions } from "./transform/schema-object.js";
import type { Config as RedoclyConfig } from "@redocly/openapi-core";
import { PathLike } from "node:fs";
import type ts from "typescript";
// Many types allow for true “any”
// Many types allow for true “any” for inheritance to work
/* eslint-disable @typescript-eslint/no-explicit-any */

@@ -12,2 +12,8 @@

// Note: these OpenAPI types are meant only for internal use, not external
// consumption. Some formatting may be better in other libraries meant for
// consumption. Some typing may be “loose” or “incorrect” in order to guarantee
// that all logical paths are handled. In other words, these are built more
// for ways schemas _can_ be written, not necessarily how they _should_ be.
/**

@@ -450,3 +456,13 @@ * [4.8] Schema

| ObjectSubtype
| { type: ("string" | "number" | "integer" | "array" | "boolean" | "null" | "object")[] }
| {
type: (
| "string"
| "number"
| "integer"
| "array"
| "boolean"
| "null"
| "object"
)[];
}
// eslint-disable-next-line @typescript-eslint/ban-types

@@ -457,3 +473,3 @@ | {}

export interface StringSubtype {
type: "string";
type: "string" | ["string", "null"];
enum?: (string | ReferenceObject)[];

@@ -463,3 +479,3 @@ }

export interface NumberSubtype {
type: "number";
type: "number" | ["number", "null"];
minimum?: number;

@@ -471,3 +487,3 @@ maximum?: number;

export interface IntegerSubtype {
type: "integer";
type: "integer" | ["integer", "null"];
minimum?: number;

@@ -479,3 +495,3 @@ maximum?: number;

export interface ArraySubtype {
type: "array";
type: "array" | ["array", "null"];
prefixItems?: (SchemaObject | ReferenceObject)[];

@@ -489,3 +505,3 @@ items?: SchemaObject | ReferenceObject | (SchemaObject | ReferenceObject)[];

export interface BooleanSubtype {
type: "boolean";
type: "boolean" | ["boolean", "null"];
enum?: (boolean | ReferenceObject)[];

@@ -501,3 +517,7 @@ }

properties?: { [name: string]: SchemaObject | ReferenceObject };
additionalProperties?: boolean | Record<string, never> | SchemaObject | ReferenceObject;
additionalProperties?:
| boolean
| Record<string, never>
| SchemaObject
| ReferenceObject;
required?: string[];

@@ -617,14 +637,15 @@ allOf?: (SchemaObject | ReferenceObject)[];

*/
export type SecurityRequirementObject = Record<keyof ComponentsObject["securitySchemes"], string[]>;
export type SecurityRequirementObject = Record<
keyof ComponentsObject["securitySchemes"],
string[]
>;
export interface OpenAPITSOptions {
/** Allow schema objects to have additional properties if not expressly forbidden? (default: false) */
/** Treat all objects as if they have `additionalProperties: true` by default (default: false) */
additionalProperties?: boolean;
/** Alphabetize all keys? (default: false) */
alphabetize?: boolean;
/** Specify auth if using openapi-typescript to fetch URL */
auth?: string;
/** Allow schema objects with no specified properties to have additional properties if not expressly forbidden? (default: false) */
emptyObjectsUnknown?: boolean;
/** Specify current working directory (cwd) to resolve remote schemas on disk (not needed for remote URL schemas) */
/** Provide current working directory (cwd) which helps resolve relative remote schemas */
cwd?: PathLike;

@@ -634,7 +655,13 @@ /** Should schema objects with a default value not be considered optional? */

/** Manually transform certain Schema Objects with a custom TypeScript type */
transform?: (schemaObject: SchemaObject, options: TransformSchemaObjectOptions) => string | undefined;
transform?: (
schemaObject: SchemaObject,
options: TransformNodeOptions,
) => ts.TypeNode | undefined;
/** Modify TypeScript types built from Schema Objects */
postTransform?: (type: string, options: TransformSchemaObjectOptions) => string | undefined;
postTransform?: (
type: ts.TypeNode,
options: TransformNodeOptions,
) => ts.TypeNode | undefined;
/** Add readonly properties and readonly arrays? (default: false) */
immutableTypes?: boolean;
immutable?: boolean;
/** (optional) Should logging be suppressed? (necessary for STDOUT) */

@@ -644,80 +671,40 @@ silent?: boolean;

version?: number;
/**
* (optional) List of HTTP headers that will be sent with the fetch request to a remote schema. This is
* in addition to the authorization header. In some cases, servers require headers such as Accept: application/json
* or Accept: text/yaml to be sent in order to figure out how to properly fetch the OpenAPI/Swagger document as code.
* These headers will only be sent in the case that the schema URL protocol is of type http or https.
*/
httpHeaders?: Record<string, any>;
/**
* HTTP verb used to fetch the schema from a remote server. This is only applied
* when the schema is a string and has the http or https protocol present. By default,
* the request will use the HTTP GET method to fetch the schema from the server.
*
* @default {string} GET
*/
httpMethod?: string;
/** (optional) Export type instead of interface */
exportType?: boolean;
/** Export true TypeScript enums instead of unions */
enum?: boolean;
/** (optional) Generate tuples using array minItems / maxItems */
supportArrayLength?: boolean;
arrayLength?: boolean;
/** (optional) Substitute path parameter names with their respective types */
pathParamsAsTypes?: boolean;
/** Exclude deprecated fields from types? (default: false) */
excludeDeprecated?: boolean;
/**
* (optional) Provide your own comment header that prefixes the generated file.
* Note this isn’t validated, so any string entered will be accepted as-is.
* Configure Redocly for validation, schema fetching, and bundling
* @see https://redocly.com/docs/cli/configuration/
*/
commentHeader?: string;
/** (optional) inject code before schema ? */
inject?: string;
/**
* (optional) A fetch implementation. Will default to the global fetch
* function if available; else, it will use unidici's fetch function.
*/
fetch?: Fetch;
/** Exclude deprecated fields from types? (default: false) */
excludeDeprecated?: boolean;
redocly?: RedoclyConfig;
}
/** Subschema discriminator (note: only valid $ref types accepted here) */
export type Subschema =
| { hint: "LinkObject"; schema: LinkObject }
| { hint: "HeaderObject"; schema: HeaderObject }
| { hint: "MediaTypeObject"; schema: MediaTypeObject }
| { hint: "OpenAPI3"; schema: OpenAPI3 }
| { hint: "OperationObject"; schema: OperationObject }
| { hint: "ParameterObject"; schema: ParameterObject }
| {
hint: "ParameterObject[]";
schema: (ParameterObject | ReferenceObject)[] | Record<string, ParameterObject | ReferenceObject>;
}
| { hint: "RequestBodyObject"; schema: RequestBodyObject }
| { hint: "ResponseObject"; schema: ResponseObject }
| { hint: "SchemaMap"; schema: Record<string, SchemaObject | ReferenceObject | PathItemObject> } // subschemas are less structured
| { hint: "SchemaObject"; schema: SchemaObject };
/** Context passed to all submodules */
export interface GlobalContext {
// user options
additionalProperties: boolean;
alphabetize: boolean;
cwd?: PathLike;
defaultNonNullable: boolean;
discriminators: Record<string, DiscriminatorObject>;
emptyObjectsUnknown: boolean;
defaultNonNullable: boolean;
discriminators: { [$ref: string]: DiscriminatorObject };
transform: OpenAPITSOptions["transform"];
enum: boolean;
excludeDeprecated: boolean;
exportType: boolean;
immutable: boolean;
injectFooter: ts.Node[];
pathParamsAsTypes: boolean;
postTransform: OpenAPITSOptions["postTransform"];
immutableTypes: boolean;
indentLv: number;
operations: Record<
string,
{
comment?: string;
operationType: string;
}
>;
parameters: Record<string, ParameterObject>;
pathParamsAsTypes: boolean;
redoc: RedoclyConfig;
silent: boolean;
supportArrayLength: boolean;
excludeDeprecated: boolean;
arrayLength: boolean;
transform: OpenAPITSOptions["transform"];
/** retrieve a node by $ref */
resolve<T>(ref: string): T | undefined;
}

@@ -727,7 +714,6 @@

// Fetch is available in the global scope starting with Node v18.
// However, @types/node does not have it yet available.
// GitHub issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/60924
// Because node's underlying implementation relies on unidici, it is safe to
// rely on unidici's type until @types/node ships it.
export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise<Response>;
/** generic options for most internal transform* functions */
export interface TransformNodeOptions {
path?: string;
ctx: GlobalContext;
}
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"sourceRoot": ".",

@@ -5,0 +6,0 @@ "outDir": "dist",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet