typed-openapi 1.5.1 → 2.0.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/dist/{chunk-OVT6OLBK.js → chunk-4EZJSCLI.js} +341 -50
- package/dist/chunk-GD55PFNE.js +208 -0
- package/dist/cli.js +11 -5
- package/dist/index.d.ts +8 -3
- package/dist/index.js +1 -1
- package/dist/node.export.d.ts +10 -1
- package/dist/node.export.js +2 -2
- package/dist/{types-DLE5RaXi.d.ts → types-DsI2d-HE.d.ts} +2 -0
- package/package.json +6 -1
- package/src/cli.ts +13 -4
- package/src/default-fetcher.generator.ts +101 -0
- package/src/generate-client-files.ts +75 -15
- package/src/generator.ts +197 -26
- package/src/map-openapi-endpoints.ts +55 -4
- package/src/openapi-schema-to-ts.ts +3 -2
- package/src/tanstack-query.generator.ts +69 -25
- package/dist/chunk-O7DZWQK4.js +0 -68
|
@@ -150,7 +150,9 @@ var openApiSchemaToTs = ({ schema, meta: _inheritedMeta, ctx }) => {
|
|
|
150
150
|
meta
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
|
-
additionalProperties = t.
|
|
153
|
+
additionalProperties = t.literal(
|
|
154
|
+
`Record<string, ${additionalPropertiesType ? additionalPropertiesType.value : t.any().value}>`
|
|
155
|
+
);
|
|
154
156
|
}
|
|
155
157
|
const hasRequiredArray = schema.required && schema.required.length > 0;
|
|
156
158
|
const isPartial = !schema.required?.length;
|
|
@@ -282,6 +284,69 @@ import { capitalize as capitalize2, groupBy } from "pastable/server";
|
|
|
282
284
|
import * as Codegen from "@sinclair/typebox-codegen";
|
|
283
285
|
import { match } from "ts-pattern";
|
|
284
286
|
import { type } from "arktype";
|
|
287
|
+
var DEFAULT_SUCCESS_STATUS_CODES = [
|
|
288
|
+
200,
|
|
289
|
+
201,
|
|
290
|
+
202,
|
|
291
|
+
203,
|
|
292
|
+
204,
|
|
293
|
+
205,
|
|
294
|
+
206,
|
|
295
|
+
207,
|
|
296
|
+
208,
|
|
297
|
+
226,
|
|
298
|
+
300,
|
|
299
|
+
301,
|
|
300
|
+
302,
|
|
301
|
+
303,
|
|
302
|
+
304,
|
|
303
|
+
305,
|
|
304
|
+
306,
|
|
305
|
+
307,
|
|
306
|
+
308
|
|
307
|
+
];
|
|
308
|
+
var DEFAULT_ERROR_STATUS_CODES = [
|
|
309
|
+
400,
|
|
310
|
+
401,
|
|
311
|
+
402,
|
|
312
|
+
403,
|
|
313
|
+
404,
|
|
314
|
+
405,
|
|
315
|
+
406,
|
|
316
|
+
407,
|
|
317
|
+
408,
|
|
318
|
+
409,
|
|
319
|
+
410,
|
|
320
|
+
411,
|
|
321
|
+
412,
|
|
322
|
+
413,
|
|
323
|
+
414,
|
|
324
|
+
415,
|
|
325
|
+
416,
|
|
326
|
+
417,
|
|
327
|
+
418,
|
|
328
|
+
421,
|
|
329
|
+
422,
|
|
330
|
+
423,
|
|
331
|
+
424,
|
|
332
|
+
425,
|
|
333
|
+
426,
|
|
334
|
+
428,
|
|
335
|
+
429,
|
|
336
|
+
431,
|
|
337
|
+
451,
|
|
338
|
+
500,
|
|
339
|
+
501,
|
|
340
|
+
502,
|
|
341
|
+
503,
|
|
342
|
+
504,
|
|
343
|
+
505,
|
|
344
|
+
506,
|
|
345
|
+
507,
|
|
346
|
+
508,
|
|
347
|
+
510,
|
|
348
|
+
511
|
|
349
|
+
];
|
|
285
350
|
var allowedRuntimes = type("'none' | 'arktype' | 'io-ts' | 'typebox' | 'valibot' | 'yup' | 'zod'");
|
|
286
351
|
var runtimeValidationGenerator = {
|
|
287
352
|
arktype: Codegen.ModelToArkType.Generate,
|
|
@@ -314,10 +379,17 @@ var replacerByRuntime = {
|
|
|
314
379
|
)
|
|
315
380
|
};
|
|
316
381
|
var generateFile = (options) => {
|
|
317
|
-
const ctx = {
|
|
382
|
+
const ctx = {
|
|
383
|
+
...options,
|
|
384
|
+
runtime: options.runtime ?? "none",
|
|
385
|
+
successStatusCodes: options.successStatusCodes ?? DEFAULT_SUCCESS_STATUS_CODES,
|
|
386
|
+
errorStatusCodes: options.errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES,
|
|
387
|
+
includeClient: options.includeClient ?? true
|
|
388
|
+
};
|
|
318
389
|
const schemaList = generateSchemaList(ctx);
|
|
319
390
|
const endpointSchemaList = options.schemasOnly ? "" : generateEndpointSchemaList(ctx);
|
|
320
|
-
const
|
|
391
|
+
const endpointByMethod = options.schemasOnly ? "" : generateEndpointByMethod(ctx);
|
|
392
|
+
const apiClient = options.schemasOnly || !ctx.includeClient ? "" : generateApiClient(ctx);
|
|
321
393
|
const transform = ctx.runtime === "none" ? (file2) => file2 : (file2) => {
|
|
322
394
|
const model = Codegen.TypeScriptToModel.Generate(file2);
|
|
323
395
|
const transformer = runtimeValidationGenerator[ctx.runtime];
|
|
@@ -337,6 +409,7 @@ var generateFile = (options) => {
|
|
|
337
409
|
};
|
|
338
410
|
const file = `
|
|
339
411
|
${transform(schemaList + endpointSchemaList)}
|
|
412
|
+
${endpointByMethod}
|
|
340
413
|
${apiClient}
|
|
341
414
|
`;
|
|
342
415
|
return file;
|
|
@@ -357,8 +430,18 @@ var generateSchemaList = ({ refs, runtime }) => {
|
|
|
357
430
|
${runtime === "none" ? "}" : ""}
|
|
358
431
|
`;
|
|
359
432
|
};
|
|
360
|
-
var parameterObjectToString = (parameters) => {
|
|
361
|
-
if (parameters instanceof Box)
|
|
433
|
+
var parameterObjectToString = (parameters, ctx) => {
|
|
434
|
+
if (parameters instanceof Box) {
|
|
435
|
+
if (ctx.runtime === "none") {
|
|
436
|
+
return parameters.recompute((box) => {
|
|
437
|
+
if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
|
|
438
|
+
box.value = `Schemas.${box.value}`;
|
|
439
|
+
}
|
|
440
|
+
return box;
|
|
441
|
+
}).value;
|
|
442
|
+
}
|
|
443
|
+
return parameters.value;
|
|
444
|
+
}
|
|
362
445
|
let str = "{";
|
|
363
446
|
for (const [key, box] of Object.entries(parameters)) {
|
|
364
447
|
str += `${wrapWithQuotesIfNeeded(key)}${box.type === "optional" ? "?" : ""}: ${box.value},
|
|
@@ -380,6 +463,20 @@ var responseHeadersObjectToString = (responseHeaders, ctx) => {
|
|
|
380
463
|
}
|
|
381
464
|
return str + "}";
|
|
382
465
|
};
|
|
466
|
+
var generateResponsesObject = (responses, ctx) => {
|
|
467
|
+
let str = "{";
|
|
468
|
+
for (const [statusCode, responseType] of Object.entries(responses)) {
|
|
469
|
+
const value = ctx.runtime === "none" ? responseType.recompute((box) => {
|
|
470
|
+
if (Box.isReference(box) && !box.params.generics && box.value !== "null") {
|
|
471
|
+
box.value = `Schemas.${box.value}`;
|
|
472
|
+
}
|
|
473
|
+
return box;
|
|
474
|
+
}).value : responseType.value;
|
|
475
|
+
str += `${wrapWithQuotesIfNeeded(statusCode)}: ${value},
|
|
476
|
+
`;
|
|
477
|
+
}
|
|
478
|
+
return str + "}";
|
|
479
|
+
};
|
|
383
480
|
var generateEndpointSchemaList = (ctx) => {
|
|
384
481
|
let file = `
|
|
385
482
|
${ctx.runtime === "none" ? "export namespace Endpoints {" : ""}
|
|
@@ -393,16 +490,17 @@ var generateEndpointSchemaList = (ctx) => {
|
|
|
393
490
|
path: "${endpoint.path}",
|
|
394
491
|
requestFormat: "${endpoint.requestFormat}",
|
|
395
492
|
${endpoint.meta.hasParameters ? `parameters: {
|
|
396
|
-
${parameters.query ? `query: ${parameterObjectToString(parameters.query)},` : ""}
|
|
397
|
-
${parameters.path ? `path: ${parameterObjectToString(parameters.path)},` : ""}
|
|
398
|
-
${parameters.header ? `header: ${parameterObjectToString(parameters.header)},` : ""}
|
|
493
|
+
${parameters.query ? `query: ${parameterObjectToString(parameters.query, ctx)},` : ""}
|
|
494
|
+
${parameters.path ? `path: ${parameterObjectToString(parameters.path, ctx)},` : ""}
|
|
495
|
+
${parameters.header ? `header: ${parameterObjectToString(parameters.header, ctx)},` : ""}
|
|
399
496
|
${parameters.body ? `body: ${parameterObjectToString(
|
|
400
497
|
ctx.runtime === "none" ? parameters.body.recompute((box) => {
|
|
401
498
|
if (Box.isReference(box) && !box.params.generics) {
|
|
402
499
|
box.value = `Schemas.${box.value}`;
|
|
403
500
|
}
|
|
404
501
|
return box;
|
|
405
|
-
}) : parameters.body
|
|
502
|
+
}) : parameters.body,
|
|
503
|
+
ctx
|
|
406
504
|
)},` : ""}
|
|
407
505
|
}` : "parameters: never,"}
|
|
408
506
|
response: ${ctx.runtime === "none" ? endpoint.response.recompute((box) => {
|
|
@@ -411,6 +509,7 @@ var generateEndpointSchemaList = (ctx) => {
|
|
|
411
509
|
}
|
|
412
510
|
return box;
|
|
413
511
|
}).value : endpoint.response.value},
|
|
512
|
+
${endpoint.responses ? `responses: ${generateResponsesObject(endpoint.responses, ctx)},` : ""}
|
|
414
513
|
${endpoint.responseHeaders ? `responseHeaders: ${responseHeadersObjectToString(endpoint.responseHeaders, ctx)},` : ""}
|
|
415
514
|
}
|
|
416
515
|
`;
|
|
@@ -431,7 +530,7 @@ var generateEndpointByMethod = (ctx) => {
|
|
|
431
530
|
return `${method}: {
|
|
432
531
|
${list.map(
|
|
433
532
|
(endpoint) => `"${endpoint.path}": ${ctx.runtime === "none" ? "Endpoints." : ""}${endpoint.meta.alias}`
|
|
434
|
-
)}
|
|
533
|
+
).join(",\n")}
|
|
435
534
|
}`;
|
|
436
535
|
}).join(",\n")}
|
|
437
536
|
}
|
|
@@ -447,9 +546,11 @@ var generateEndpointByMethod = (ctx) => {
|
|
|
447
546
|
return endpointByMethod + shorthands;
|
|
448
547
|
};
|
|
449
548
|
var generateApiClient = (ctx) => {
|
|
549
|
+
if (!ctx.includeClient) {
|
|
550
|
+
return "";
|
|
551
|
+
}
|
|
450
552
|
const { endpointList } = ctx;
|
|
451
553
|
const byMethods = groupBy(endpointList, "method");
|
|
452
|
-
const endpointSchemaList = generateEndpointByMethod(ctx);
|
|
453
554
|
const apiClientTypes = `
|
|
454
555
|
// <ApiClientTypes>
|
|
455
556
|
export type EndpointParameters = {
|
|
@@ -467,6 +568,7 @@ type RequestFormat = "json" | "form-data" | "form-url" | "binary" | "text";
|
|
|
467
568
|
export type DefaultEndpoint = {
|
|
468
569
|
parameters?: EndpointParameters | undefined;
|
|
469
570
|
response: unknown;
|
|
571
|
+
responses?: Record<string, unknown>;
|
|
470
572
|
responseHeaders?: Record<string, unknown>;
|
|
471
573
|
};
|
|
472
574
|
|
|
@@ -482,11 +584,64 @@ export type Endpoint<TConfig extends DefaultEndpoint = DefaultEndpoint> = {
|
|
|
482
584
|
areParametersRequired: boolean;
|
|
483
585
|
};
|
|
484
586
|
response: TConfig["response"];
|
|
587
|
+
responses?: TConfig["responses"];
|
|
485
588
|
responseHeaders?: TConfig["responseHeaders"]
|
|
486
589
|
};
|
|
487
590
|
|
|
488
591
|
export type Fetcher = (method: Method, url: string, parameters?: EndpointParameters | undefined) => Promise<Response>;
|
|
489
592
|
|
|
593
|
+
export const successStatusCodes = [${ctx.successStatusCodes.join(",")}] as const;
|
|
594
|
+
export type SuccessStatusCode = typeof successStatusCodes[number];
|
|
595
|
+
|
|
596
|
+
export const errorStatusCodes = [${ctx.errorStatusCodes.join(",")}] as const;
|
|
597
|
+
export type ErrorStatusCode = typeof errorStatusCodes[number];
|
|
598
|
+
|
|
599
|
+
// Error handling types
|
|
600
|
+
/** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
|
|
601
|
+
interface SuccessResponse<TSuccess, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
|
|
602
|
+
ok: true;
|
|
603
|
+
status: TStatusCode;
|
|
604
|
+
data: TSuccess;
|
|
605
|
+
/** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
|
|
606
|
+
json: () => Promise<TSuccess>;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** @see https://developer.mozilla.org/en-US/docs/Web/API/Response */
|
|
610
|
+
interface ErrorResponse<TData, TStatusCode> extends Omit<Response, "ok" | "status" | "json"> {
|
|
611
|
+
ok: false;
|
|
612
|
+
status: TStatusCode;
|
|
613
|
+
data: TData;
|
|
614
|
+
/** [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Response/json) */
|
|
615
|
+
json: () => Promise<TData>;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export type TypedApiResponse<TSuccess, TAllResponses extends Record<string | number, unknown> = {}> =
|
|
619
|
+
(keyof TAllResponses extends never
|
|
620
|
+
? SuccessResponse<TSuccess, number>
|
|
621
|
+
: {
|
|
622
|
+
[K in keyof TAllResponses]: K extends string
|
|
623
|
+
? K extends \`\${infer TStatusCode extends number}\`
|
|
624
|
+
? TStatusCode extends SuccessStatusCode
|
|
625
|
+
? SuccessResponse<TSuccess, TStatusCode>
|
|
626
|
+
: ErrorResponse<TAllResponses[K], TStatusCode>
|
|
627
|
+
: never
|
|
628
|
+
: K extends number
|
|
629
|
+
? K extends SuccessStatusCode
|
|
630
|
+
? SuccessResponse<TSuccess, K>
|
|
631
|
+
: ErrorResponse<TAllResponses[K], K>
|
|
632
|
+
: never;
|
|
633
|
+
}[keyof TAllResponses]);
|
|
634
|
+
|
|
635
|
+
export type SafeApiResponse<TEndpoint> = TEndpoint extends { response: infer TSuccess; responses: infer TResponses }
|
|
636
|
+
? TResponses extends Record<string, unknown>
|
|
637
|
+
? TypedApiResponse<TSuccess, TResponses>
|
|
638
|
+
: SuccessResponse<TSuccess, number>
|
|
639
|
+
: TEndpoint extends { response: infer TSuccess }
|
|
640
|
+
? SuccessResponse<TSuccess, number>
|
|
641
|
+
: never;
|
|
642
|
+
|
|
643
|
+
export type InferResponseByStatus<TEndpoint, TStatusCode> = Extract<SafeApiResponse<TEndpoint>, { status: TStatusCode }>
|
|
644
|
+
|
|
490
645
|
type RequiredKeys<T> = {
|
|
491
646
|
[P in keyof T]-?: undefined extends T[P] ? never : P;
|
|
492
647
|
}[keyof T];
|
|
@@ -496,9 +651,23 @@ type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [confi
|
|
|
496
651
|
// </ApiClientTypes>
|
|
497
652
|
`;
|
|
498
653
|
const apiClient = `
|
|
654
|
+
// <TypedResponseError>
|
|
655
|
+
export class TypedResponseError extends Error {
|
|
656
|
+
response: ErrorResponse<unknown, ErrorStatusCode>;
|
|
657
|
+
status: number;
|
|
658
|
+
constructor(response: ErrorResponse<unknown, ErrorStatusCode>) {
|
|
659
|
+
super(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
660
|
+
this.name = 'TypedResponseError';
|
|
661
|
+
this.response = response;
|
|
662
|
+
this.status = response.status;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// </TypedResponseError>
|
|
499
666
|
// <ApiClient>
|
|
500
667
|
export class ApiClient {
|
|
501
668
|
baseUrl: string = "";
|
|
669
|
+
successStatusCodes = successStatusCodes;
|
|
670
|
+
errorStatusCodes = errorStatusCodes;
|
|
502
671
|
|
|
503
672
|
constructor(public fetcher: Fetcher) {}
|
|
504
673
|
|
|
@@ -521,10 +690,38 @@ export class ApiClient {
|
|
|
521
690
|
return endpointByMethod.length ? `// <ApiClient.${method}>
|
|
522
691
|
${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
|
|
523
692
|
path: Path,
|
|
524
|
-
...params: MaybeOptionalArg<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["parameters"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["parameters"]`).otherwise(() => `TEndpoint["parameters"]`)}>
|
|
525
|
-
): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}
|
|
526
|
-
|
|
527
|
-
|
|
693
|
+
...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 }>
|
|
694
|
+
): Promise<${match(ctx.runtime).with("zod", "yup", () => infer(`TEndpoint["response"]`)).with("arktype", "io-ts", "typebox", "valibot", () => infer(`TEndpoint`) + `["response"]`).otherwise(() => `TEndpoint["response"]`)}>;
|
|
695
|
+
|
|
696
|
+
${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
|
|
697
|
+
path: Path,
|
|
698
|
+
...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 }>
|
|
699
|
+
): Promise<SafeApiResponse<TEndpoint>>;
|
|
700
|
+
|
|
701
|
+
${method}<Path extends keyof ${capitalizedMethod}Endpoints, TEndpoint extends ${capitalizedMethod}Endpoints[Path]>(
|
|
702
|
+
path: Path,
|
|
703
|
+
...params: MaybeOptionalArg<any>
|
|
704
|
+
): Promise<any> {
|
|
705
|
+
const requestParams = params[0];
|
|
706
|
+
const withResponse = requestParams?.withResponse;
|
|
707
|
+
const { withResponse: _, throwOnStatusError = withResponse ? false : true, ...fetchParams } = requestParams || {};
|
|
708
|
+
|
|
709
|
+
const promise = this.fetcher("${method}", this.baseUrl + path, Object.keys(fetchParams).length ? requestParams : undefined)
|
|
710
|
+
.then(async (response) => {
|
|
711
|
+
const data = await this.parseResponse(response);
|
|
712
|
+
const typedResponse = Object.assign(response, {
|
|
713
|
+
data: data,
|
|
714
|
+
json: () => Promise.resolve(data)
|
|
715
|
+
}) as SafeApiResponse<TEndpoint>;
|
|
716
|
+
|
|
717
|
+
if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
|
|
718
|
+
throw new TypedResponseError(typedResponse as never);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return withResponse ? typedResponse : data;
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
return promise ${match(ctx.runtime).with("zod", "yup", () => `as Promise<${infer(`TEndpoint["response"]`)}>`).with("arktype", "io-ts", "typebox", "valibot", () => `as Promise<${infer(`TEndpoint`) + `["response"]`}>`).otherwise(() => `as Promise<TEndpoint["response"]>`)}
|
|
528
725
|
}
|
|
529
726
|
// </ApiClient.${method}>
|
|
530
727
|
` : "";
|
|
@@ -552,11 +749,8 @@ export class ApiClient {
|
|
|
552
749
|
"valibot",
|
|
553
750
|
() => inferByRuntime[ctx.runtime](`TEndpoint`) + `["parameters"]`
|
|
554
751
|
).otherwise(() => `TEndpoint extends { parameters: infer Params } ? Params : never`)}>)
|
|
555
|
-
: Promise<
|
|
556
|
-
|
|
557
|
-
json: () => Promise<TEndpoint extends { response: infer Res } ? Res : never>;
|
|
558
|
-
}> {
|
|
559
|
-
return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters);
|
|
752
|
+
: Promise<SafeApiResponse<TEndpoint>> {
|
|
753
|
+
return this.fetcher(method, this.baseUrl + (path as string), params[0] as EndpointParameters) as Promise<SafeApiResponse<TEndpoint>>;
|
|
560
754
|
}
|
|
561
755
|
// </ApiClient.request>
|
|
562
756
|
}
|
|
@@ -574,11 +768,26 @@ export function createApiClient(fetcher: Fetcher, baseUrl?: string) {
|
|
|
574
768
|
api.get("/users").then((users) => console.log(users));
|
|
575
769
|
api.post("/users", { body: { name: "John" } }).then((user) => console.log(user));
|
|
576
770
|
api.put("/users/:id", { path: { id: 1 }, body: { name: "John" } }).then((user) => console.log(user));
|
|
771
|
+
|
|
772
|
+
// With error handling
|
|
773
|
+
const result = await api.get("/users/{id}", { path: { id: "123" }, withResponse: true });
|
|
774
|
+
if (result.ok) {
|
|
775
|
+
// Access data directly
|
|
776
|
+
const user = result.data;
|
|
777
|
+
console.log(user);
|
|
778
|
+
|
|
779
|
+
// Or use the json() method for compatibility
|
|
780
|
+
const userFromJson = await result.json();
|
|
781
|
+
console.log(userFromJson);
|
|
782
|
+
} else {
|
|
783
|
+
const error = result.data;
|
|
784
|
+
console.error(\`Error \${result.status}:\`, error);
|
|
785
|
+
}
|
|
577
786
|
*/
|
|
578
787
|
|
|
579
|
-
// </ApiClient
|
|
788
|
+
// </ApiClient>
|
|
580
789
|
`;
|
|
581
|
-
return
|
|
790
|
+
return apiClientTypes + apiClient;
|
|
582
791
|
};
|
|
583
792
|
|
|
584
793
|
// src/ref-resolver.ts
|
|
@@ -926,10 +1135,17 @@ var mapOpenApiEndpoints = (doc, options) => {
|
|
|
926
1135
|
endpoint.requestFormat = match2(matchingMediaType).with("application/octet-stream", () => "binary").with("multipart/form-data", () => "form-data").with("application/x-www-form-urlencoded", () => "form-url").with(P.string.includes("json"), () => "json").otherwise(() => "text");
|
|
927
1136
|
}
|
|
928
1137
|
if (params) {
|
|
929
|
-
const t = createBoxFactory({}, ctx);
|
|
930
1138
|
const filtered_params = ["query", "path", "header"];
|
|
931
1139
|
for (const k of filtered_params) {
|
|
932
1140
|
if (params[k] && lists[k].length) {
|
|
1141
|
+
const properties = Object.entries(params[k]).reduce(
|
|
1142
|
+
(acc, [key, value]) => {
|
|
1143
|
+
if (value.schema) acc[key] = value.schema;
|
|
1144
|
+
return acc;
|
|
1145
|
+
},
|
|
1146
|
+
{}
|
|
1147
|
+
);
|
|
1148
|
+
const t = createBoxFactory({ type: "object", properties }, ctx);
|
|
933
1149
|
if (lists[k].every((param) => !param.required)) {
|
|
934
1150
|
params[k] = t.reference("Partial", [t.object(params[k])]);
|
|
935
1151
|
} else {
|
|
@@ -944,14 +1160,45 @@ var mapOpenApiEndpoints = (doc, options) => {
|
|
|
944
1160
|
endpoint.parameters = Object.keys(params).length ? params : void 0;
|
|
945
1161
|
}
|
|
946
1162
|
let responseObject;
|
|
1163
|
+
const allResponses = {};
|
|
947
1164
|
Object.entries(operation.responses ?? {}).map(([status, responseOrRef]) => {
|
|
948
1165
|
const statusCode = Number(status);
|
|
949
|
-
|
|
950
|
-
|
|
1166
|
+
const responseObj = refs.unwrap(responseOrRef);
|
|
1167
|
+
const content2 = responseObj?.content;
|
|
1168
|
+
if (content2) {
|
|
1169
|
+
const matchingMediaType = Object.keys(content2).find(isResponseMediaType);
|
|
1170
|
+
if (matchingMediaType && content2[matchingMediaType]) {
|
|
1171
|
+
allResponses[status] = openApiSchemaToTs({
|
|
1172
|
+
schema: content2[matchingMediaType]?.schema ?? {},
|
|
1173
|
+
ctx
|
|
1174
|
+
});
|
|
1175
|
+
} else {
|
|
1176
|
+
allResponses[status] = openApiSchemaToTs({ schema: {}, ctx });
|
|
1177
|
+
}
|
|
1178
|
+
} else {
|
|
1179
|
+
allResponses[status] = openApiSchemaToTs({ schema: {}, ctx });
|
|
1180
|
+
}
|
|
1181
|
+
if (statusCode >= 200 && statusCode < 300 && !responseObject) {
|
|
1182
|
+
responseObject = responseObj;
|
|
951
1183
|
}
|
|
952
1184
|
});
|
|
953
1185
|
if (!responseObject && operation.responses?.default) {
|
|
954
1186
|
responseObject = refs.unwrap(operation.responses.default);
|
|
1187
|
+
if (!allResponses["default"]) {
|
|
1188
|
+
const content2 = responseObject?.content;
|
|
1189
|
+
if (content2) {
|
|
1190
|
+
const matchingMediaType = Object.keys(content2).find(isResponseMediaType);
|
|
1191
|
+
if (matchingMediaType && content2[matchingMediaType]) {
|
|
1192
|
+
allResponses["default"] = openApiSchemaToTs({
|
|
1193
|
+
schema: content2[matchingMediaType]?.schema ?? {},
|
|
1194
|
+
ctx
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (Object.keys(allResponses).length > 0) {
|
|
1201
|
+
endpoint.responses = allResponses;
|
|
955
1202
|
}
|
|
956
1203
|
const content = responseObject?.content;
|
|
957
1204
|
if (content) {
|
|
@@ -986,7 +1233,7 @@ var allowedParamMediaTypes = [
|
|
|
986
1233
|
"*/*"
|
|
987
1234
|
];
|
|
988
1235
|
var isAllowedParamMediaTypes = (mediaType) => mediaType.includes("application/") && mediaType.includes("json") || allowedParamMediaTypes.includes(mediaType) || mediaType.includes("text/");
|
|
989
|
-
var isResponseMediaType = (mediaType) => mediaType === "application/json";
|
|
1236
|
+
var isResponseMediaType = (mediaType) => mediaType === "application/json" || mediaType === "*/*";
|
|
990
1237
|
var getAlias = ({ path, method, operation }) => sanitizeName(
|
|
991
1238
|
(method + "_" + capitalize3(operation.operationId ?? pathToVariableName(path))).replace(/-/g, "__"),
|
|
992
1239
|
"endpoint"
|
|
@@ -998,7 +1245,8 @@ var generateTanstackQueryFile = async (ctx) => {
|
|
|
998
1245
|
const endpointMethods = new Set(ctx.endpointList.map((endpoint) => endpoint.method.toLowerCase()));
|
|
999
1246
|
const file = `
|
|
1000
1247
|
import { queryOptions } from "@tanstack/react-query"
|
|
1001
|
-
import type { EndpointByMethod, ApiClient } from "${ctx.relativeApiClientPath}"
|
|
1248
|
+
import type { EndpointByMethod, ApiClient, SuccessStatusCode, ErrorStatusCode, InferResponseByStatus } from "${ctx.relativeApiClientPath}"
|
|
1249
|
+
import { errorStatusCodes, TypedResponseError } from "${ctx.relativeApiClientPath}"
|
|
1002
1250
|
|
|
1003
1251
|
type EndpointQueryKey<TOptions extends EndpointParameters> = [
|
|
1004
1252
|
TOptions & {
|
|
@@ -1062,30 +1310,36 @@ var generateTanstackQueryFile = async (ctx) => {
|
|
|
1062
1310
|
path: Path,
|
|
1063
1311
|
...params: MaybeOptionalArg<TEndpoint["parameters"]>
|
|
1064
1312
|
) {
|
|
1065
|
-
const queryKey = createQueryKey(path, params[0]);
|
|
1313
|
+
const queryKey = createQueryKey(path as string, params[0]);
|
|
1066
1314
|
const query = {
|
|
1067
1315
|
/** type-only property if you need easy access to the endpoint params */
|
|
1068
1316
|
"~endpoint": {} as TEndpoint,
|
|
1069
1317
|
queryKey,
|
|
1318
|
+
queryFn: {} as "You need to pass .queryOptions to the useQuery hook",
|
|
1070
1319
|
queryOptions: queryOptions({
|
|
1071
1320
|
queryFn: async ({ queryKey, signal, }) => {
|
|
1072
|
-
const
|
|
1073
|
-
...params,
|
|
1074
|
-
...queryKey[0],
|
|
1321
|
+
const requestParams = {
|
|
1322
|
+
...(params[0] || {}),
|
|
1323
|
+
...(queryKey[0] || {}),
|
|
1075
1324
|
signal,
|
|
1076
|
-
|
|
1325
|
+
withResponse: false as const
|
|
1326
|
+
};
|
|
1327
|
+
const res = await this.client.${method}(path, requestParams);
|
|
1077
1328
|
return res as TEndpoint["response"];
|
|
1078
1329
|
},
|
|
1079
1330
|
queryKey: queryKey
|
|
1080
1331
|
}),
|
|
1332
|
+
mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
|
|
1081
1333
|
mutationOptions: {
|
|
1082
1334
|
mutationKey: queryKey,
|
|
1083
1335
|
mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters} ? Parameters: never) => {
|
|
1084
|
-
const
|
|
1085
|
-
...params,
|
|
1086
|
-
...queryKey[0],
|
|
1087
|
-
...localOptions,
|
|
1088
|
-
|
|
1336
|
+
const requestParams = {
|
|
1337
|
+
...(params[0] || {}),
|
|
1338
|
+
...(queryKey[0] || {}),
|
|
1339
|
+
...(localOptions || {}),
|
|
1340
|
+
withResponse: false as const
|
|
1341
|
+
};
|
|
1342
|
+
const res = await this.client.${method}(path, requestParams);
|
|
1089
1343
|
return res as TEndpoint["response"];
|
|
1090
1344
|
}
|
|
1091
1345
|
}
|
|
@@ -1099,32 +1353,67 @@ var generateTanstackQueryFile = async (ctx) => {
|
|
|
1099
1353
|
|
|
1100
1354
|
// <ApiClient.request>
|
|
1101
1355
|
/**
|
|
1102
|
-
* Generic mutation method with full type-safety for any endpoint
|
|
1356
|
+
* Generic mutation method with full type-safety for any endpoint; it doesnt require parameters to be passed initially
|
|
1357
|
+
* but instead will require them to be passed when calling the mutation.mutate() method
|
|
1103
1358
|
*/
|
|
1104
1359
|
mutation<
|
|
1105
1360
|
TMethod extends keyof EndpointByMethod,
|
|
1106
1361
|
TPath extends keyof EndpointByMethod[TMethod],
|
|
1107
1362
|
TEndpoint extends EndpointByMethod[TMethod][TPath],
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1363
|
+
TWithResponse extends boolean = false,
|
|
1364
|
+
TSelection = TWithResponse extends true
|
|
1365
|
+
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
|
|
1366
|
+
: TEndpoint extends { response: infer Res } ? Res : never,
|
|
1367
|
+
TError = TEndpoint extends { responses: infer TResponses }
|
|
1368
|
+
? TResponses extends Record<string | number, unknown>
|
|
1369
|
+
? InferResponseByStatus<TEndpoint, ErrorStatusCode>
|
|
1370
|
+
: Error
|
|
1371
|
+
: Error
|
|
1372
|
+
>(method: TMethod, path: TPath, options?: {
|
|
1373
|
+
withResponse?: TWithResponse;
|
|
1374
|
+
selectFn?: (res: TWithResponse extends true
|
|
1375
|
+
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
|
|
1376
|
+
: TEndpoint extends { response: infer Res } ? Res : never
|
|
1377
|
+
) => TSelection;
|
|
1378
|
+
throwOnStatusError?: boolean
|
|
1379
|
+
}) {
|
|
1113
1380
|
const mutationKey = [{ method, path }] as const;
|
|
1114
1381
|
return {
|
|
1115
1382
|
/** type-only property if you need easy access to the endpoint params */
|
|
1116
1383
|
"~endpoint": {} as TEndpoint,
|
|
1117
1384
|
mutationKey: mutationKey,
|
|
1385
|
+
mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
|
|
1118
1386
|
mutationOptions: {
|
|
1119
1387
|
mutationKey: mutationKey,
|
|
1120
|
-
mutationFn: async
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1388
|
+
mutationFn: async <TLocalWithResponse extends boolean = TWithResponse, TLocalSelection = TLocalWithResponse extends true
|
|
1389
|
+
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
|
|
1390
|
+
: TEndpoint extends { response: infer Res }
|
|
1391
|
+
? Res
|
|
1392
|
+
: never>
|
|
1393
|
+
(params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
|
|
1394
|
+
withResponse?: TLocalWithResponse;
|
|
1395
|
+
throwOnStatusError?: boolean;
|
|
1396
|
+
}): Promise<TLocalSelection> => {
|
|
1397
|
+
const withResponse = params.withResponse ??options?.withResponse ?? false;
|
|
1398
|
+
const throwOnStatusError = params.throwOnStatusError ?? options?.throwOnStatusError ?? (withResponse ? false : true);
|
|
1399
|
+
const selectFn = options?.selectFn;
|
|
1400
|
+
const response = await (this.client as any)[method](path, { ...params as any, withResponse: true, throwOnStatusError: false });
|
|
1401
|
+
|
|
1402
|
+
if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
|
|
1403
|
+
throw new TypedResponseError(response as never);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// Return just the data if withResponse is false, otherwise return the full response
|
|
1407
|
+
const finalResponse = withResponse ? response : response.data;
|
|
1408
|
+
const res = selectFn ? selectFn(finalResponse as any) : finalResponse;
|
|
1409
|
+
return res as never;
|
|
1410
|
+
}
|
|
1411
|
+
} satisfies import("@tanstack/react-query").UseMutationOptions<TSelection, TError, (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
|
|
1412
|
+
withResponse?: boolean;
|
|
1413
|
+
throwOnStatusError?: boolean;
|
|
1414
|
+
}>,
|
|
1127
1415
|
}
|
|
1416
|
+
}
|
|
1128
1417
|
// </ApiClient.request>
|
|
1129
1418
|
}
|
|
1130
1419
|
`;
|
|
@@ -1136,6 +1425,8 @@ export {
|
|
|
1136
1425
|
createFactory,
|
|
1137
1426
|
createBoxFactory,
|
|
1138
1427
|
openApiSchemaToTs,
|
|
1428
|
+
DEFAULT_SUCCESS_STATUS_CODES,
|
|
1429
|
+
DEFAULT_ERROR_STATUS_CODES,
|
|
1139
1430
|
allowedRuntimes,
|
|
1140
1431
|
generateFile,
|
|
1141
1432
|
createRefResolver,
|