typed-openapi 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-N6BWPZUB.js → chunk-E6A7N4ND.js} +420 -67
- package/dist/chunk-KAEXXJ7X.js +21 -0
- package/dist/{chunk-RGFFCU3R.js → chunk-MCVYB63W.js} +25 -12
- package/dist/cli.js +8 -4
- package/dist/index.d.ts +12 -230
- package/dist/index.js +2 -1
- package/dist/node.export.d.ts +11 -5
- package/dist/node.export.js +4 -6
- package/dist/pretty.export.d.ts +5 -0
- package/dist/pretty.export.js +6 -0
- package/dist/types-DsI2d-HE.d.ts +244 -0
- package/package.json +8 -2
- package/src/cli.ts +8 -3
- package/src/generate-client-files.ts +43 -11
- package/src/generator.ts +187 -20
- package/src/map-openapi-endpoints.ts +61 -8
- package/src/node.export.ts +0 -1
- package/src/pretty.export.ts +1 -0
- package/src/ref-resolver.ts +12 -2
- package/src/sanitize-name.ts +79 -0
- package/src/tanstack-query.generator.ts +69 -25
- package/src/types.ts +14 -1
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
prettify
|
|
3
|
+
} from "./chunk-KAEXXJ7X.js";
|
|
4
|
+
|
|
1
5
|
// src/asserts.ts
|
|
2
6
|
var isPrimitiveType = (type2) => primitiveTypeList.includes(type2);
|
|
3
7
|
var primitiveTypeList = ["string", "number", "integer", "boolean", "null"];
|
|
@@ -278,6 +282,69 @@ import { capitalize as capitalize2, groupBy } from "pastable/server";
|
|
|
278
282
|
import * as Codegen from "@sinclair/typebox-codegen";
|
|
279
283
|
import { match } from "ts-pattern";
|
|
280
284
|
import { type } from "arktype";
|
|
285
|
+
var DEFAULT_SUCCESS_STATUS_CODES = [
|
|
286
|
+
200,
|
|
287
|
+
201,
|
|
288
|
+
202,
|
|
289
|
+
203,
|
|
290
|
+
204,
|
|
291
|
+
205,
|
|
292
|
+
206,
|
|
293
|
+
207,
|
|
294
|
+
208,
|
|
295
|
+
226,
|
|
296
|
+
300,
|
|
297
|
+
301,
|
|
298
|
+
302,
|
|
299
|
+
303,
|
|
300
|
+
304,
|
|
301
|
+
305,
|
|
302
|
+
306,
|
|
303
|
+
307,
|
|
304
|
+
308
|
|
305
|
+
];
|
|
306
|
+
var DEFAULT_ERROR_STATUS_CODES = [
|
|
307
|
+
400,
|
|
308
|
+
401,
|
|
309
|
+
402,
|
|
310
|
+
403,
|
|
311
|
+
404,
|
|
312
|
+
405,
|
|
313
|
+
406,
|
|
314
|
+
407,
|
|
315
|
+
408,
|
|
316
|
+
409,
|
|
317
|
+
410,
|
|
318
|
+
411,
|
|
319
|
+
412,
|
|
320
|
+
413,
|
|
321
|
+
414,
|
|
322
|
+
415,
|
|
323
|
+
416,
|
|
324
|
+
417,
|
|
325
|
+
418,
|
|
326
|
+
421,
|
|
327
|
+
422,
|
|
328
|
+
423,
|
|
329
|
+
424,
|
|
330
|
+
425,
|
|
331
|
+
426,
|
|
332
|
+
428,
|
|
333
|
+
429,
|
|
334
|
+
431,
|
|
335
|
+
451,
|
|
336
|
+
500,
|
|
337
|
+
501,
|
|
338
|
+
502,
|
|
339
|
+
503,
|
|
340
|
+
504,
|
|
341
|
+
505,
|
|
342
|
+
506,
|
|
343
|
+
507,
|
|
344
|
+
508,
|
|
345
|
+
510,
|
|
346
|
+
511
|
|
347
|
+
];
|
|
281
348
|
var allowedRuntimes = type("'none' | 'arktype' | 'io-ts' | 'typebox' | 'valibot' | 'yup' | 'zod'");
|
|
282
349
|
var runtimeValidationGenerator = {
|
|
283
350
|
arktype: Codegen.ModelToArkType.Generate,
|
|
@@ -310,10 +377,17 @@ var replacerByRuntime = {
|
|
|
310
377
|
)
|
|
311
378
|
};
|
|
312
379
|
var generateFile = (options) => {
|
|
313
|
-
const ctx = {
|
|
380
|
+
const ctx = {
|
|
381
|
+
...options,
|
|
382
|
+
runtime: options.runtime ?? "none",
|
|
383
|
+
successStatusCodes: options.successStatusCodes ?? DEFAULT_SUCCESS_STATUS_CODES,
|
|
384
|
+
errorStatusCodes: options.errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES,
|
|
385
|
+
includeClient: options.includeClient ?? true
|
|
386
|
+
};
|
|
314
387
|
const schemaList = generateSchemaList(ctx);
|
|
315
388
|
const endpointSchemaList = options.schemasOnly ? "" : generateEndpointSchemaList(ctx);
|
|
316
|
-
const
|
|
389
|
+
const endpointByMethod = options.schemasOnly ? "" : generateEndpointByMethod(ctx);
|
|
390
|
+
const apiClient = options.schemasOnly || !ctx.includeClient ? "" : generateApiClient(ctx);
|
|
317
391
|
const transform = ctx.runtime === "none" ? (file2) => file2 : (file2) => {
|
|
318
392
|
const model = Codegen.TypeScriptToModel.Generate(file2);
|
|
319
393
|
const transformer = runtimeValidationGenerator[ctx.runtime];
|
|
@@ -333,6 +407,7 @@ var generateFile = (options) => {
|
|
|
333
407
|
};
|
|
334
408
|
const file = `
|
|
335
409
|
${transform(schemaList + endpointSchemaList)}
|
|
410
|
+
${endpointByMethod}
|
|
336
411
|
${apiClient}
|
|
337
412
|
`;
|
|
338
413
|
return file;
|
|
@@ -376,6 +451,20 @@ var responseHeadersObjectToString = (responseHeaders, ctx) => {
|
|
|
376
451
|
}
|
|
377
452
|
return str + "}";
|
|
378
453
|
};
|
|
454
|
+
var generateResponsesObject = (responses, ctx) => {
|
|
455
|
+
let str = "{";
|
|
456
|
+
for (const [statusCode, responseType] of Object.entries(responses)) {
|
|
457
|
+
const value = ctx.runtime === "none" ? responseType.recompute((box) => {
|
|
458
|
+
if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
|
|
459
|
+
box.value = `Schemas.${box.value}`;
|
|
460
|
+
}
|
|
461
|
+
return box;
|
|
462
|
+
}).value : responseType.value;
|
|
463
|
+
str += `${wrapWithQuotesIfNeeded(statusCode)}: ${value},
|
|
464
|
+
`;
|
|
465
|
+
}
|
|
466
|
+
return str + "}";
|
|
467
|
+
};
|
|
379
468
|
var generateEndpointSchemaList = (ctx) => {
|
|
380
469
|
let file = `
|
|
381
470
|
${ctx.runtime === "none" ? "export namespace Endpoints {" : ""}
|
|
@@ -407,6 +496,7 @@ var generateEndpointSchemaList = (ctx) => {
|
|
|
407
496
|
}
|
|
408
497
|
return box;
|
|
409
498
|
}).value : endpoint.response.value},
|
|
499
|
+
${endpoint.responses ? `responses: ${generateResponsesObject(endpoint.responses, ctx)},` : ""}
|
|
410
500
|
${endpoint.responseHeaders ? `responseHeaders: ${responseHeadersObjectToString(endpoint.responseHeaders, ctx)},` : ""}
|
|
411
501
|
}
|
|
412
502
|
`;
|
|
@@ -427,7 +517,7 @@ var generateEndpointByMethod = (ctx) => {
|
|
|
427
517
|
return `${method}: {
|
|
428
518
|
${list.map(
|
|
429
519
|
(endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`
|
|
430
|
-
)}
|
|
520
|
+
).join(",\n")}
|
|
431
521
|
}`;
|
|
432
522
|
}).join(",\n")}
|
|
433
523
|
}
|
|
@@ -443,9 +533,11 @@ var generateEndpointByMethod = (ctx) => {
|
|
|
443
533
|
return endpointByMethod + shorthands;
|
|
444
534
|
};
|
|
445
535
|
var generateApiClient = (ctx) => {
|
|
536
|
+
if (!ctx.includeClient) {
|
|
537
|
+
return "";
|
|
538
|
+
}
|
|
446
539
|
const { endpointList } = ctx;
|
|
447
540
|
const byMethods = groupBy(endpointList, "method");
|
|
448
|
-
const endpointSchemaList = generateEndpointByMethod(ctx);
|
|
449
541
|
const apiClientTypes = `
|
|
450
542
|
// <ApiClientTypes>
|
|
451
543
|
export type EndpointParameters = {
|
|
@@ -463,6 +555,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text";
|
|
|
463
555
|
export type DefaultEndpoint = {
|
|
464
556
|
parameters?: EndpointParameters | undefined;
|
|
465
557
|
response: unknown;
|
|
558
|
+
responses?: Record<string, unknown>;
|
|
466
559
|
responseHeaders?: Record<string, unknown>;
|
|
467
560
|
};
|
|
468
561
|
|
|
@@ -478,11 +571,64 @@ export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
|
|
|
478
571
|
areParametersRequired: boolean;
|
|
479
572
|
};
|
|
480
573
|
response: TConfig["response"];
|
|
574
|
+
responses?: TConfig["responses"];
|
|
481
575
|
responseHeaders?: TConfig["responseHeaders"]
|
|
482
576
|
};
|
|
483
577
|
|
|
484
578
|
export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Response>;
|
|
485
579
|
|
|
580
|
+
export const successStatusCodes = [${ctx.successStatusCodes.join(",")}] as const;
|
|
581
|
+
export type SuccessStatusCode = typeof successStatusCodes[number];
|
|
582
|
+
|
|
583
|
+
export const errorStatusCodes = [${ctx.errorStatusCodes.join(",")}] as const;
|
|
584
|
+
export type ErrorStatusCode = typeof errorStatusCodes[number];
|
|
585
|
+
|
|
586
|
+
// Error handling types
|
|
587
|
+
/** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
|
|
588
|
+
interface SuccessResponse<TSuccess, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
|
|
589
|
+
ok: true;
|
|
590
|
+
status: TStatusCode;
|
|
591
|
+
data: TSuccess;
|
|
592
|
+
/** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
|
|
593
|
+
json: () => Promise<TSuccess>;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
|
|
597
|
+
interface ErrorResponse<TData, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
|
|
598
|
+
ok: false;
|
|
599
|
+
status: TStatusCode;
|
|
600
|
+
data: TData;
|
|
601
|
+
/** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
|
|
602
|
+
json: () => Promise<TData>;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export type TypedApiResponse<TSuccess, TAllResponses extends Record<string | number, unknown> = {}> =
|
|
606
|
+
(keyof TAllResponses extends never
|
|
607
|
+
? SuccessResponse<TSuccess, number>
|
|
608
|
+
: {
|
|
609
|
+
[K in keyof TAllResponses]: K extends string
|
|
610
|
+
? K extends \`\${infer TStatusCode extends number}\`
|
|
611
|
+
? TStatusCode extends SuccessStatusCode
|
|
612
|
+
? SuccessResponse<TSuccess, TStatusCode>
|
|
613
|
+
: ErrorResponse<TAllResponses[K], TStatusCode>
|
|
614
|
+
: never
|
|
615
|
+
: K extends number
|
|
616
|
+
? K extends SuccessStatusCode
|
|
617
|
+
? SuccessResponse<TSuccess, K>
|
|
618
|
+
: ErrorResponse<TAllResponses[K], K>
|
|
619
|
+
: never;
|
|
620
|
+
}[keyof TAllResponses]);
|
|
621
|
+
|
|
622
|
+
export type SafeApiResponse<TEndpoint> = TEndpoint extends { response: infer TSuccess; responses: infer TResponses }
|
|
623
|
+
? TResponses extends Record<string, unknown>
|
|
624
|
+
? TypedApiResponse<TSuccess, TResponses>
|
|
625
|
+
: SuccessResponse<TSuccess, number>
|
|
626
|
+
: TEndpoint extends { response: infer TSuccess }
|
|
627
|
+
? SuccessResponse<TSuccess, number>
|
|
628
|
+
: never;
|
|
629
|
+
|
|
630
|
+
export type InferResponseByStatus<TEndpoint, TStatusCode> = Extract<SafeApiResponse<TEndpoint>, { status: TStatusCode }>
|
|
631
|
+
|
|
486
632
|
type RequiredKeys<T> = {
|
|
487
633
|
[P in keyof T]-?: undefined extends T[P] ? never : P;
|
|
488
634
|
}[keyof T];
|
|
@@ -492,9 +638,23 @@ type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [confi
|
|
|
492
638
|
// </ApiClientTypes>
|
|
493
639
|
`;
|
|
494
640
|
const apiClient = `
|
|
641
|
+
// <TypedResponseError>
|
|
642
|
+
export class TypedResponseError extends Error {
|
|
643
|
+
response: ErrorResponse<unknown, ErrorStatusCode>;
|
|
644
|
+
status: number;
|
|
645
|
+
constructor(response: ErrorResponse<unknown, ErrorStatusCode>) {
|
|
646
|
+
super(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
647
|
+
this.name = 'TypedResponseError';
|
|
648
|
+
this.response = response;
|
|
649
|
+
this.status = response.status;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// </TypedResponseError>
|
|
495
653
|
// <ApiClient>
|
|
496
654
|
export class ApiClient {
|
|
497
655
|
baseUrl: string = "";
|
|
656
|
+
successStatusCodes = successStatusCodes;
|
|
657
|
+
errorStatusCodes = errorStatusCodes;
|
|
498
658
|
|
|
499
659
|
constructor(public fetcher: Fetcher) {}
|
|
500
660
|
|
|
@@ -517,10 +677,44 @@ export class ApiClient {
|
|
|
517
677
|
return endpointByMethod.length ? `// <ApiClient.${method}>
|
|
518
678
|
${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
|
|
519
679
|
path: Path,
|
|
520
|
-
...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)}>
|
|
521
|
-
): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}
|
|
522
|
-
|
|
523
|
-
|
|
680
|
+
...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)} & { withResponse?: false; throwOnStatusError?: boolean }>
|
|
681
|
+
): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}>;
|
|
682
|
+
|
|
683
|
+
${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
|
|
684
|
+
path: Path,
|
|
685
|
+
...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)} & { withResponse: true; throwOnStatusError?: boolean }>
|
|
686
|
+
): Promise<SafeApiResponse<TEndpoint>>;
|
|
687
|
+
|
|
688
|
+
${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
|
|
689
|
+
path: Path,
|
|
690
|
+
...params: MaybeOptionalArg<any>
|
|
691
|
+
): Promise<any> {
|
|
692
|
+
const requestParams = params[0];
|
|
693
|
+
const withResponse = requestParams?.withResponse;
|
|
694
|
+
const { withResponse: _, throwOnStatusError = withResponse ? false : true, ...fetchParams } = requestParams || {};
|
|
695
|
+
|
|
696
|
+
const promise = this.fetcher("${method}", this.baseUrl + path, Object.keys(fetchParams).length ? requestParams : undefined)
|
|
697
|
+
.then(async (response) => {
|
|
698
|
+
const data = await this.parseResponse(response);
|
|
699
|
+
const typedResponse = Object.assign(response, {
|
|
700
|
+
data: data,
|
|
701
|
+
json: () => Promise.resolve(data)
|
|
702
|
+
}) as SafeApiResponse<TEndpoint>;
|
|
703
|
+
|
|
704
|
+
if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
|
|
705
|
+
throw new TypedResponseError(typedResponse as never);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return withResponse ? typedResponse : data;
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
return promise ${match(ctx.runtime).with("zod", "yup", () => `as Promise<${infer(`TEndpoint["response"]`)}>`).with(
|
|
712
|
+
"arktype",
|
|
713
|
+
"io-ts",
|
|
714
|
+
"typebox",
|
|
715
|
+
"valibot",
|
|
716
|
+
() => `as Promise<${infer(`TEndpoint`) + `["response"]`}>`
|
|
717
|
+
).otherwise(() => `as Promise<TEndpoint["response"]>`)}
|
|
524
718
|
}
|
|
525
719
|
// </ApiClient.${method}>
|
|
526
720
|
` : "";
|
|
@@ -548,11 +742,8 @@ export class ApiClient {
|
|
|
548
742
|
"valibot",
|
|
549
743
|
() => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`
|
|
550
744
|
).otherwise(() => `TEndpoint extends { parameters: infer Params } ? Params : never`)}>)
|
|
551
|
-
: Promise<
|
|
552
|
-
|
|
553
|
-
json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
|
|
554
|
-
}> {
|
|
555
|
-
return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters);
|
|
745
|
+
: Promise<SafeApiResponse<TEndpoint>> {
|
|
746
|
+
return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters) as Promise<SafeApiResponse<TEndpoint>>;
|
|
556
747
|
}
|
|
557
748
|
// </ApiClient.request>
|
|
558
749
|
}
|
|
@@ -570,11 +761,26 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
|
|
|
570
761
|
api.get("/users").then((users) => console.log(users));
|
|
571
762
|
api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
|
|
572
763
|
api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
|
|
764
|
+
|
|
765
|
+
// With error handling
|
|
766
|
+
const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true });
|
|
767
|
+
if (result.ok) {
|
|
768
|
+
// Access data directly
|
|
769
|
+
const user = result.data;
|
|
770
|
+
console.log(user);
|
|
771
|
+
|
|
772
|
+
// Or use the json() method for compatibility
|
|
773
|
+
const userFromJson = await result.json();
|
|
774
|
+
console.log(userFromJson);
|
|
775
|
+
} else {
|
|
776
|
+
const error = result.data;
|
|
777
|
+
console.error(\`Error \${result.status}:\`, error);
|
|
778
|
+
}
|
|
573
779
|
*/
|
|
574
780
|
|
|
575
|
-
// </ApiClient
|
|
781
|
+
// </ApiClient>
|
|
576
782
|
`;
|
|
577
|
-
return
|
|
783
|
+
return apiClientTypes + apiClient;
|
|
578
784
|
};
|
|
579
785
|
|
|
580
786
|
// src/ref-resolver.ts
|
|
@@ -603,10 +809,90 @@ function topologicalSort(graph) {
|
|
|
603
809
|
return sorted;
|
|
604
810
|
}
|
|
605
811
|
|
|
812
|
+
// src/sanitize-name.ts
|
|
813
|
+
var reservedWords = /* @__PURE__ */ new Set([
|
|
814
|
+
// TS keywords and built-ins
|
|
815
|
+
"import",
|
|
816
|
+
"package",
|
|
817
|
+
"namespace",
|
|
818
|
+
"Record",
|
|
819
|
+
"Partial",
|
|
820
|
+
"Required",
|
|
821
|
+
"Readonly",
|
|
822
|
+
"Pick",
|
|
823
|
+
"Omit",
|
|
824
|
+
"String",
|
|
825
|
+
"Number",
|
|
826
|
+
"Boolean",
|
|
827
|
+
"Object",
|
|
828
|
+
"Array",
|
|
829
|
+
"Function",
|
|
830
|
+
"any",
|
|
831
|
+
"unknown",
|
|
832
|
+
"never",
|
|
833
|
+
"void",
|
|
834
|
+
"extends",
|
|
835
|
+
"super",
|
|
836
|
+
"class",
|
|
837
|
+
"interface",
|
|
838
|
+
"type",
|
|
839
|
+
"enum",
|
|
840
|
+
"const",
|
|
841
|
+
"let",
|
|
842
|
+
"var",
|
|
843
|
+
"if",
|
|
844
|
+
"else",
|
|
845
|
+
"for",
|
|
846
|
+
"while",
|
|
847
|
+
"do",
|
|
848
|
+
"switch",
|
|
849
|
+
"case",
|
|
850
|
+
"default",
|
|
851
|
+
"break",
|
|
852
|
+
"continue",
|
|
853
|
+
"return",
|
|
854
|
+
"try",
|
|
855
|
+
"catch",
|
|
856
|
+
"finally",
|
|
857
|
+
"throw",
|
|
858
|
+
"new",
|
|
859
|
+
"delete",
|
|
860
|
+
"in",
|
|
861
|
+
"instanceof",
|
|
862
|
+
"typeof",
|
|
863
|
+
"void",
|
|
864
|
+
"with",
|
|
865
|
+
"yield",
|
|
866
|
+
"await",
|
|
867
|
+
"static",
|
|
868
|
+
"public",
|
|
869
|
+
"private",
|
|
870
|
+
"protected",
|
|
871
|
+
"abstract",
|
|
872
|
+
"as",
|
|
873
|
+
"asserts",
|
|
874
|
+
"from",
|
|
875
|
+
"get",
|
|
876
|
+
"set",
|
|
877
|
+
"module",
|
|
878
|
+
"require",
|
|
879
|
+
"keyof",
|
|
880
|
+
"readonly",
|
|
881
|
+
"global",
|
|
882
|
+
"symbol",
|
|
883
|
+
"bigint"
|
|
884
|
+
]);
|
|
885
|
+
function sanitizeName(name, type2) {
|
|
886
|
+
let n = name.replace(/[\W/]+/g, "_");
|
|
887
|
+
if (/^\d/.test(n)) n = "_" + n;
|
|
888
|
+
if (reservedWords.has(n)) n = (type2 === "schema" ? "Schema_" : "Endpoint_") + n;
|
|
889
|
+
return n;
|
|
890
|
+
}
|
|
891
|
+
|
|
606
892
|
// src/ref-resolver.ts
|
|
607
893
|
var autocorrectRef = (ref) => ref[1] === "/" ? ref : "#/" + ref.slice(1);
|
|
608
894
|
var componentsWithSchemas = ["schemas", "responses", "parameters", "requestBodies", "headers"];
|
|
609
|
-
var createRefResolver = (doc, factory2) => {
|
|
895
|
+
var createRefResolver = (doc, factory2, nameTransform) => {
|
|
610
896
|
const nameByRef = /* @__PURE__ */ new Map();
|
|
611
897
|
const refByName = /* @__PURE__ */ new Map();
|
|
612
898
|
const byRef = /* @__PURE__ */ new Map();
|
|
@@ -619,7 +905,11 @@ var createRefResolver = (doc, factory2) => {
|
|
|
619
905
|
const normalizedPath = path.replace("#/", "").replace("#", "").replaceAll("/", ".");
|
|
620
906
|
const map = get(doc, normalizedPath) ?? {};
|
|
621
907
|
const name = split[split.length - 1];
|
|
622
|
-
|
|
908
|
+
let normalized = normalizeString(name);
|
|
909
|
+
if (nameTransform?.transformSchemaName) {
|
|
910
|
+
normalized = nameTransform.transformSchemaName(normalized);
|
|
911
|
+
}
|
|
912
|
+
normalized = sanitizeName(normalized, "schema");
|
|
623
913
|
nameByRef.set(correctRef, normalized);
|
|
624
914
|
refByName.set(normalized, correctRef);
|
|
625
915
|
const infos = { ref: correctRef, name, normalized, kind: normalizedPath.split(".")[1] };
|
|
@@ -768,7 +1058,7 @@ var tsFactory = createFactory({
|
|
|
768
1058
|
import { capitalize as capitalize3, pick } from "pastable/server";
|
|
769
1059
|
import { match as match2, P } from "ts-pattern";
|
|
770
1060
|
var factory = tsFactory;
|
|
771
|
-
var mapOpenApiEndpoints = (doc) => {
|
|
1061
|
+
var mapOpenApiEndpoints = (doc, options) => {
|
|
772
1062
|
const refs = createRefResolver(doc, factory);
|
|
773
1063
|
const ctx = { refs, factory };
|
|
774
1064
|
const endpointList = [];
|
|
@@ -776,6 +1066,10 @@ var mapOpenApiEndpoints = (doc) => {
|
|
|
776
1066
|
const pathItem = pick(pathItemObj, ["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
777
1067
|
Object.entries(pathItem).forEach(([method, operation]) => {
|
|
778
1068
|
if (operation.deprecated) return;
|
|
1069
|
+
let alias = getAlias({ path, method, operation });
|
|
1070
|
+
if (options?.nameTransform?.transformEndpointName) {
|
|
1071
|
+
alias = options.nameTransform.transformEndpointName({ alias, path, method, operation });
|
|
1072
|
+
}
|
|
779
1073
|
const endpoint = {
|
|
780
1074
|
operation,
|
|
781
1075
|
method,
|
|
@@ -783,7 +1077,7 @@ var mapOpenApiEndpoints = (doc) => {
|
|
|
783
1077
|
requestFormat: "json",
|
|
784
1078
|
response: openApiSchemaToTs({ schema: {}, ctx }),
|
|
785
1079
|
meta: {
|
|
786
|
-
alias
|
|
1080
|
+
alias,
|
|
787
1081
|
areParametersRequired: false,
|
|
788
1082
|
hasParameters: false
|
|
789
1083
|
}
|
|
@@ -827,7 +1121,7 @@ var mapOpenApiEndpoints = (doc) => {
|
|
|
827
1121
|
const matchingMediaType = Object.keys(content2).find(isAllowedParamMediaTypes);
|
|
828
1122
|
if (matchingMediaType && content2[matchingMediaType]) {
|
|
829
1123
|
params.body = openApiSchemaToTs({
|
|
830
|
-
schema: content2[matchingMediaType]?.schema ?? {}
|
|
1124
|
+
schema: content2[matchingMediaType]?.schema ?? {},
|
|
831
1125
|
ctx
|
|
832
1126
|
});
|
|
833
1127
|
}
|
|
@@ -852,21 +1146,52 @@ var mapOpenApiEndpoints = (doc) => {
|
|
|
852
1146
|
endpoint.parameters = Object.keys(params).length ? params : void 0;
|
|
853
1147
|
}
|
|
854
1148
|
let responseObject;
|
|
1149
|
+
const allResponses = {};
|
|
855
1150
|
Object.entries(operation.responses ?? {}).map(([status, responseOrRef]) => {
|
|
856
1151
|
const statusCode = Number(status);
|
|
857
|
-
|
|
858
|
-
|
|
1152
|
+
const responseObj = refs.unwrap(responseOrRef);
|
|
1153
|
+
const content2 = responseObj?.content;
|
|
1154
|
+
if (content2) {
|
|
1155
|
+
const matchingMediaType = Object.keys(content2).find(isResponseMediaType);
|
|
1156
|
+
if (matchingMediaType && content2[matchingMediaType]) {
|
|
1157
|
+
allResponses[status] = openApiSchemaToTs({
|
|
1158
|
+
schema: content2[matchingMediaType]?.schema ?? {},
|
|
1159
|
+
ctx
|
|
1160
|
+
});
|
|
1161
|
+
} else {
|
|
1162
|
+
allResponses[status] = openApiSchemaToTs({ schema: {}, ctx });
|
|
1163
|
+
}
|
|
1164
|
+
} else {
|
|
1165
|
+
allResponses[status] = openApiSchemaToTs({ schema: {}, ctx });
|
|
1166
|
+
}
|
|
1167
|
+
if (statusCode >= 200 && statusCode < 300 && !responseObject) {
|
|
1168
|
+
responseObject = responseObj;
|
|
859
1169
|
}
|
|
860
1170
|
});
|
|
861
1171
|
if (!responseObject && operation.responses?.default) {
|
|
862
1172
|
responseObject = refs.unwrap(operation.responses.default);
|
|
1173
|
+
if (!allResponses["default"]) {
|
|
1174
|
+
const content2 = responseObject?.content;
|
|
1175
|
+
if (content2) {
|
|
1176
|
+
const matchingMediaType = Object.keys(content2).find(isResponseMediaType);
|
|
1177
|
+
if (matchingMediaType && content2[matchingMediaType]) {
|
|
1178
|
+
allResponses["default"] = openApiSchemaToTs({
|
|
1179
|
+
schema: content2[matchingMediaType]?.schema ?? {},
|
|
1180
|
+
ctx
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (Object.keys(allResponses).length > 0) {
|
|
1187
|
+
endpoint.responses = allResponses;
|
|
863
1188
|
}
|
|
864
1189
|
const content = responseObject?.content;
|
|
865
1190
|
if (content) {
|
|
866
1191
|
const matchingMediaType = Object.keys(content).find(isResponseMediaType);
|
|
867
1192
|
if (matchingMediaType && content[matchingMediaType]) {
|
|
868
1193
|
endpoint.response = openApiSchemaToTs({
|
|
869
|
-
schema: content[matchingMediaType]?.schema ?? {}
|
|
1194
|
+
schema: content[matchingMediaType]?.schema ?? {},
|
|
870
1195
|
ctx
|
|
871
1196
|
});
|
|
872
1197
|
}
|
|
@@ -895,25 +1220,10 @@ var allowedParamMediaTypes = [
|
|
|
895
1220
|
];
|
|
896
1221
|
var isAllowedParamMediaTypes = (mediaType) => mediaType.includes("application/") && mediaType.includes("json") || allowedParamMediaTypes.includes(mediaType) || mediaType.includes("text/");
|
|
897
1222
|
var isResponseMediaType = (mediaType) => mediaType === "application/json";
|
|
898
|
-
var getAlias = ({ path, method, operation }) => (
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
import parserTypescript from "prettier/parser-typescript";
|
|
903
|
-
function maybePretty(input, options) {
|
|
904
|
-
try {
|
|
905
|
-
return prettier.format(input, {
|
|
906
|
-
parser: "typescript",
|
|
907
|
-
plugins: [parserTypescript],
|
|
908
|
-
...options
|
|
909
|
-
});
|
|
910
|
-
} catch (err) {
|
|
911
|
-
console.warn("Failed to format code");
|
|
912
|
-
console.warn(err);
|
|
913
|
-
return input;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
var prettify = (str, options) => maybePretty(str, { printWidth: 120, trailingComma: "all", ...options });
|
|
1223
|
+
var getAlias = ({ path, method, operation }) => sanitizeName(
|
|
1224
|
+
(method + "_" + capitalize3(operation.operationId ?? pathToVariableName(path))).replace(/-/g, "__"),
|
|
1225
|
+
"endpoint"
|
|
1226
|
+
);
|
|
917
1227
|
|
|
918
1228
|
// src/tanstack-query.generator.ts
|
|
919
1229
|
import { capitalize as capitalize4 } from "pastable/server";
|
|
@@ -921,7 +1231,8 @@ var generateTanstackQueryFile = async (ctx) => {
|
|
|
921
1231
|
const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase()));
|
|
922
1232
|
const file = `
|
|
923
1233
|
import { queryOptions } from "@tanstack/react-query"
|
|
924
|
-
import type { EndpointByMethod, ApiClient } from "${ctx.relativeApiClientPath}"
|
|
1234
|
+
import type { EndpointByMethod, ApiClient, SuccessStatusCode, ErrorStatusCode, InferResponseByStatus } from "${ctx.relativeApiClientPath}"
|
|
1235
|
+
import { errorStatusCodes, TypedResponseError } from "${ctx.relativeApiClientPath}"
|
|
925
1236
|
|
|
926
1237
|
type EndpointQueryKey<TOptions extends EndpointParameters> = [
|
|
927
1238
|
TOptions & {
|
|
@@ -985,30 +1296,36 @@ var generateTanstackQueryFile = async (ctx) => {
|
|
|
985
1296
|
path: Path,
|
|
986
1297
|
...params: MaybeOptionalArg<TEndpoint["parameters"]>
|
|
987
1298
|
) {
|
|
988
|
-
const queryKey = createQueryKey(path, params[0]);
|
|
1299
|
+
const queryKey = createQueryKey(path as string, params[0]);
|
|
989
1300
|
const query = {
|
|
990
1301
|
/** type-only property if you need easy access to the endpoint params */
|
|
991
1302
|
"~endpoint": {} as TEndpoint,
|
|
992
1303
|
queryKey,
|
|
1304
|
+
queryFn: {} as "You need to pass .queryOptions to the useQuery hook",
|
|
993
1305
|
queryOptions: queryOptions({
|
|
994
1306
|
queryFn: async ({ queryKey, signal, }) => {
|
|
995
|
-
const
|
|
996
|
-
...params,
|
|
997
|
-
...queryKey[0],
|
|
1307
|
+
const requestParams = {
|
|
1308
|
+
...(params[0] || {}),
|
|
1309
|
+
...(queryKey[0] || {}),
|
|
998
1310
|
signal,
|
|
999
|
-
|
|
1311
|
+
withResponse: false as const
|
|
1312
|
+
};
|
|
1313
|
+
const res = await this.client.${method}(path, requestParams);
|
|
1000
1314
|
return res as TEndpoint["response"];
|
|
1001
1315
|
},
|
|
1002
1316
|
queryKey: queryKey
|
|
1003
1317
|
}),
|
|
1318
|
+
mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
|
|
1004
1319
|
mutationOptions: {
|
|
1005
1320
|
mutationKey: queryKey,
|
|
1006
1321
|
mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters} ? Parameters: never) => {
|
|
1007
|
-
const
|
|
1008
|
-
...params,
|
|
1009
|
-
...queryKey[0],
|
|
1010
|
-
...localOptions,
|
|
1011
|
-
|
|
1322
|
+
const requestParams = {
|
|
1323
|
+
...(params[0] || {}),
|
|
1324
|
+
...(queryKey[0] || {}),
|
|
1325
|
+
...(localOptions || {}),
|
|
1326
|
+
withResponse: false as const
|
|
1327
|
+
};
|
|
1328
|
+
const res = await this.client.${method}(path, requestParams);
|
|
1012
1329
|
return res as TEndpoint["response"];
|
|
1013
1330
|
}
|
|
1014
1331
|
}
|
|
@@ -1022,32 +1339,67 @@ var generateTanstackQueryFile = async (ctx) => {
|
|
|
1022
1339
|
|
|
1023
1340
|
// <ApiClient.request>
|
|
1024
1341
|
/**
|
|
1025
|
-
* Generic mutation method with full type-safety for any endpoint
|
|
1342
|
+
* Generic mutation method with full type-safety for any endpoint; it doesnt require parameters to be passed initially
|
|
1343
|
+
* but instead will require them to be passed when calling the mutation.mutate() method
|
|
1026
1344
|
*/
|
|
1027
1345
|
mutation<
|
|
1028
1346
|
TMethod extends keyof EndpointByMethod,
|
|
1029
1347
|
TPath extends keyof EndpointByMethod[TMethod],
|
|
1030
1348
|
TEndpoint extends EndpointByMethod[TMethod][TPath],
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1349
|
+
TWithResponse extends boolean = false,
|
|
1350
|
+
TSelection = TWithResponse extends true
|
|
1351
|
+
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
|
|
1352
|
+
: TEndpoint extends { response: infer Res } ? Res : never,
|
|
1353
|
+
TError = TEndpoint extends { responses: infer TResponses }
|
|
1354
|
+
? TResponses extends Record<string | number, unknown>
|
|
1355
|
+
? InferResponseByStatus<TEndpoint, ErrorStatusCode>
|
|
1356
|
+
: Error
|
|
1357
|
+
: Error
|
|
1358
|
+
>(method: TMethod, path: TPath, options?: {
|
|
1359
|
+
withResponse?: TWithResponse;
|
|
1360
|
+
selectFn?: (res: TWithResponse extends true
|
|
1361
|
+
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
|
|
1362
|
+
: TEndpoint extends { response: infer Res } ? Res : never
|
|
1363
|
+
) => TSelection;
|
|
1364
|
+
throwOnStatusError?: boolean
|
|
1365
|
+
}) {
|
|
1036
1366
|
const mutationKey = [{ method, path }] as const;
|
|
1037
1367
|
return {
|
|
1038
1368
|
/** type-only property if you need easy access to the endpoint params */
|
|
1039
1369
|
"~endpoint": {} as TEndpoint,
|
|
1040
1370
|
mutationKey: mutationKey,
|
|
1371
|
+
mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
|
|
1041
1372
|
mutationOptions: {
|
|
1042
1373
|
mutationKey: mutationKey,
|
|
1043
|
-
mutationFn: async
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1374
|
+
mutationFn: async <TLocalWithResponse extends boolean = TWithResponse, TLocalSelection = TLocalWithResponse extends true
|
|
1375
|
+
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
|
|
1376
|
+
: TEndpoint extends { response: infer Res }
|
|
1377
|
+
? Res
|
|
1378
|
+
: never>
|
|
1379
|
+
(params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
|
|
1380
|
+
withResponse?: TLocalWithResponse;
|
|
1381
|
+
throwOnStatusError?: boolean;
|
|
1382
|
+
}): Promise<TLocalSelection> => {
|
|
1383
|
+
const withResponse = params.withResponse ??options?.withResponse ?? false;
|
|
1384
|
+
const throwOnStatusError = params.throwOnStatusError ?? options?.throwOnStatusError ?? (withResponse ? false : true);
|
|
1385
|
+
const selectFn = options?.selectFn;
|
|
1386
|
+
const response = await (this.client as any)[method](path, { ...params as any, withResponse: true, throwOnStatusError: false });
|
|
1387
|
+
|
|
1388
|
+
if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
|
|
1389
|
+
throw new TypedResponseError(response as never);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// Return just the data if withResponse is false, otherwise return the full response
|
|
1393
|
+
const finalResponse = withResponse ? response : response.data;
|
|
1394
|
+
const res = selectFn ? selectFn(finalResponse as any) : finalResponse;
|
|
1395
|
+
return res as never;
|
|
1396
|
+
}
|
|
1397
|
+
} satisfies import("@tanstack/react-query").UseMutationOptions<TSelection, TError, (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
|
|
1398
|
+
withResponse?: boolean;
|
|
1399
|
+
throwOnStatusError?: boolean;
|
|
1400
|
+
}>,
|
|
1050
1401
|
}
|
|
1402
|
+
}
|
|
1051
1403
|
// </ApiClient.request>
|
|
1052
1404
|
}
|
|
1053
1405
|
`;
|
|
@@ -1059,11 +1411,12 @@ export {
|
|
|
1059
1411
|
createFactory,
|
|
1060
1412
|
createBoxFactory,
|
|
1061
1413
|
openApiSchemaToTs,
|
|
1414
|
+
DEFAULT_SUCCESS_STATUS_CODES,
|
|
1415
|
+
DEFAULT_ERROR_STATUS_CODES,
|
|
1062
1416
|
allowedRuntimes,
|
|
1063
1417
|
generateFile,
|
|
1064
1418
|
createRefResolver,
|
|
1065
1419
|
tsFactory,
|
|
1066
1420
|
mapOpenApiEndpoints,
|
|
1067
|
-
prettify,
|
|
1068
1421
|
generateTanstackQueryFile
|
|
1069
1422
|
};
|