typed-openapi 0.1.2 → 0.1.4

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/dist/cli.js CHANGED
@@ -1,825 +1,25 @@
1
+ import {
2
+ allowedRuntimes,
3
+ generateFile,
4
+ mapOpenApiEndpoints
5
+ } from "./chunk-TUNDL3P7.js";
6
+
1
7
  // src/cli.ts
2
8
  import SwaggerParser from "@apidevtools/swagger-parser";
3
9
  import { cac } from "cac";
4
10
  import { join } from "pathe";
5
- import { type as type2 } from "arktype";
11
+ import { type } from "arktype";
6
12
  import { writeFile } from "fs/promises";
7
13
 
8
14
  // package.json
9
15
  var name = "typed-openapi";
10
- var version = "0.1.1";
11
-
12
- // src/generator.ts
13
- import { capitalize as capitalize2, groupBy } from "pastable/server";
14
-
15
- // src/asserts.ts
16
- var isPrimitiveType = (type3) => primitiveTypeList.includes(type3);
17
- var primitiveTypeList = ["string", "number", "integer", "boolean", "null"];
18
-
19
- // src/box-factory.ts
20
- var unwrap = (param) => typeof param === "string" ? param : param.value;
21
- var createFactory = (f) => f;
22
- var createBoxFactory = (schema, ctx) => {
23
- const f = typeof ctx.factory === "function" ? ctx.factory(schema, ctx) : ctx.factory;
24
- const callback = (box2) => {
25
- if (f.callback) {
26
- box2 = f.callback(box2);
27
- }
28
- if (ctx?.onBox) {
29
- box2 = ctx.onBox?.(box2);
30
- }
31
- return box2;
32
- };
33
- const box = {
34
- union: (types) => callback(new Box({ ctx, schema, type: "union", params: { types }, value: f.union(types) })),
35
- intersection: (types) => callback(new Box({ ctx, schema, type: "intersection", params: { types }, value: f.intersection(types) })),
36
- array: (type3) => callback(new Box({ ctx, schema, type: "array", params: { type: type3 }, value: f.array(type3) })),
37
- optional: (type3) => callback(new Box({ ctx, schema, type: "optional", params: { type: type3 }, value: f.optional(type3) })),
38
- reference: (name2, generics) => callback(
39
- new Box({
40
- ctx,
41
- schema,
42
- type: "ref",
43
- params: generics ? { name: name2, generics } : { name: name2 },
44
- value: f.reference(name2, generics)
45
- })
46
- ),
47
- literal: (value) => callback(new Box({ ctx, schema, type: "literal", params: {}, value: f.literal(value) })),
48
- string: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "string" }, value: f.string() })),
49
- number: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "number" }, value: f.number() })),
50
- boolean: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "boolean" }, value: f.boolean() })),
51
- unknown: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "unknown" }, value: f.unknown() })),
52
- any: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "any" }, value: f.any() })),
53
- never: () => callback(new Box({ ctx, schema, type: "keyword", params: { name: "never" }, value: f.never() })),
54
- object: (props) => callback(new Box({ ctx, schema, type: "object", params: { props }, value: f.object(props) }))
55
- };
56
- return box;
57
- };
58
-
59
- // src/is-reference-object.ts
60
- function isReferenceObject(obj) {
61
- return obj != null && Object.prototype.hasOwnProperty.call(obj, "$ref");
62
- }
63
-
64
- // src/string-utils.ts
65
- import { capitalize, kebabToCamel } from "pastable/server";
66
- function normalizeString(text) {
67
- const prefixed = prefixStringStartingWithNumberIfNeeded(text);
68
- return prefixed.normalize("NFKD").trim().replace(/\s+/g, "_").replace(/-+/g, "_").replace(/[^\w\-]+/g, "_").replace(/--+/g, "-");
69
- }
70
- var onlyWordRegex = /^\w+$/;
71
- var wrapWithQuotesIfNeeded = (str) => {
72
- if (str[0] === '"' && str[str.length - 1] === '"')
73
- return str;
74
- if (onlyWordRegex.test(str)) {
75
- return str;
76
- }
77
- return `"${str}"`;
78
- };
79
- var prefixStringStartingWithNumberIfNeeded = (str) => {
80
- const firstAsNumber = Number(str[0]);
81
- if (typeof firstAsNumber === "number" && !Number.isNaN(firstAsNumber)) {
82
- return "_" + str;
83
- }
84
- return str;
85
- };
86
- var pathParamWithBracketsRegex = /({\w+})/g;
87
- var wordPrecededByNonWordCharacter = /[^\w\-]+/g;
88
- var pathToVariableName = (path) => capitalize(kebabToCamel(path).replaceAll("/", "")).replace(pathParamWithBracketsRegex, (group) => capitalize(group.slice(1, -1))).replace(wordPrecededByNonWordCharacter, "_");
89
-
90
- // src/openapi-schema-to-ts.ts
91
- var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
92
- const meta = {};
93
- if (!schema) {
94
- throw new Error("Schema is required");
95
- }
96
- const t = createBoxFactory(schema, ctx);
97
- const getTs = () => {
98
- if (isReferenceObject(schema)) {
99
- const refInfo = ctx.refs.getInfosByRef(schema.$ref);
100
- return t.reference(refInfo.normalized);
101
- }
102
- if (Array.isArray(schema.type)) {
103
- if (schema.type.length === 1) {
104
- return openApiSchemaToTs({ schema: { ...schema, type: schema.type[0] }, ctx, meta });
105
- }
106
- return t.union(schema.type.map((prop) => openApiSchemaToTs({ schema: { ...schema, type: prop }, ctx, meta })));
107
- }
108
- if (schema.type === "null") {
109
- return t.reference("null");
110
- }
111
- if (schema.oneOf) {
112
- if (schema.oneOf.length === 1) {
113
- return openApiSchemaToTs({ schema: schema.oneOf[0], ctx, meta });
114
- }
115
- return t.union(schema.oneOf.map((prop) => openApiSchemaToTs({ schema: prop, ctx, meta })));
116
- }
117
- if (schema.anyOf) {
118
- if (schema.anyOf.length === 1) {
119
- return openApiSchemaToTs({ schema: schema.anyOf[0], ctx, meta });
120
- }
121
- const oneOf = t.union(schema.anyOf.map((prop) => openApiSchemaToTs({ schema: prop, ctx, meta })));
122
- return t.union([oneOf, t.array(oneOf)]);
123
- }
124
- if (schema.allOf) {
125
- if (schema.allOf.length === 1) {
126
- return openApiSchemaToTs({ schema: schema.allOf[0], ctx, meta });
127
- }
128
- const types = schema.allOf.map((prop) => openApiSchemaToTs({ schema: prop, ctx, meta }));
129
- return t.intersection(types);
130
- }
131
- const schemaType = schema.type ? schema.type.toLowerCase() : void 0;
132
- if (schemaType && isPrimitiveType(schemaType)) {
133
- if (schema.enum) {
134
- if (schema.enum.length === 1) {
135
- const value = schema.enum[0];
136
- return t.literal(value === null ? "null" : `"${value}"`);
137
- }
138
- if (schemaType === "string") {
139
- return t.union(schema.enum.map((value) => t.literal(`"${value}"`)));
140
- }
141
- if (schema.enum.some((e) => typeof e === "string")) {
142
- return t.never();
143
- }
144
- return t.union(schema.enum.map((value) => t.literal(value === null ? "null" : value)));
145
- }
146
- if (schemaType === "string")
147
- return t.string();
148
- if (schemaType === "boolean")
149
- return t.boolean();
150
- if (schemaType === "number" || schemaType === "integer")
151
- return t.number();
152
- if (schemaType === "null")
153
- return t.reference("null");
154
- }
155
- if (schemaType === "array") {
156
- if (schema.items) {
157
- let arrayOfType = openApiSchemaToTs({ schema: schema.items, ctx, meta });
158
- if (typeof arrayOfType === "string") {
159
- arrayOfType = t.reference(arrayOfType);
160
- }
161
- return t.array(arrayOfType);
162
- }
163
- return t.array(t.any());
164
- }
165
- if (schemaType === "object" || schema.properties || schema.additionalProperties) {
166
- if (!schema.properties) {
167
- return t.unknown();
168
- }
169
- let additionalProperties;
170
- if (schema.additionalProperties) {
171
- let additionalPropertiesType;
172
- if (typeof schema.additionalProperties === "boolean" && schema.additionalProperties || typeof schema.additionalProperties === "object" && Object.keys(schema.additionalProperties).length === 0) {
173
- additionalPropertiesType = t.any();
174
- } else if (typeof schema.additionalProperties === "object") {
175
- additionalPropertiesType = openApiSchemaToTs({
176
- schema: schema.additionalProperties,
177
- ctx,
178
- meta
179
- });
180
- }
181
- additionalProperties = t.object({ [t.string().value]: additionalPropertiesType });
182
- }
183
- const hasRequiredArray = schema.required && schema.required.length > 0;
184
- const isPartial = !schema.required?.length;
185
- const props = Object.fromEntries(
186
- Object.entries(schema.properties).map(([prop, propSchema]) => {
187
- let propType = openApiSchemaToTs({ schema: propSchema, ctx, meta });
188
- if (typeof propType === "string") {
189
- propType = t.reference(propType);
190
- }
191
- const isRequired = Boolean(isPartial ? true : hasRequiredArray ? schema.required?.includes(prop) : false);
192
- const isOptional = !isPartial && !isRequired;
193
- return [
194
- `${wrapWithQuotesIfNeeded(prop)}${isOptional ? "?" : ""}`,
195
- isOptional ? t.optional(propType) : propType
196
- ];
197
- })
198
- );
199
- const objectType = additionalProperties ? t.intersection([t.object(props), additionalProperties]) : t.object(props);
200
- return isPartial ? t.reference("Partial", [objectType]) : objectType;
201
- }
202
- if (!schemaType)
203
- return t.unknown();
204
- throw new Error(`Unsupported schema type: ${schemaType}`);
205
- };
206
- return getTs();
207
- };
208
-
209
- // src/box.ts
210
- var Box = class _Box {
211
- constructor(definition) {
212
- this.definition = definition;
213
- this.definition = definition;
214
- this.type = definition.type;
215
- this.value = definition.value;
216
- this.params = definition.params;
217
- this.schema = definition.schema;
218
- this.ctx = definition.ctx;
219
- }
220
- type;
221
- value;
222
- params;
223
- schema;
224
- ctx;
225
- toJSON() {
226
- return { type: this.type, value: this.value };
227
- }
228
- toString() {
229
- return JSON.stringify(this.toJSON(), null, 2);
230
- }
231
- recompute(callback) {
232
- return openApiSchemaToTs({ schema: this.schema, ctx: { ...this.ctx, onBox: callback } });
233
- }
234
- static fromJSON(json) {
235
- return new _Box(JSON.parse(json));
236
- }
237
- static isBox(box) {
238
- return box instanceof _Box;
239
- }
240
- static isUnion(box) {
241
- return box.type === "union";
242
- }
243
- static isIntersection(box) {
244
- return box.type === "intersection";
245
- }
246
- static isArray(box) {
247
- return box.type === "array";
248
- }
249
- static isOptional(box) {
250
- return box.type === "optional";
251
- }
252
- static isReference(box) {
253
- return box.type === "ref";
254
- }
255
- static isKeyword(box) {
256
- return box.type === "keyword";
257
- }
258
- static isObject(box) {
259
- return box.type === "object";
260
- }
261
- static isLiteral(box) {
262
- return box.type === "literal";
263
- }
264
- };
265
-
266
- // src/format.ts
267
- import prettier from "prettier";
268
- import parserTypescript from "prettier/parser-typescript";
269
- function maybePretty(input, options) {
270
- try {
271
- return prettier.format(input, {
272
- parser: "typescript",
273
- plugins: [parserTypescript],
274
- ...options
275
- });
276
- } catch (err) {
277
- console.warn("Failed to format code");
278
- console.warn(err);
279
- return input;
280
- }
281
- }
282
- var prettify = (str, options) => maybePretty(str, { printWidth: 120, trailingComma: "all", ...options });
283
-
284
- // src/generator.ts
285
- import * as Codegen from "@sinclair/typebox-codegen";
286
- import { match } from "ts-pattern";
287
- import { type } from "arktype";
288
- var allowedRuntimes = type("'none' | 'arktype' | 'io-ts' | 'typebox' | 'valibot' | 'yup' | 'zod'");
289
- var runtimeValidationGenerator = {
290
- arktype: Codegen.ModelToArkType.Generate,
291
- "io-ts": Codegen.ModelToIoTs.Generate,
292
- typebox: Codegen.ModelToTypeBox.Generate,
293
- valibot: Codegen.ModelToValibot.Generate,
294
- yup: Codegen.ModelToYup.Generate,
295
- zod: Codegen.ModelToZod.Generate
296
- };
297
- var inferByRuntime = {
298
- none: (input) => input,
299
- arktype: (input) => `${input}["infer"]`,
300
- "io-ts": (input) => `t.TypeOf<${input}>`,
301
- typebox: (input) => `Static<${input}>`,
302
- valibot: (input) => `v.Output<${input}>`,
303
- yup: (input) => `y.InferType<${input}>`,
304
- zod: (input) => `z.infer<${input}>`
305
- };
306
- var methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
307
- var methodsRegex = new RegExp(`(?:${methods.join("|")})_`);
308
- var endpointExport = new RegExp(`export (?:type|const) (?:${methodsRegex.source})`);
309
- var replacerByRuntime = {
310
- yup: (line) => line.replace(/y\.InferType<\s*?typeof (.*?)\s*?>/g, "typeof $1").replace(new RegExp(`(${endpointExport.source})` + new RegExp(/(.*? )(y\.object)(\()/).source, "g"), "$1$2("),
311
- zod: (line) => line.replace(/z\.infer<\s*?typeof (.*?)\s*?>/g, "typeof $1").replace(new RegExp(`(${endpointExport.source})` + new RegExp(/(.*? )(z\.object)(\()/).source, "g"), "$1$2(")
312
- };
313
- var generateFile = (options) => {
314
- const ctx = { ...options, runtime: options.runtime ?? "none" };
315
- const schemaList = generateSchemaList(ctx);
316
- const endpointSchemaList = generateEndpointSchemaList(ctx);
317
- const apiClient = generateApiClient(ctx);
318
- const transform = ctx.runtime === "none" ? (file2) => file2 : (file2) => {
319
- const model = Codegen.TypeScriptToModel.Generate(file2);
320
- const transformer = runtimeValidationGenerator[ctx.runtime];
321
- const generated = ctx.runtime === "typebox" ? Codegen.TypeScriptToTypeBox.Generate(file2) : transformer(model);
322
- let converted = "";
323
- const match2 = generated.match(/(const __ENDPOINTS_START__ =)([\s\S]*?)(export type __ENDPOINTS_END__)/);
324
- const content = match2?.[2];
325
- if (content && ctx.runtime in replacerByRuntime) {
326
- const before = generated.slice(0, generated.indexOf("export type __ENDPOINTS_START"));
327
- converted = before + replacerByRuntime[ctx.runtime](
328
- content.slice(content.indexOf("export"))
329
- );
330
- } else {
331
- converted = generated;
332
- }
333
- return converted;
334
- };
335
- const file = `
336
- ${transform(schemaList + endpointSchemaList)}
337
- ${apiClient}
338
- `;
339
- return prettify(file);
340
- };
341
- var generateSchemaList = ({ refs, runtime }) => {
342
- let file = `
343
- ${runtime === "none" ? "export namespace Schemas {" : ""}
344
- // <Schemas>
345
- `;
346
- refs.getOrderedSchemas().forEach(([schema, infos]) => {
347
- if (!infos?.name)
348
- return;
349
- if (infos.kind !== "schemas")
350
- return;
351
- file += `export type ${infos.normalized} = ${schema.value}
352
- `;
353
- });
354
- return file + `
355
- // </Schemas>
356
- ${runtime === "none" ? "}" : ""}
357
- `;
358
- };
359
- var parameterObjectToString = (parameters) => {
360
- if (parameters instanceof Box)
361
- return parameters.value;
362
- let str = "{";
363
- for (const [key, box] of Object.entries(parameters)) {
364
- str += `${wrapWithQuotesIfNeeded(key)}: ${box.value},
365
- `;
366
- }
367
- return str + "}";
368
- };
369
- var generateEndpointSchemaList = (ctx) => {
370
- let file = `
371
- ${ctx.runtime === "none" ? "export namespace Endpoints {" : ""}
372
- // <Endpoints>
373
- ${ctx.runtime === "none" ? "" : "type __ENDPOINTS_START__ = {}"}
374
- `;
375
- ctx.endpointList.map((endpoint) => {
376
- const parameters = endpoint.parameters ?? {};
377
- file += `export type ${endpoint.meta.alias} = {
378
- method: "${endpoint.method.toUpperCase()}",
379
- path: "${endpoint.path}",
380
- ${endpoint.meta.hasParameters ? `parameters: {
381
- ${parameters.query ? `query: ${parameterObjectToString(parameters.query)},` : ""}
382
- ${parameters.path ? `path: ${parameterObjectToString(parameters.path)},` : ""}
383
- ${parameters.header ? `header: ${parameterObjectToString(parameters.header)},` : ""}
384
- }` : "parameters: never,"}
385
- response: ${ctx.runtime === "none" ? endpoint.response.recompute((box) => {
386
- if (Box.isReference(box) && !box.params.generics) {
387
- box.value = `Schemas.${box.value}`;
388
- }
389
- return box;
390
- }).value : endpoint.response.value},
391
- }
392
- `;
393
- });
394
- return file + `
395
- // </Endpoints>
396
- ${ctx.runtime === "none" ? "}" : ""}
397
- ${ctx.runtime === "none" ? "" : "type __ENDPOINTS_END__ = {}"}
398
- `;
399
- };
400
- var generateEndpointByMethod = (ctx) => {
401
- const { endpointList } = ctx;
402
- const byMethods = groupBy(endpointList, "method");
403
- const endpointByMethod = `
404
- // <EndpointByMethod>
405
- export ${ctx.runtime === "none" ? "type" : "const"} EndpointByMethod = {
406
- ${Object.entries(byMethods).map(([method, list]) => {
407
- return `${method}: {
408
- ${list.map(
409
- (endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`
410
- )}
411
- }`;
412
- }).join(",\n")}
413
- }
414
- ${ctx.runtime === "none" ? "" : "export type EndpointByMethod = typeof EndpointByMethod;"}
415
- // </EndpointByMethod>
416
- `;
417
- const shorthands = `
418
-
419
- // <EndpointByMethod.Shorthands>
420
- ${Object.keys(byMethods).map((method) => `export type ${capitalize2(method)}Endpoints = EndpointByMethod["${method}"]`).join("\n")}
421
- ${endpointList.length ? `export type AllEndpoints = EndpointByMethod[keyof EndpointByMethod];` : ""}
422
- // </EndpointByMethod.Shorthands>
423
- `;
424
- return endpointByMethod + shorthands;
425
- };
426
- var generateApiClient = (ctx) => {
427
- const { endpointList } = ctx;
428
- const byMethods = groupBy(endpointList, "method");
429
- const endpointSchemaList = generateEndpointByMethod(ctx);
430
- const apiClientTypes = `
431
- // <ApiClientTypes>
432
- export type EndpointParameters = {
433
- body?: unknown;
434
- query?: Record<string, unknown>;
435
- header?: Record<string, unknown>;
436
- path?: Record<string, unknown>;
437
- };
438
-
439
- export type MutationMethod = "post" | "put" | "patch" | "delete";
440
- export type Method = "get" | "head" | MutationMethod;
441
-
442
- export type DefaultEndpoint = {
443
- parameters?: EndpointParameters | undefined;
444
- response: unknown;
445
- };
446
-
447
- export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
448
- operationId: string;
449
- method: Method;
450
- path: string;
451
- parameters?: TConfig["parameters"];
452
- meta: {
453
- alias: string;
454
- hasParameters: boolean;
455
- areParametersRequired: boolean;
456
- };
457
- response: TConfig["response"];
458
- };
459
-
460
- type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Endpoint["response"]>;
461
-
462
- type RequiredKeys<T> = {
463
- [P in keyof T]-?: undefined extends T[P] ? never : P;
464
- }[keyof T];
465
-
466
- type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T];
467
-
468
- // </ApiClientTypes>
469
- `;
470
- const apiClient = `
471
- // <ApiClient>
472
- export class ApiClient {
473
- baseUrl: string = "";
474
-
475
- constructor(public fetcher: Fetcher) {}
476
-
477
- setBaseUrl(baseUrl: string) {
478
- this.baseUrl = baseUrl;
479
- return this;
480
- }
481
-
482
- ${Object.entries(byMethods).map(([method, endpointByMethod]) => {
483
- const capitalizedMethod = capitalize2(method);
484
- const infer = inferByRuntime[ctx.runtime];
485
- return endpointByMethod.length ? `// <ApiClient.${method}>
486
- ${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
487
- path: Path,
488
- ...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)}>
489
- ): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}> {
490
- return this.fetcher("${method}", this.baseUrl + path, params[0]);
491
- }
492
- // </ApiClient.${method}>
493
- ` : "";
494
- }).join("\n")}
495
- }
496
-
497
- export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
498
- return new ApiClient(fetcher).setBaseUrl(baseUrl ?? "");
499
- }
500
-
501
-
502
- /**
503
- Example usage:
504
- const api = createApiClient((method, url, params) =>
505
- fetch(url, { method, body: JSON.stringify(params) }).then((res) => res.json()),
506
- );
507
- api.get("/users").then((users) => console.log(users));
508
- api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
509
- api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
510
- */
511
-
512
- // </ApiClient
513
- `;
514
- return endpointSchemaList + apiClientTypes + apiClient;
515
- };
516
-
517
- // src/map-openapi-endpoints.ts
518
- import { capitalize as capitalize3, pick } from "pastable/server";
519
-
520
- // src/ref-resolver.ts
521
- import { get } from "pastable/server";
522
-
523
- // src/topological-sort.ts
524
- function topologicalSort(graph) {
525
- const sorted = [], visited = {};
526
- function visit(name2, ancestors) {
527
- if (!Array.isArray(ancestors))
528
- ancestors = [];
529
- ancestors.push(name2);
530
- visited[name2] = true;
531
- const deps = graph.get(name2);
532
- if (deps) {
533
- deps.forEach((dep) => {
534
- if (ancestors.includes(dep)) {
535
- return;
536
- }
537
- if (visited[dep])
538
- return;
539
- visit(dep, ancestors.slice(0));
540
- });
541
- }
542
- if (!sorted.includes(name2))
543
- sorted.push(name2);
544
- }
545
- graph.forEach((_, name2) => visit(name2, []));
546
- return sorted;
547
- }
548
-
549
- // src/ref-resolver.ts
550
- var autocorrectRef = (ref) => ref[1] === "/" ? ref : "#/" + ref.slice(1);
551
- var componentsWithSchemas = ["schemas", "responses", "parameters", "requestBodies", "headers"];
552
- var createRefResolver = (doc, factory2) => {
553
- const nameByRef = /* @__PURE__ */ new Map();
554
- const refByName = /* @__PURE__ */ new Map();
555
- const byRef = /* @__PURE__ */ new Map();
556
- const byNormalized = /* @__PURE__ */ new Map();
557
- const boxByRef = /* @__PURE__ */ new Map();
558
- const getSchemaByRef = (ref) => {
559
- const correctRef = autocorrectRef(ref);
560
- const split = correctRef.split("/");
561
- const path = split.slice(1, -1).join("/");
562
- const normalizedPath = path.replace("#/", "").replace("#", "").replaceAll("/", ".");
563
- const map = get(doc, normalizedPath) ?? {};
564
- const name2 = split[split.length - 1];
565
- const normalized = normalizeString(name2);
566
- nameByRef.set(correctRef, normalized);
567
- refByName.set(normalized, correctRef);
568
- const infos = { ref: correctRef, name: name2, normalized, kind: normalizedPath.split(".")[1] };
569
- byRef.set(infos.ref, infos);
570
- byNormalized.set(infos.normalized, infos);
571
- const schema = map[name2];
572
- if (!schema) {
573
- throw new Error(`Unresolved ref "${name2}" not found in "${path}"`);
574
- }
575
- return schema;
576
- };
577
- const getInfosByRef = (ref) => byRef.get(autocorrectRef(ref));
578
- const schemaEntries = Object.entries(doc.components ?? {}).filter(([key]) => componentsWithSchemas.includes(key));
579
- schemaEntries.forEach(([key, component]) => {
580
- Object.keys(component).map((name2) => {
581
- const ref = `#/components/${key}/${name2}`;
582
- getSchemaByRef(ref);
583
- });
584
- });
585
- const directDependencies = /* @__PURE__ */ new Map();
586
- schemaEntries.forEach(([key, component]) => {
587
- Object.keys(component).map((name2) => {
588
- const ref = `#/components/${key}/${name2}`;
589
- const schema = getSchemaByRef(ref);
590
- boxByRef.set(ref, openApiSchemaToTs({ schema, ctx: { factory: factory2, refs: { getInfosByRef } } }));
591
- if (!directDependencies.has(ref)) {
592
- directDependencies.set(ref, /* @__PURE__ */ new Set());
593
- }
594
- setSchemaDependencies(schema, directDependencies.get(ref));
595
- });
596
- });
597
- const transitiveDependencies = getTransitiveDependencies(directDependencies);
598
- return {
599
- get: getSchemaByRef,
600
- unwrap: (component) => {
601
- return isReferenceObject(component) ? getSchemaByRef(component.$ref) : component;
602
- },
603
- getInfosByRef,
604
- infos: byRef,
605
- /**
606
- * Get the schemas in the order they should be generated, depending on their dependencies
607
- * so that a schema is generated before the ones that depend on it
608
- */
609
- getOrderedSchemas: () => {
610
- const schemaOrderedByDependencies = topologicalSort(transitiveDependencies).map((ref) => {
611
- const infos = getInfosByRef(ref);
612
- return [boxByRef.get(infos.ref), infos];
613
- });
614
- return schemaOrderedByDependencies;
615
- },
616
- directDependencies,
617
- transitiveDependencies
618
- };
619
- };
620
- var setSchemaDependencies = (schema, deps) => {
621
- const visit = (schema2) => {
622
- if (!schema2)
623
- return;
624
- if (isReferenceObject(schema2)) {
625
- deps.add(schema2.$ref);
626
- return;
627
- }
628
- if (schema2.allOf) {
629
- for (const allOf of schema2.allOf) {
630
- visit(allOf);
631
- }
632
- return;
633
- }
634
- if (schema2.oneOf) {
635
- for (const oneOf of schema2.oneOf) {
636
- visit(oneOf);
637
- }
638
- return;
639
- }
640
- if (schema2.anyOf) {
641
- for (const anyOf of schema2.anyOf) {
642
- visit(anyOf);
643
- }
644
- return;
645
- }
646
- if (schema2.type === "array") {
647
- if (!schema2.items)
648
- return;
649
- return void visit(schema2.items);
650
- }
651
- if (schema2.type === "object" || schema2.properties || schema2.additionalProperties) {
652
- if (schema2.properties) {
653
- for (const property in schema2.properties) {
654
- visit(schema2.properties[property]);
655
- }
656
- }
657
- if (schema2.additionalProperties && typeof schema2.additionalProperties === "object") {
658
- visit(schema2.additionalProperties);
659
- }
660
- }
661
- };
662
- visit(schema);
663
- };
664
- var getTransitiveDependencies = (directDependencies) => {
665
- const transitiveDependencies = /* @__PURE__ */ new Map();
666
- const visitedsDeepRefs = /* @__PURE__ */ new Set();
667
- directDependencies.forEach((deps, ref) => {
668
- if (!transitiveDependencies.has(ref)) {
669
- transitiveDependencies.set(ref, /* @__PURE__ */ new Set());
670
- }
671
- const visit = (depRef) => {
672
- transitiveDependencies.get(ref).add(depRef);
673
- const deps2 = directDependencies.get(depRef);
674
- if (deps2 && ref !== depRef) {
675
- deps2.forEach((transitive) => {
676
- const key = ref + "__" + transitive;
677
- if (visitedsDeepRefs.has(key))
678
- return;
679
- visitedsDeepRefs.add(key);
680
- visit(transitive);
681
- });
682
- }
683
- };
684
- deps.forEach((dep) => visit(dep));
685
- });
686
- return transitiveDependencies;
687
- };
688
-
689
- // src/ts-factory.ts
690
- var tsFactory = createFactory({
691
- union: (types) => types.map(unwrap).join(" | "),
692
- intersection: (types) => types.map(unwrap).join(" & "),
693
- array: (type3) => `Array<${unwrap(type3)}>`,
694
- optional: (type3) => `${unwrap(type3)} | undefined`,
695
- reference: (name2, typeArgs) => `${name2}${typeArgs ? `<${typeArgs.map(unwrap).join(", ")}>` : ""}`,
696
- literal: (value) => value.toString(),
697
- string: () => "string",
698
- number: () => "number",
699
- boolean: () => "boolean",
700
- unknown: () => "unknown",
701
- any: () => "any",
702
- never: () => "never",
703
- object: (props) => {
704
- const propsString = Object.entries(props).map(([prop, type3]) => `${wrapWithQuotesIfNeeded(prop)}: ${unwrap(type3)}`).join(", ");
705
- return `{ ${propsString} }`;
706
- }
707
- });
708
-
709
- // src/map-openapi-endpoints.ts
710
- var factory = tsFactory;
711
- var mapOpenApiEndpoints = (doc) => {
712
- const refs = createRefResolver(doc, factory);
713
- const ctx = { refs, factory };
714
- const endpointList = [];
715
- Object.entries(doc.paths ?? {}).forEach(([path, pathItemObj]) => {
716
- const pathItem = pick(pathItemObj, ["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
717
- Object.entries(pathItem).forEach(([method, operation]) => {
718
- if (operation.deprecated)
719
- return;
720
- const endpoint = {
721
- operation,
722
- method,
723
- path,
724
- response: openApiSchemaToTs({ schema: {}, ctx }),
725
- meta: {
726
- alias: getAlias({ path, method, operation }),
727
- areParametersRequired: false,
728
- hasParameters: false
729
- }
730
- };
731
- const lists = { query: [], path: [], header: [] };
732
- const paramObjects = (operation.parameters ?? []).reduce(
733
- (acc, paramOrRef) => {
734
- const param = refs.unwrap(paramOrRef);
735
- const schema = openApiSchemaToTs({ schema: refs.unwrap(param.schema ?? {}), ctx });
736
- lists.query.push(param);
737
- if (param.required)
738
- endpoint.meta.areParametersRequired = true;
739
- endpoint.meta.hasParameters = true;
740
- if (param.in === "query")
741
- acc.query[param.name] = schema;
742
- if (param.in === "path")
743
- acc.path[param.name] = schema;
744
- if (param.in === "header")
745
- acc.header[param.name] = schema;
746
- return acc;
747
- },
748
- { query: {}, path: {}, header: {} }
749
- );
750
- const params = Object.entries(paramObjects).reduce((acc, [key, value]) => {
751
- if (Object.keys(value).length) {
752
- acc[key] = value;
753
- }
754
- return acc;
755
- }, {});
756
- if (operation.requestBody) {
757
- const requestBody = refs.unwrap(operation.requestBody ?? {});
758
- const content2 = requestBody.content;
759
- const matchingMediaType = Object.keys(content2).find(isAllowedParamMediaTypes);
760
- if (matchingMediaType && content2[matchingMediaType]) {
761
- params.body = openApiSchemaToTs({
762
- schema: content2[matchingMediaType]?.schema ?? {} ?? {},
763
- ctx
764
- });
765
- }
766
- }
767
- if (params) {
768
- const t = createBoxFactory({}, ctx);
769
- if (params.query && lists.query.length && lists.query.every((param) => !param.required)) {
770
- if (!params.query)
771
- params.query = {};
772
- params.query = t.reference("Partial", [t.object(params.query)]);
773
- }
774
- if (params.path && lists.path.length && lists.path.every((param) => !param.required)) {
775
- params.path = t.reference("Partial", [t.object(params.path)]);
776
- }
777
- if (params.header && lists.header.length && lists.header.every((param) => !param.required)) {
778
- params.header = t.reference("Partial", [t.object(params.header)]);
779
- }
780
- endpoint.parameters = Object.keys(params).length ? params : void 0;
781
- }
782
- let responseObject;
783
- if (operation.responses.default) {
784
- responseObject = refs.unwrap(operation.responses.default);
785
- } else {
786
- Object.entries(operation.responses).map(([status, responseOrRef]) => {
787
- const statusCode = Number(status);
788
- if (statusCode >= 200 && statusCode < 300) {
789
- responseObject = refs.unwrap(responseOrRef);
790
- }
791
- });
792
- }
793
- const content = responseObject?.content;
794
- if (content) {
795
- const matchingMediaType = Object.keys(content).find(isResponseMediaType);
796
- if (matchingMediaType && content[matchingMediaType]) {
797
- endpoint.response = openApiSchemaToTs({
798
- schema: content[matchingMediaType]?.schema ?? {} ?? {},
799
- ctx
800
- });
801
- }
802
- }
803
- endpointList.push(endpoint);
804
- });
805
- });
806
- return { doc, refs, endpointList, factory };
807
- };
808
- var allowedParamMediaTypes = [
809
- "application/octet-stream",
810
- "multipart/form-data",
811
- "application/x-www-form-urlencoded",
812
- "*/*"
813
- ];
814
- var isAllowedParamMediaTypes = (mediaType) => mediaType.includes("application/") && mediaType.includes("json") || allowedParamMediaTypes.includes(mediaType) || mediaType.includes("text/");
815
- var isResponseMediaType = (mediaType) => mediaType === "application/json";
816
- var getAlias = ({ path, method, operation }) => method + "_" + capitalize3(operation.operationId ?? pathToVariableName(path));
16
+ var version = "0.1.4";
817
17
 
818
18
  // src/cli.ts
819
19
  var cwd = process.cwd();
820
20
  var cli = cac(name);
821
21
  var now = /* @__PURE__ */ new Date();
822
- var optionsSchema = type2({ "output?": "string", runtime: allowedRuntimes });
22
+ var optionsSchema = type({ "output?": "string", runtime: allowedRuntimes });
823
23
  cli.command("<input>", "Generate").option("-o, --output <path>", "Output path for the api client ts file (defaults to `<input>.<runtime>.ts`)").option(
824
24
  "-r, --runtime <name>",
825
25
  `Runtime to use for validation; defaults to \`none\`; available: ${allowedRuntimes.definition}`,