typed-openapi 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "typed-openapi",
3
+ "type": "module",
4
+ "version": "0.1.1",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
7
+ "bin": {
8
+ "typed-openapi": "bin.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/astahmer/typed-openapi.git"
13
+ },
14
+ "dependencies": {
15
+ "@apidevtools/swagger-parser": "^10.1.0",
16
+ "@changesets/cli": "^2.26.2",
17
+ "@dprint/formatter": "^0.2.0",
18
+ "@dprint/typescript": "^0.85.1",
19
+ "@sinclair/typebox-codegen": "^0.8.5",
20
+ "arktype": "1.0.18-alpha",
21
+ "cac": "^6.7.14",
22
+ "dprint": "^0.40.2",
23
+ "openapi3-ts": "^4.1.2",
24
+ "pastable": "^2.2.1",
25
+ "pathe": "^1.1.1",
26
+ "ts-pattern": "^5.0.4"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.4.5",
30
+ "tsup": "^7.1.0",
31
+ "typescript": "^5.1.6",
32
+ "vite-node": "^0.33.0",
33
+ "vitest": "^0.33.0"
34
+ },
35
+ "files": [
36
+ "src",
37
+ "dist",
38
+ "cli",
39
+ "README.md"
40
+ ],
41
+ "sideEffects": false,
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "start": "node ./dist/cli.cjs",
47
+ "dev": "tsup --watch",
48
+ "build": "tsup",
49
+ "test": "vitest",
50
+ "typecheck": "tsc --noEmit"
51
+ }
52
+ }
package/src/asserts.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { SchemaObject } from "openapi3-ts/oas31";
2
+
3
+ export type SingleType = Exclude<SchemaObject["type"], any[] | undefined>;
4
+ export const isPrimitiveType = (type: unknown): type is PrimitiveType => primitiveTypeList.includes(type as any);
5
+
6
+ const primitiveTypeList = ["string", "number", "integer", "boolean", "null"] as const;
7
+ export type PrimitiveType = (typeof primitiveTypeList)[number];
@@ -0,0 +1,52 @@
1
+ import type { ReferenceObject, SchemaObject } from "openapi3-ts/oas31";
2
+ import { Box } from "./box";
3
+ import { AnyBoxDef, BoxFactory, OpenapiSchemaConvertContext, StringOrBox } from "./types";
4
+
5
+ export const unwrap = (param: StringOrBox) => (typeof param === "string" ? param : param.value);
6
+ export const createFactory = <T extends OpenapiSchemaConvertContext["factory"]>(f: T) => f;
7
+
8
+ /**
9
+ * Create a box-factory using your schema provider and automatically add the input schema to each box.
10
+ */
11
+ export const createBoxFactory = (schema: SchemaObject | ReferenceObject, ctx: OpenapiSchemaConvertContext) => {
12
+ const f = typeof ctx.factory === "function" ? ctx.factory(schema, ctx) : ctx.factory;
13
+ const callback = <T extends AnyBoxDef>(box: Box<T>) => {
14
+ if (f.callback) {
15
+ box = f.callback(box) as Box<T>;
16
+ }
17
+
18
+ if (ctx?.onBox) {
19
+ box = ctx.onBox?.(box) as Box<T>;
20
+ }
21
+
22
+ return box;
23
+ };
24
+
25
+ const box: BoxFactory = {
26
+ union: (types) => callback(new Box({ ctx, schema, type: "union", params: { types }, value: f.union(types) })),
27
+ intersection: (types) =>
28
+ callback(new Box({ ctx, schema, type: "intersection", params: { types }, value: f.intersection(types) })),
29
+ array: (type) => callback(new Box({ ctx, schema, type: "array", params: { type }, value: f.array(type) })),
30
+ optional: (type) => callback(new Box({ ctx, schema, type: "optional", params: { type }, value: f.optional(type) })),
31
+ reference: (name, generics) =>
32
+ callback(
33
+ new Box({
34
+ ctx,
35
+ schema,
36
+ type: "ref",
37
+ params: generics ? { name, generics } : { name },
38
+ value: f.reference(name, generics),
39
+ }),
40
+ ),
41
+ literal: (value) => callback(new Box({ ctx, schema, type: "literal", params: {}, value: f.literal(value) })),
42
+ string: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "string" }, value: f.string() })),
43
+ number: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "number" }, value: f.number() })),
44
+ boolean: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "boolean" }, value: f.boolean() })),
45
+ unknown: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "unknown" }, value: f.unknown() })),
46
+ any: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "any" }, value: f.any() })),
47
+ never: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "never" }, value: f.never() })),
48
+ object: (props) => callback(new Box({ ctx, schema, type: "object", params: { props }, value: f.object(props) })),
49
+ };
50
+
51
+ return box;
52
+ };
package/src/box.ts ADDED
@@ -0,0 +1,84 @@
1
+ import { SchemaObject } from "openapi3-ts/oas31";
2
+ import { openApiSchemaToTs } from "./openapi-schema-to-ts";
3
+ import {
4
+ AnyBoxDef,
5
+ BoxArray,
6
+ BoxIntersection,
7
+ BoxKeyword,
8
+ BoxLiteral,
9
+ BoxObject,
10
+ BoxOptional,
11
+ BoxRef,
12
+ BoxUnion,
13
+ OpenapiSchemaConvertContext,
14
+ } from "./types";
15
+
16
+ // TODO rename SchemaBox
17
+ export class Box<T extends AnyBoxDef = AnyBoxDef> {
18
+ type: T["type"];
19
+ value: T["value"];
20
+ params: T["params"];
21
+ schema: T["schema"];
22
+ ctx: T["ctx"];
23
+
24
+ constructor(public definition: T) {
25
+ this.definition = definition;
26
+ this.type = definition.type;
27
+ this.value = definition.value;
28
+ this.params = definition.params;
29
+ this.schema = definition.schema;
30
+ this.ctx = definition.ctx;
31
+ }
32
+
33
+ toJSON() {
34
+ return { type: this.type, value: this.value };
35
+ }
36
+
37
+ toString() {
38
+ return JSON.stringify(this.toJSON(), null, 2);
39
+ }
40
+
41
+ recompute(callback: OpenapiSchemaConvertContext["onBox"]) {
42
+ return openApiSchemaToTs({ schema: this.schema as SchemaObject, ctx: { ...this.ctx, onBox: callback! } });
43
+ }
44
+
45
+ static fromJSON(json: string) {
46
+ return new Box(JSON.parse(json));
47
+ }
48
+
49
+ static isBox(box: unknown): box is Box<AnyBoxDef> {
50
+ return box instanceof Box;
51
+ }
52
+
53
+ static isUnion(box: Box<AnyBoxDef>): box is Box<BoxUnion> {
54
+ return box.type === "union";
55
+ }
56
+
57
+ static isIntersection(box: Box<AnyBoxDef>): box is Box<BoxIntersection> {
58
+ return box.type === "intersection";
59
+ }
60
+
61
+ static isArray(box: Box<AnyBoxDef>): box is Box<BoxArray> {
62
+ return box.type === "array";
63
+ }
64
+
65
+ static isOptional(box: Box<AnyBoxDef>): box is Box<BoxOptional> {
66
+ return box.type === "optional";
67
+ }
68
+
69
+ static isReference(box: Box<AnyBoxDef>): box is Box<BoxRef> {
70
+ return box.type === "ref";
71
+ }
72
+
73
+ static isKeyword(box: Box<AnyBoxDef>): box is Box<BoxKeyword> {
74
+ return box.type === "keyword";
75
+ }
76
+
77
+ static isObject(box: Box<AnyBoxDef>): box is Box<BoxObject> {
78
+ return box.type === "object";
79
+ }
80
+
81
+ static isLiteral(box: Box<AnyBoxDef>): box is Box<BoxLiteral> {
82
+ return box.type === "literal";
83
+ }
84
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,47 @@
1
+ import SwaggerParser from "@apidevtools/swagger-parser";
2
+ import { cac } from "cac";
3
+ import type { OpenAPIObject } from "openapi3-ts/oas31";
4
+ import { join } from "pathe";
5
+ import { type } from "arktype";
6
+
7
+ import { writeFile } from "fs/promises";
8
+ // @ts-expect-error
9
+ import { name, version } from "../package.json";
10
+ import { allowedRuntimes, generateFile } from "./generator";
11
+ import { mapOpenApiEndpoints } from "./map-openapi-endpoints";
12
+
13
+ const cwd = process.cwd();
14
+ const cli = cac(name);
15
+ const now = new Date();
16
+
17
+ const optionsSchema = type({ "output?": "string", runtime: allowedRuntimes });
18
+
19
+ cli
20
+ .command("<input>", "Generate")
21
+ .option("-o, --output <path>", "Output path for the api client ts file (defaults to `<input>.<runtime>.ts`)")
22
+ .option(
23
+ "-r, --runtime <name>",
24
+ `Runtime to use for validation; defaults to \`none\`; available: ${allowedRuntimes.definition}`,
25
+ { default: "none" },
26
+ )
27
+ .action(async (input, _options) => {
28
+ const options = optionsSchema.assert(_options);
29
+ const openApiDoc = (await SwaggerParser.bundle(input)) as OpenAPIObject;
30
+
31
+ const ctx = mapOpenApiEndpoints(openApiDoc);
32
+ console.log(`Found ${ctx.endpointList.length} endpoints`);
33
+
34
+ const content = generateFile({ ...ctx, runtime: options.runtime });
35
+ const output = join(
36
+ cwd,
37
+ options.output ?? input + `.${options.runtime === "none" ? "client" : options.runtime}.ts`,
38
+ );
39
+
40
+ console.log("Generating...", output);
41
+ await writeFile(output, content);
42
+ console.log(`Done in ${new Date().getTime() - now.getTime()}ms !`);
43
+ });
44
+
45
+ cli.help();
46
+ cli.version(version);
47
+ cli.parse();
package/src/format.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { createFromBuffer, GlobalConfiguration } from "@dprint/formatter";
2
+ import * as fs from "fs";
3
+ import { join } from "path";
4
+
5
+ const tsPluginPath = join(__dirname, "..", "node_modules", "@dprint", "typescript", "plugin.wasm");
6
+ const buffer = fs.readFileSync(tsPluginPath);
7
+ const formatter = createFromBuffer(buffer);
8
+
9
+ formatter.setConfig({ lineWidth: 120, indentWidth: 2 }, {});
10
+
11
+ export const prettify = (code: string, options?: GlobalConfiguration) =>
12
+ formatter.formatText("code.ts", code, options as any);
@@ -0,0 +1,305 @@
1
+ import { capitalize, groupBy } from "pastable/server";
2
+ import { Box } from "./box";
3
+ import { prettify } from "./format";
4
+ import { mapOpenApiEndpoints } from "./map-openapi-endpoints";
5
+ import { AnyBox, BoxRef } from "./types";
6
+ import * as Codegen from "@sinclair/typebox-codegen";
7
+ import { match } from "ts-pattern";
8
+ import { type } from "arktype";
9
+ import { wrapWithQuotesIfNeeded } from "./string-utils";
10
+
11
+ type GeneratorOptions = ReturnType<typeof mapOpenApiEndpoints> & {
12
+ runtime?: "none" | keyof typeof runtimeValidationGenerator;
13
+ };
14
+ type GeneratorContext = Required<GeneratorOptions>;
15
+
16
+ export const allowedRuntimes = type("'none' | 'arktype' | 'io-ts' | 'typebox' | 'valibot' | 'yup' | 'zod'");
17
+
18
+ // TODO validate response schemas in sample fetch ApiClient
19
+ // also, check that we can easily retrieve the response schema from the Fetcher
20
+
21
+ const runtimeValidationGenerator = {
22
+ arktype: Codegen.ModelToArkType.Generate,
23
+ "io-ts": Codegen.ModelToIoTs.Generate,
24
+ typebox: Codegen.ModelToTypeBox.Generate,
25
+ valibot: Codegen.ModelToValibot.Generate,
26
+ yup: Codegen.ModelToYup.Generate,
27
+ zod: Codegen.ModelToZod.Generate,
28
+ };
29
+
30
+ const inferByRuntime = {
31
+ none: (input: string) => input,
32
+ arktype: (input: string) => `${input}["infer"]`,
33
+ "io-ts": (input: string) => `t.TypeOf<${input}>`,
34
+ typebox: (input: string) => `Static<${input}>`,
35
+ valibot: (input: string) => `v.Output<${input}>`,
36
+ yup: (input: string) => `y.InferType<${input}>`,
37
+ zod: (input: string) => `z.infer<${input}>`,
38
+ };
39
+
40
+ const methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"] as const;
41
+ const methodsRegex = new RegExp(`(?:${methods.join("|")})_`);
42
+ const endpointExport = new RegExp(`export (?:type|const) (?:${methodsRegex.source})`);
43
+
44
+ const replacerByRuntime = {
45
+ yup: (line: string) =>
46
+ line
47
+ .replace(/y\.InferType<\s*?typeof (.*?)\s*?>/g, "typeof $1")
48
+ .replace(new RegExp(`(${endpointExport.source})` + new RegExp(/(.*? )(y\.object)(\()/).source, "g"), "$1$2("),
49
+ zod: (line: string) =>
50
+ line
51
+ .replace(/z\.infer<\s*?typeof (.*?)\s*?>/g, "typeof $1")
52
+ .replace(new RegExp(`(${endpointExport.source})` + new RegExp(/(.*? )(z\.object)(\()/).source, "g"), "$1$2("),
53
+ };
54
+
55
+ export const generateFile = (options: GeneratorOptions) => {
56
+ const ctx = { ...options, runtime: options.runtime ?? "none" } as GeneratorContext;
57
+
58
+ const schemaList = generateSchemaList(ctx);
59
+ const endpointSchemaList = generateEndpointSchemaList(ctx);
60
+ const apiClient = generateApiClient(ctx);
61
+
62
+ const transform =
63
+ ctx.runtime === "none"
64
+ ? (file: string) => file
65
+ : (file: string) => {
66
+ const model = Codegen.TypeScriptToModel.Generate(file);
67
+ const transformer = runtimeValidationGenerator[ctx.runtime as Exclude<typeof ctx.runtime, "none">];
68
+ // tmp fix for typebox, there's currently a "// todo" only with Codegen.ModelToTypeBox.Generate
69
+ // https://github.com/sinclairzx81/typebox-codegen/blob/44d44d55932371b69f349331b1c8a60f5d760d9e/src/model/model-to-typebox.ts#L31
70
+ const generated = ctx.runtime === "typebox" ? Codegen.TypeScriptToTypeBox.Generate(file) : transformer(model);
71
+
72
+ let converted = "";
73
+ const match = generated.match(/(const __ENDPOINTS_START__ =)([\s\S]*?)(export type __ENDPOINTS_END__)/);
74
+ const content = match?.[2];
75
+
76
+ if (content && ctx.runtime in replacerByRuntime) {
77
+ const before = generated.slice(0, generated.indexOf("export type __ENDPOINTS_START"));
78
+ converted =
79
+ before +
80
+ replacerByRuntime[ctx.runtime as keyof typeof replacerByRuntime](
81
+ content.slice(content.indexOf("export")),
82
+ );
83
+ } else {
84
+ converted = generated;
85
+ }
86
+
87
+ return converted;
88
+ };
89
+
90
+ const file = `
91
+ ${transform(schemaList + endpointSchemaList)}
92
+ ${apiClient}
93
+ `;
94
+
95
+ return prettify(file);
96
+ };
97
+
98
+ const generateSchemaList = ({ refs }: GeneratorContext) => {
99
+ let file = `
100
+ export namespace Schemas {
101
+ // <Schemas>
102
+ `;
103
+ refs.getOrderedSchemas().forEach(([schema, infos]) => {
104
+ if (!infos?.name) return;
105
+ if (infos.kind !== "schemas") return;
106
+
107
+ file += `export type ${infos.normalized} = ${schema.value}\n`;
108
+ });
109
+
110
+ return (
111
+ file +
112
+ `
113
+ // </Schemas>
114
+ }
115
+ `
116
+ );
117
+ };
118
+
119
+ const parameterObjectToString = (parameters: Box<BoxRef> | Record<string, AnyBox>) => {
120
+ if (parameters instanceof Box) return parameters.value;
121
+
122
+ let str = "{";
123
+ for (const [key, box] of Object.entries(parameters)) {
124
+ str += `${wrapWithQuotesIfNeeded(key)}: ${box.value},\n`;
125
+ }
126
+ return str + "}";
127
+ };
128
+ const generateEndpointSchemaList = (ctx: GeneratorContext) => {
129
+ let file = `
130
+ export namespace Endpoints {
131
+ // <Endpoints>
132
+ ${ctx.runtime === "none" ? "" : "type __ENDPOINTS_START__ = {}"}
133
+ `;
134
+ ctx.endpointList.map((endpoint) => {
135
+ const parameters = endpoint.parameters ?? {};
136
+ file += `export type ${endpoint.meta.alias} = {
137
+ method: "${endpoint.method.toUpperCase()}",
138
+ path: "${endpoint.path}",
139
+ ${
140
+ endpoint.meta.hasParameters
141
+ ? `parameters: {
142
+ ${parameters.query ? `query: ${parameterObjectToString(parameters.query)},` : ""}
143
+ ${parameters.path ? `path: ${parameterObjectToString(parameters.path)},` : ""}
144
+ ${parameters.header ? `header: ${parameterObjectToString(parameters.header)},` : ""}
145
+ }`
146
+ : "parameters: never,"
147
+ }
148
+ response: ${
149
+ endpoint.response.recompute((box) => {
150
+ if (Box.isReference(box) && !box.params.generics) {
151
+ box.value = `Schemas.${box.value}`;
152
+ }
153
+
154
+ return box;
155
+ }).value
156
+ },
157
+ }\n`;
158
+ });
159
+
160
+ return (
161
+ file +
162
+ `
163
+ // </Endpoints>
164
+ }
165
+ ${ctx.runtime === "none" ? "" : "type __ENDPOINTS_END__ = {}"}
166
+ `
167
+ );
168
+ };
169
+
170
+ const generateEndpointByMethod = (ctx: GeneratorContext) => {
171
+ const { endpointList } = ctx;
172
+ const byMethods = groupBy(endpointList, "method");
173
+
174
+ const endpointByMethod = `
175
+ // <EndpointByMethod>
176
+ export ${ctx.runtime === "none" ? "type" : "const"} EndpointByMethod = {
177
+ ${Object.entries(byMethods)
178
+ .map(([method, list]) => {
179
+ return `${method}: {
180
+ ${list.map((endpoint) => `"${endpoint.path}": Endpoints.${endpoint.meta.alias}`)}
181
+ }`;
182
+ })
183
+ .join(",\n")}
184
+ }
185
+ ${ctx.runtime === "none" ? "" : "export type EndpointByMethod = typeof EndpointByMethod;"}
186
+ // </EndpointByMethod>
187
+ `;
188
+
189
+ const shorthands = `
190
+
191
+ // <EndpointByMethod.Shorthands>
192
+ ${Object.keys(byMethods)
193
+ .map((method) => `export type ${capitalize(method)}Endpoints = EndpointByMethod["${method}"]`)
194
+ .join("\n")}
195
+ ${endpointList.length ? `export type AllEndpoints = EndpointByMethod[keyof EndpointByMethod];` : ""}
196
+ // </EndpointByMethod.Shorthands>
197
+ `;
198
+
199
+ return endpointByMethod + shorthands;
200
+ };
201
+
202
+ const generateApiClient = (ctx: GeneratorContext) => {
203
+ const { endpointList } = ctx;
204
+ const byMethods = groupBy(endpointList, "method");
205
+ const endpointSchemaList = generateEndpointByMethod(ctx);
206
+
207
+ const apiClientTypes = `
208
+ // <ApiClientTypes>
209
+ export type EndpointParameters = {
210
+ body?: unknown;
211
+ query?: Record<string, unknown>;
212
+ header?: Record<string, unknown>;
213
+ path?: Record<string, unknown>;
214
+ };
215
+
216
+ export type MutationMethod = "post" | "put" | "patch" | "delete";
217
+ export type Method = "get" | "head" | MutationMethod;
218
+
219
+ export type DefaultEndpoint = {
220
+ parameters?: EndpointParameters | undefined;
221
+ response: unknown;
222
+ };
223
+
224
+ export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
225
+ operationId: string;
226
+ method: Method;
227
+ path: string;
228
+ parameters?: TConfig["parameters"];
229
+ meta: {
230
+ alias: string;
231
+ hasParameters: boolean;
232
+ areParametersRequired: boolean;
233
+ };
234
+ response: TConfig["response"];
235
+ };
236
+
237
+ type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Endpoint["response"]>;
238
+
239
+ type RequiredKeys<T> = {
240
+ [P in keyof T]-?: undefined extends T[P] ? never : P;
241
+ }[keyof T];
242
+
243
+ type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T];
244
+
245
+ // </ApiClientTypes>
246
+ `;
247
+
248
+ const apiClient = `
249
+ // <ApiClient>
250
+ export class ApiClient {
251
+ baseUrl: string = "";
252
+
253
+ constructor(public fetcher: Fetcher) {}
254
+
255
+ setBaseUrl(baseUrl: string) {
256
+ this.baseUrl = baseUrl;
257
+ return this;
258
+ }
259
+
260
+ ${Object.entries(byMethods)
261
+ .map(([method, endpointByMethod]) => {
262
+ const capitalizedMethod = capitalize(method);
263
+ const infer = inferByRuntime[ctx.runtime];
264
+
265
+ return endpointByMethod.length
266
+ ? `// <ApiClient.${method}>
267
+ ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
268
+ path: Path,
269
+ ...params: MaybeOptionalArg<${match(ctx.runtime)
270
+ .with("zod", "yup", () => infer(`TEndpoint["parameters"]`))
271
+ .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`)
272
+ .otherwise(() => `TEndpoint["parameters"]`)}>
273
+ ): Promise<${match(ctx.runtime)
274
+ .with("zod", "yup", () => infer(`TEndpoint["response"]`))
275
+ .with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`)
276
+ .otherwise(() => `TEndpoint["response"]`)}> {
277
+ return this.fetcher("${method}", this.baseUrl + path, params[0]);
278
+ }
279
+ // </ApiClient.${method}>
280
+ `
281
+ : "";
282
+ })
283
+ .join("\n")}
284
+ }
285
+
286
+ export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
287
+ return new ApiClient(fetcher).setBaseUrl(baseUrl ?? "");
288
+ }
289
+
290
+
291
+ /**
292
+ Example usage:
293
+ const api = createApiClient((method, url, params) =>
294
+ fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()),
295
+ );
296
+ api.get("/users").then((users) => console.log(users));
297
+ api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
298
+ api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
299
+ */
300
+
301
+ // </ApiClient
302
+ `;
303
+
304
+ return endpointSchemaList + apiClientTypes + apiClient;
305
+ };
@@ -0,0 +1,16 @@
1
+ // taken from
2
+ // https://github.dev/metadevpro/openapi3-ts/blob/a62ff445207af599f591532ef776e671c456cc37/src/model/OpenApi.ts#L261-L269
3
+ // to avoid the runtime dependency on `openapi3-ts`
4
+ // which itself depends on `yaml` import (which use CJS `require` and thus can't be imported in a ESM module)
5
+
6
+ import type { ReferenceObject } from "openapi3-ts/oas31";
7
+
8
+ /**
9
+ * A type guard to check if the given value is a `ReferenceObject`.
10
+ * See https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types
11
+ *
12
+ * @param obj The value to check.
13
+ */
14
+ export function isReferenceObject(obj: any): obj is ReferenceObject {
15
+ return obj != null && Object.prototype.hasOwnProperty.call(obj, "$ref");
16
+ }