@cushin/api-codegen 1.0.3 → 1.0.5
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 +423 -108
- package/dist/cli.js.map +1 -1
- package/dist/index.js +428 -113
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -184,9 +184,9 @@ var APIClient = class {
|
|
|
184
184
|
})();
|
|
185
185
|
return this.refreshPromise;
|
|
186
186
|
}
|
|
187
|
-
buildPath(
|
|
188
|
-
if (!params) return
|
|
189
|
-
let finalPath =
|
|
187
|
+
buildPath(path9, params) {
|
|
188
|
+
if (!params) return path9;
|
|
189
|
+
let finalPath = path9;
|
|
190
190
|
Object.entries(params).forEach(([key, value]) => {
|
|
191
191
|
finalPath = finalPath.replace(
|
|
192
192
|
`:${key}`,
|
|
@@ -218,7 +218,7 @@ var APIClient = class {
|
|
|
218
218
|
}
|
|
219
219
|
async request(endpoint, params, query, body) {
|
|
220
220
|
try {
|
|
221
|
-
const
|
|
221
|
+
const path9 = this.buildPath(endpoint.path, params);
|
|
222
222
|
const client = this.getClientForEndpoint(endpoint);
|
|
223
223
|
const options = {
|
|
224
224
|
method: endpoint.method
|
|
@@ -242,7 +242,7 @@ var APIClient = class {
|
|
|
242
242
|
options.json = body;
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
-
const response = await client(
|
|
245
|
+
const response = await client(path9, options);
|
|
246
246
|
const data = await response.json();
|
|
247
247
|
if (endpoint.response) {
|
|
248
248
|
return endpoint.response.parse(data);
|
|
@@ -384,6 +384,49 @@ var BaseGenerator = class {
|
|
|
384
384
|
return `return apiClient.${name}();`;
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
|
+
inferNonNull(expr) {
|
|
388
|
+
return `z.infer<NonNullable<${expr}>>`;
|
|
389
|
+
}
|
|
390
|
+
toCamelCase(str) {
|
|
391
|
+
return str.toLowerCase().replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toLowerCase());
|
|
392
|
+
}
|
|
393
|
+
getResourceFromEndpoint(_name, endpoint) {
|
|
394
|
+
const tag = endpoint.tags?.find((t) => t !== "query" && t !== "mutation");
|
|
395
|
+
if (tag) return this.toCamelCase(tag);
|
|
396
|
+
const match = endpoint.path.match(/^\/([^/]+)/);
|
|
397
|
+
return match ? this.toCamelCase(match[1]) : "general";
|
|
398
|
+
}
|
|
399
|
+
groupEndpointsByResource() {
|
|
400
|
+
const groups = {};
|
|
401
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
402
|
+
([name, endpoint]) => {
|
|
403
|
+
const res = this.getResourceFromEndpoint(name, endpoint);
|
|
404
|
+
if (!groups[res]) groups[res] = [];
|
|
405
|
+
groups[res].push({ name, endpoint });
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
return groups;
|
|
409
|
+
}
|
|
410
|
+
resourceHasQueryEndpoints(resource) {
|
|
411
|
+
return this.groupEndpointsByResource()[resource]?.some(
|
|
412
|
+
({ endpoint }) => endpoint.method === "GET"
|
|
413
|
+
) ?? false;
|
|
414
|
+
}
|
|
415
|
+
getEndpointKeyName(name) {
|
|
416
|
+
return name.startsWith("get") ? name[3].toLowerCase() + name.slice(4) : name;
|
|
417
|
+
}
|
|
418
|
+
generateQueryKeyCall(resource, name, endpoint) {
|
|
419
|
+
const key = this.getEndpointKeyName(name);
|
|
420
|
+
const args = [];
|
|
421
|
+
if (endpoint.params) args.push("params");
|
|
422
|
+
if (endpoint.query) args.push("filters");
|
|
423
|
+
return args.length ? `queryKeys.${resource}.${key}(${args.join(", ")})` : `queryKeys.${resource}.${key}()`;
|
|
424
|
+
}
|
|
425
|
+
hasQueryOptions() {
|
|
426
|
+
return Object.values(this.context.apiConfig.endpoints).some(
|
|
427
|
+
(e) => e.method === "GET"
|
|
428
|
+
);
|
|
429
|
+
}
|
|
387
430
|
};
|
|
388
431
|
|
|
389
432
|
// src/generators/hooks.ts
|
|
@@ -396,100 +439,132 @@ var HooksGenerator = class extends BaseGenerator {
|
|
|
396
439
|
}
|
|
397
440
|
generateContent() {
|
|
398
441
|
const useClientDirective = this.context.config.options?.useClientDirective ?? true;
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
} from
|
|
406
|
-
import {
|
|
407
|
-
import
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
ExtractResponse
|
|
413
|
-
} from './types';
|
|
442
|
+
const outputPath = path6.join(this.context.config.outputDir, "types.ts");
|
|
443
|
+
const endpointsPath = path6.join(this.context.config.endpointsPath);
|
|
444
|
+
const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
445
|
+
const content = `${useClientDirective ? "'use client';\n" : ""}
|
|
446
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
447
|
+
import { apiClient } from "./client";
|
|
448
|
+
import { queryKeys } from "./query-keys";
|
|
449
|
+
import { apiQueryOptions } from "./query-options";
|
|
450
|
+
import { z } from "zod";
|
|
451
|
+
import { apiConfig } from "${relativePath}";
|
|
452
|
+
|
|
453
|
+
${this.generateQueryHooks()}
|
|
454
|
+
${this.generateMutationHooks()}
|
|
414
455
|
`;
|
|
456
|
+
return content;
|
|
457
|
+
}
|
|
458
|
+
generateQueryHooks() {
|
|
415
459
|
const hooks = [];
|
|
416
|
-
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
hooks.push(this.generateMutationHook(name, endpoint));
|
|
460
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
461
|
+
([name, endpoint]) => {
|
|
462
|
+
if (endpoint.method === "GET")
|
|
463
|
+
hooks.push(this.generateQueryHook(name, endpoint));
|
|
421
464
|
}
|
|
422
|
-
|
|
423
|
-
return
|
|
465
|
+
);
|
|
466
|
+
return hooks.join("\n\n");
|
|
424
467
|
}
|
|
425
468
|
generateQueryHook(name, endpoint) {
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
const
|
|
469
|
+
const hookName = `use${this.capitalize(name)}`;
|
|
470
|
+
const resource = this.getResourceFromEndpoint(name, endpoint);
|
|
471
|
+
const optionName = this.getEndpointKeyName(name);
|
|
472
|
+
const inferParams = this.inferNonNull(
|
|
473
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
474
|
+
);
|
|
475
|
+
const inferQuery = this.inferNonNull(
|
|
476
|
+
`typeof apiConfig.endpoints.${name}.query`
|
|
477
|
+
);
|
|
478
|
+
const inferResponse = this.inferNonNull(
|
|
479
|
+
`typeof apiConfig.endpoints.${name}.response`
|
|
480
|
+
);
|
|
481
|
+
const params = [];
|
|
482
|
+
const optionParams = [];
|
|
429
483
|
const queryTags = this.getQueryTags(endpoint);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
484
|
+
if (endpoint.params) {
|
|
485
|
+
params.push(`params: ${inferParams}`);
|
|
486
|
+
optionParams.push("params");
|
|
487
|
+
}
|
|
488
|
+
if (endpoint.query) {
|
|
489
|
+
params.push(`filters?: ${inferQuery}`);
|
|
490
|
+
optionParams.push("filters");
|
|
491
|
+
}
|
|
492
|
+
params.push(`options?: {
|
|
493
|
+
enabled?: boolean;
|
|
494
|
+
select?: <TData = ${inferResponse}>(data: ${inferResponse}) => TData;
|
|
495
|
+
}`);
|
|
442
496
|
return `/**
|
|
443
497
|
* ${endpoint.description || `Query hook for ${name}`}
|
|
444
498
|
* @tags ${queryTags.join(", ") || "none"}
|
|
445
499
|
*/
|
|
446
|
-
export function ${hookName}(
|
|
447
|
-
${paramsList}
|
|
448
|
-
) {
|
|
500
|
+
export function ${hookName}(${params.join(",\n ")}) {
|
|
449
501
|
return useQuery({
|
|
450
|
-
|
|
451
|
-
queryFn: () => apiClient.${name}(${clientCallArgs.join(", ")}),
|
|
502
|
+
...apiQueryOptions.${resource}.${optionName}(${optionParams.join(", ")}),
|
|
452
503
|
...options,
|
|
453
504
|
});
|
|
454
505
|
}`;
|
|
455
506
|
}
|
|
507
|
+
generateMutationHooks() {
|
|
508
|
+
const hooks = [];
|
|
509
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
510
|
+
([name, endpoint]) => {
|
|
511
|
+
if (endpoint.method !== "GET")
|
|
512
|
+
hooks.push(this.generateMutationHook(name, endpoint));
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
return hooks.join("\n\n");
|
|
516
|
+
}
|
|
456
517
|
generateMutationHook(name, endpoint) {
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
518
|
+
const hookName = `use${this.capitalize(name)}`;
|
|
519
|
+
const resource = this.getResourceFromEndpoint(name, endpoint);
|
|
520
|
+
const inferParams = this.inferNonNull(
|
|
521
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
522
|
+
);
|
|
523
|
+
const inferBody = this.inferNonNull(
|
|
524
|
+
`typeof apiConfig.endpoints.${name}.body`
|
|
525
|
+
);
|
|
526
|
+
const inferResponse = this.inferNonNull(
|
|
527
|
+
`typeof apiConfig.endpoints.${name}.response`
|
|
528
|
+
);
|
|
529
|
+
const resourceHasQueries = this.resourceHasQueryEndpoints(resource);
|
|
530
|
+
let inputType;
|
|
531
|
+
let fnBody;
|
|
532
|
+
if (endpoint.params && endpoint.body) {
|
|
533
|
+
inputType = `{ params: ${inferParams}; body: ${inferBody}; }`;
|
|
534
|
+
fnBody = `({ params, body }: ${inputType}) => apiClient.${name}(params, body)`;
|
|
535
|
+
} else if (endpoint.params) {
|
|
536
|
+
inputType = `${inferParams}`;
|
|
537
|
+
fnBody = `(params: ${inputType}) => apiClient.${name}(params)`;
|
|
538
|
+
} else if (endpoint.body) {
|
|
539
|
+
inputType = `${inferBody}`;
|
|
540
|
+
fnBody = `(body: ${inputType}) => apiClient.${name}(body)`;
|
|
541
|
+
} else {
|
|
542
|
+
inputType = "void";
|
|
543
|
+
fnBody = `() => apiClient.${name}()`;
|
|
468
544
|
}
|
|
469
|
-
const
|
|
545
|
+
const invalidate = resourceHasQueries ? `queryClient.invalidateQueries({ queryKey: queryKeys.${resource}.all });` : "";
|
|
470
546
|
return `/**
|
|
471
547
|
* ${endpoint.description || `Mutation hook for ${name}`}
|
|
472
548
|
* @tags ${endpoint.tags?.join(", ") || "none"}
|
|
473
549
|
*/
|
|
474
|
-
export function ${hookName}(
|
|
475
|
-
|
|
476
|
-
)
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}`;
|
|
550
|
+
export function ${hookName}(options?: {
|
|
551
|
+
onSuccess?: (data: ${inferResponse}, variables: ${inputType}, context: unknown) => void;
|
|
552
|
+
onError?: (error: Error, variables: ${inputType}, context: unknown) => void;
|
|
553
|
+
onSettled?: (data: ${inferResponse} | undefined, error: Error | null, variables: ${inputType}, context: unknown) => void;
|
|
554
|
+
onMutate?: (variables: ${inputType}) => Promise<unknown> | unknown;
|
|
555
|
+
}) {
|
|
556
|
+
${invalidate ? "const queryClient = useQueryClient();" : ""}
|
|
557
|
+
return useMutation({
|
|
558
|
+
mutationFn: ${fnBody},
|
|
559
|
+
onSuccess: (data, variables, context) => {
|
|
560
|
+
${invalidate}
|
|
561
|
+
options?.onSuccess?.(data, variables, context);
|
|
562
|
+
},
|
|
563
|
+
onError: options?.onError,
|
|
564
|
+
onSettled: options?.onSettled,
|
|
565
|
+
onMutate: options?.onMutate,
|
|
566
|
+
});
|
|
567
|
+
}`;
|
|
493
568
|
}
|
|
494
569
|
};
|
|
495
570
|
var ServerActionsGenerator = class extends BaseGenerator {
|
|
@@ -584,11 +659,13 @@ import type {
|
|
|
584
659
|
} from './types';
|
|
585
660
|
`;
|
|
586
661
|
const queries = [];
|
|
587
|
-
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
588
|
-
|
|
589
|
-
|
|
662
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
663
|
+
([name, endpoint]) => {
|
|
664
|
+
if (this.isQueryEndpoint(endpoint)) {
|
|
665
|
+
queries.push(this.generateServerQuery(name, endpoint));
|
|
666
|
+
}
|
|
590
667
|
}
|
|
591
|
-
|
|
668
|
+
);
|
|
592
669
|
return imports + "\n" + queries.join("\n\n");
|
|
593
670
|
}
|
|
594
671
|
generateServerQuery(name, endpoint) {
|
|
@@ -601,9 +678,7 @@ import type {
|
|
|
601
678
|
const clientCallArgs = [];
|
|
602
679
|
if (signature.hasParams) clientCallArgs.push("params");
|
|
603
680
|
if (signature.hasQuery) clientCallArgs.push("query");
|
|
604
|
-
const cacheKeyParts = [
|
|
605
|
-
`'${name}'`
|
|
606
|
-
];
|
|
681
|
+
const cacheKeyParts = [`'${name}'`];
|
|
607
682
|
if (signature.hasParams) cacheKeyParts.push("JSON.stringify(params)");
|
|
608
683
|
if (signature.hasQuery) cacheKeyParts.push("JSON.stringify(query)");
|
|
609
684
|
return `/**
|
|
@@ -632,13 +707,18 @@ var TypesGenerator = class extends BaseGenerator {
|
|
|
632
707
|
await fs5.writeFile(outputPath, content, "utf-8");
|
|
633
708
|
}
|
|
634
709
|
generateContent() {
|
|
710
|
+
const outputPath = path6.join(this.context.config.outputDir, "types.ts");
|
|
711
|
+
const endpointsPath = path6.join(this.context.config.endpointsPath);
|
|
712
|
+
const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
635
713
|
return `// Auto-generated type definitions
|
|
636
714
|
// Do not edit this file manually
|
|
637
715
|
|
|
638
716
|
import type { z } from 'zod';
|
|
717
|
+
import { apiConfig } from '${relativePath}';
|
|
718
|
+
|
|
639
719
|
|
|
640
720
|
// Re-export endpoint configuration types
|
|
641
|
-
export type { APIConfig, APIEndpoint, HTTPMethod } from '@
|
|
721
|
+
export type { APIConfig, APIEndpoint, HTTPMethod } from '@cushin/api-codegen/schema';
|
|
642
722
|
|
|
643
723
|
/**
|
|
644
724
|
* Type helper to extract params schema from an endpoint
|
|
@@ -671,13 +751,37 @@ export type ExtractResponse<T> = T extends { response: infer R extends z.ZodType
|
|
|
671
751
|
/**
|
|
672
752
|
* Import your API config to get typed endpoints
|
|
673
753
|
*
|
|
674
|
-
* @example
|
|
675
|
-
* import { apiConfig } from './config/endpoints';
|
|
676
|
-
* export type APIEndpoints = typeof apiConfig.endpoints;
|
|
677
754
|
*/
|
|
678
|
-
export type APIEndpoints =
|
|
755
|
+
export type APIEndpoints = typeof apiConfig.endpoints;
|
|
756
|
+
|
|
757
|
+
${this.generateEndpointTypes()}
|
|
679
758
|
`;
|
|
680
759
|
}
|
|
760
|
+
generateEndpointTypes() {
|
|
761
|
+
const types = [];
|
|
762
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
763
|
+
([name, endpoint]) => {
|
|
764
|
+
const cap = this.capitalize(name);
|
|
765
|
+
if (endpoint.response)
|
|
766
|
+
types.push(
|
|
767
|
+
`export type ${cap}Response = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.response`)};`
|
|
768
|
+
);
|
|
769
|
+
if (endpoint.body)
|
|
770
|
+
types.push(
|
|
771
|
+
`export type ${cap}Input = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.body`)};`
|
|
772
|
+
);
|
|
773
|
+
if (endpoint.query)
|
|
774
|
+
types.push(
|
|
775
|
+
`export type ${cap}Query = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.query`)};`
|
|
776
|
+
);
|
|
777
|
+
if (endpoint.params)
|
|
778
|
+
types.push(
|
|
779
|
+
`export type ${cap}Params = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.params`)};`
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
return types.join("\n");
|
|
784
|
+
}
|
|
681
785
|
};
|
|
682
786
|
var ClientGenerator = class extends BaseGenerator {
|
|
683
787
|
async generate() {
|
|
@@ -694,51 +798,62 @@ var ClientGenerator = class extends BaseGenerator {
|
|
|
694
798
|
}
|
|
695
799
|
async generateServerClientFile() {
|
|
696
800
|
const content = this.generateServerClientContent();
|
|
697
|
-
const outputPath = path6.join(
|
|
801
|
+
const outputPath = path6.join(
|
|
802
|
+
this.context.config.outputDir,
|
|
803
|
+
"server-client.ts"
|
|
804
|
+
);
|
|
698
805
|
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
699
806
|
await fs5.writeFile(outputPath, content, "utf-8");
|
|
700
807
|
}
|
|
701
808
|
generateClientContent() {
|
|
702
809
|
const useClientDirective = this.context.config.options?.useClientDirective ?? true;
|
|
810
|
+
const outputPath = path6.join(this.context.config.outputDir, "types.ts");
|
|
811
|
+
const endpointsPath = path6.join(this.context.config.endpointsPath);
|
|
812
|
+
const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
703
813
|
return `${useClientDirective ? "'use client';\n" : ""}
|
|
704
814
|
import { createAPIClient } from '@cushin/api-codegen/client';
|
|
705
815
|
import type { AuthCallbacks } from '@cushin/api-codegen/client';
|
|
706
|
-
import { apiConfig } from '
|
|
707
|
-
import
|
|
816
|
+
import { apiConfig } from '${relativePath}';
|
|
817
|
+
import { z } from 'zod';
|
|
708
818
|
|
|
709
|
-
// Type
|
|
819
|
+
// Type the methods based on endpoints
|
|
710
820
|
type APIClientMethods = {
|
|
711
|
-
[K in keyof
|
|
821
|
+
[K in keyof typeof apiConfig.endpoints]: (typeof apiConfig.endpoints)[K] extends {
|
|
712
822
|
method: infer M;
|
|
713
823
|
params?: infer P;
|
|
714
824
|
query?: infer Q;
|
|
715
825
|
body?: infer B;
|
|
716
826
|
response: infer R;
|
|
717
827
|
}
|
|
718
|
-
? M extends
|
|
719
|
-
? P extends
|
|
720
|
-
? Q extends
|
|
721
|
-
? (params: P
|
|
722
|
-
: (params: P
|
|
723
|
-
: Q extends
|
|
724
|
-
? (query?: Q
|
|
725
|
-
: () => Promise<R
|
|
726
|
-
: P extends
|
|
727
|
-
? B extends
|
|
728
|
-
? (params: P
|
|
729
|
-
: (params: P
|
|
730
|
-
: B extends
|
|
731
|
-
? (body: B
|
|
732
|
-
: () => Promise<R
|
|
828
|
+
? M extends "GET"
|
|
829
|
+
? P extends z.ZodJSONSchema
|
|
830
|
+
? Q extends z.ZodJSONSchema
|
|
831
|
+
? (params: z.infer<P>, query?: z.infer<Q>) => Promise<z.infer<R>>
|
|
832
|
+
: (params: z.infer<P>) => Promise<z.infer<R>>
|
|
833
|
+
: Q extends z.ZodJSONSchema
|
|
834
|
+
? (query?: z.infer<Q>) => Promise<z.infer<R>>
|
|
835
|
+
: () => Promise<z.infer<R>>
|
|
836
|
+
: P extends z.ZodJSONSchema
|
|
837
|
+
? B extends z.ZodJSONSchema
|
|
838
|
+
? (params: z.infer<P>, body: z.infer<B>) => Promise<z.infer<R>>
|
|
839
|
+
: (params: z.infer<P>) => Promise<z.infer<R>>
|
|
840
|
+
: B extends z.ZodJSONSchema
|
|
841
|
+
? (body: z.infer<B>) => Promise<z.infer<R>>
|
|
842
|
+
: () => Promise<z.infer<R>>
|
|
733
843
|
: never;
|
|
734
844
|
};
|
|
735
845
|
|
|
846
|
+
|
|
736
847
|
// Export singleton instance (will be initialized later)
|
|
737
|
-
export let
|
|
848
|
+
export let baseClient: APIClientMethods & {
|
|
738
849
|
refreshAuth: () => Promise<void>;
|
|
739
850
|
updateAuthCallbacks: (callbacks: AuthCallbacks) => void;
|
|
740
851
|
};
|
|
741
852
|
|
|
853
|
+
export const apiClient = {
|
|
854
|
+
${this.generateApiClientMethods()}
|
|
855
|
+
};
|
|
856
|
+
|
|
742
857
|
/**
|
|
743
858
|
* Initialize API client with auth callbacks
|
|
744
859
|
* Call this function in your auth provider setup
|
|
@@ -758,8 +873,8 @@ export let apiClient: APIClientMethods & {
|
|
|
758
873
|
* initializeAPIClient(authCallbacks);
|
|
759
874
|
*/
|
|
760
875
|
export const initializeAPIClient = (authCallbacks: AuthCallbacks) => {
|
|
761
|
-
|
|
762
|
-
return
|
|
876
|
+
baseClient = createAPIClient(apiConfig, authCallbacks) as any;
|
|
877
|
+
return baseClient;
|
|
763
878
|
};
|
|
764
879
|
|
|
765
880
|
// Export for custom usage
|
|
@@ -806,6 +921,204 @@ type APIClientMethods = {
|
|
|
806
921
|
export const serverClient = createAPIClient(apiConfig) as APIClientMethods;
|
|
807
922
|
`;
|
|
808
923
|
}
|
|
924
|
+
generateApiClientMethods() {
|
|
925
|
+
const methods = [];
|
|
926
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
927
|
+
([name, endpoint]) => {
|
|
928
|
+
const inferParams = this.inferNonNull(
|
|
929
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
930
|
+
);
|
|
931
|
+
const inferQuery = this.inferNonNull(
|
|
932
|
+
`typeof apiConfig.endpoints.${name}.query`
|
|
933
|
+
);
|
|
934
|
+
const inferBody = this.inferNonNull(
|
|
935
|
+
`typeof apiConfig.endpoints.${name}.body`
|
|
936
|
+
);
|
|
937
|
+
const inferResponse = this.inferNonNull(
|
|
938
|
+
`typeof apiConfig.endpoints.${name}.response`
|
|
939
|
+
);
|
|
940
|
+
if (endpoint.method === "GET") {
|
|
941
|
+
if (endpoint.params && endpoint.query) {
|
|
942
|
+
methods.push(` ${name}: (params: ${inferParams}, query?: ${inferQuery}): Promise<${inferResponse}> =>
|
|
943
|
+
(baseClient as any).${name}(params, query),`);
|
|
944
|
+
} else if (endpoint.params) {
|
|
945
|
+
methods.push(` ${name}: (params: ${inferParams}): Promise<${inferResponse}> =>
|
|
946
|
+
(baseClient as any).${name}(params),`);
|
|
947
|
+
} else if (endpoint.query) {
|
|
948
|
+
methods.push(` ${name}: (query?: ${inferQuery}): Promise<${inferResponse}> =>
|
|
949
|
+
(baseClient as any).${name}(query),`);
|
|
950
|
+
} else {
|
|
951
|
+
methods.push(` ${name}: (): Promise<${inferResponse}> =>
|
|
952
|
+
(baseClient as any).${name}(),`);
|
|
953
|
+
}
|
|
954
|
+
} else {
|
|
955
|
+
if (endpoint.params && endpoint.body) {
|
|
956
|
+
methods.push(` ${name}: (params: ${inferParams}, body: ${inferBody}): Promise<${inferResponse}> =>
|
|
957
|
+
(baseClient as any).${name}(params, body),`);
|
|
958
|
+
} else if (endpoint.params) {
|
|
959
|
+
methods.push(` ${name}: (params: ${inferParams}): Promise<${inferResponse}> =>
|
|
960
|
+
(baseClient as any).${name}(params),`);
|
|
961
|
+
} else if (endpoint.body) {
|
|
962
|
+
methods.push(` ${name}: (body: ${inferBody}): Promise<${inferResponse}> =>
|
|
963
|
+
(baseClient as any).${name}(body),`);
|
|
964
|
+
} else {
|
|
965
|
+
methods.push(` ${name}: (): Promise<${inferResponse}> =>
|
|
966
|
+
(baseClient as any).${name}(),`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
);
|
|
971
|
+
return methods.join("\n");
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
var QueryKeysGenerator = class extends BaseGenerator {
|
|
975
|
+
async generate() {
|
|
976
|
+
const content = this.generateContent();
|
|
977
|
+
const outputPath = path6.join(
|
|
978
|
+
this.context.config.outputDir,
|
|
979
|
+
"query-keys.ts"
|
|
980
|
+
);
|
|
981
|
+
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
982
|
+
await fs5.writeFile(outputPath, content, "utf-8");
|
|
983
|
+
}
|
|
984
|
+
generateContent() {
|
|
985
|
+
const content = `// Auto-generated query keys
|
|
986
|
+
import { z } from 'zod';
|
|
987
|
+
import { apiConfig } from '../config/endpoints';
|
|
988
|
+
|
|
989
|
+
export const queryKeys = {
|
|
990
|
+
${this.generateQueryKeysContent()}
|
|
991
|
+
} as const;
|
|
992
|
+
`;
|
|
993
|
+
return content;
|
|
994
|
+
}
|
|
995
|
+
generateQueryKeysContent() {
|
|
996
|
+
const resourceGroups = this.groupEndpointsByResource();
|
|
997
|
+
const keys = [];
|
|
998
|
+
Object.entries(resourceGroups).forEach(([resource, endpoints]) => {
|
|
999
|
+
const queryEndpoints = endpoints.filter(
|
|
1000
|
+
({ endpoint }) => endpoint.method === "GET"
|
|
1001
|
+
);
|
|
1002
|
+
if (queryEndpoints.length === 0) return;
|
|
1003
|
+
const resourceKeys = [` all: ['${resource}'] as const,`];
|
|
1004
|
+
const added = /* @__PURE__ */ new Set();
|
|
1005
|
+
queryEndpoints.forEach(({ name, endpoint }) => {
|
|
1006
|
+
const keyName = this.getEndpointKeyName(name);
|
|
1007
|
+
if (added.has(keyName)) return;
|
|
1008
|
+
const inferParams = this.inferNonNull(
|
|
1009
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
1010
|
+
);
|
|
1011
|
+
const inferQuery = this.inferNonNull(
|
|
1012
|
+
`typeof apiConfig.endpoints.${name}.query`
|
|
1013
|
+
);
|
|
1014
|
+
if (endpoint.params || endpoint.query) {
|
|
1015
|
+
const params = [];
|
|
1016
|
+
if (endpoint.params) params.push(`params?: ${inferParams}`);
|
|
1017
|
+
if (endpoint.query) params.push(`query?: ${inferQuery}`);
|
|
1018
|
+
resourceKeys.push(` ${keyName}: (${params.join(", ")}) =>
|
|
1019
|
+
['${resource}', '${keyName}', ${endpoint.params ? "params" : "undefined"}, ${endpoint.query ? "query" : "undefined"}] as const,`);
|
|
1020
|
+
} else {
|
|
1021
|
+
resourceKeys.push(
|
|
1022
|
+
` ${keyName}: () => ['${resource}', '${keyName}'] as const,`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
added.add(keyName);
|
|
1026
|
+
});
|
|
1027
|
+
keys.push(` ${resource}: {
|
|
1028
|
+
${resourceKeys.join("\n")}
|
|
1029
|
+
},`);
|
|
1030
|
+
});
|
|
1031
|
+
return keys.join("\n");
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
var QueryOptionsGenerator = class extends BaseGenerator {
|
|
1035
|
+
async generate() {
|
|
1036
|
+
const content = this.generateContent();
|
|
1037
|
+
const outputPath = path6.join(
|
|
1038
|
+
this.context.config.outputDir,
|
|
1039
|
+
"query-options.ts"
|
|
1040
|
+
);
|
|
1041
|
+
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
1042
|
+
await fs5.writeFile(outputPath, content, "utf-8");
|
|
1043
|
+
}
|
|
1044
|
+
generateContent() {
|
|
1045
|
+
const content = `// Auto-generated query options
|
|
1046
|
+
import { queryOptions } from '@tanstack/react-query';
|
|
1047
|
+
import { apiClient } from './api-client';
|
|
1048
|
+
import { queryKeys } from './query-keys';
|
|
1049
|
+
import { z } from 'zod';
|
|
1050
|
+
import { apiConfig } from '../config/endpoints';
|
|
1051
|
+
|
|
1052
|
+
${this.generateQueryOptionsContent()}
|
|
1053
|
+
|
|
1054
|
+
export const apiQueryOptions = {
|
|
1055
|
+
${this.generateQueryOptionsExports()}
|
|
1056
|
+
} as const;
|
|
1057
|
+
`;
|
|
1058
|
+
return content;
|
|
1059
|
+
}
|
|
1060
|
+
generateQueryOptionsContent() {
|
|
1061
|
+
const groups = this.groupEndpointsByResource();
|
|
1062
|
+
const options = [];
|
|
1063
|
+
Object.entries(groups).forEach(([resource, endpoints]) => {
|
|
1064
|
+
const queries = endpoints.filter(
|
|
1065
|
+
({ endpoint }) => endpoint.method === "GET"
|
|
1066
|
+
);
|
|
1067
|
+
if (queries.length === 0) return;
|
|
1068
|
+
const resourceOptions = [];
|
|
1069
|
+
queries.forEach(({ name, endpoint }) => {
|
|
1070
|
+
const optionName = this.getEndpointKeyName(name);
|
|
1071
|
+
const inferParams = this.inferNonNull(
|
|
1072
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
1073
|
+
);
|
|
1074
|
+
const inferQuery = this.inferNonNull(
|
|
1075
|
+
`typeof apiConfig.endpoints.${name}.query`
|
|
1076
|
+
);
|
|
1077
|
+
const inferResponse = this.inferNonNull(
|
|
1078
|
+
`typeof apiConfig.endpoints.${name}.response`
|
|
1079
|
+
);
|
|
1080
|
+
const params = [];
|
|
1081
|
+
let apiCall = "";
|
|
1082
|
+
if (endpoint.params && endpoint.query) {
|
|
1083
|
+
params.push(`params: ${inferParams}`, `filters?: ${inferQuery}`);
|
|
1084
|
+
apiCall = `apiClient.${name}(params, filters)`;
|
|
1085
|
+
} else if (endpoint.params) {
|
|
1086
|
+
params.push(`params: ${inferParams}`);
|
|
1087
|
+
apiCall = `apiClient.${name}(params)`;
|
|
1088
|
+
} else if (endpoint.query) {
|
|
1089
|
+
params.push(`filters?: ${inferQuery}`);
|
|
1090
|
+
apiCall = `apiClient.${name}(filters)`;
|
|
1091
|
+
} else {
|
|
1092
|
+
apiCall = `apiClient.${name}()`;
|
|
1093
|
+
}
|
|
1094
|
+
const keyCall = this.generateQueryKeyCall(resource, name, endpoint);
|
|
1095
|
+
resourceOptions.push(` ${optionName}: (${params.join(", ")}) =>
|
|
1096
|
+
queryOptions({
|
|
1097
|
+
queryKey: ${keyCall},
|
|
1098
|
+
queryFn: (): Promise<${inferResponse}> => ${apiCall},
|
|
1099
|
+
staleTime: 1000 * 60 * 5,
|
|
1100
|
+
}),`);
|
|
1101
|
+
});
|
|
1102
|
+
options.push(
|
|
1103
|
+
`const ${resource}QueryOptions = {
|
|
1104
|
+
${resourceOptions.join("\n")}
|
|
1105
|
+
};
|
|
1106
|
+
`
|
|
1107
|
+
);
|
|
1108
|
+
});
|
|
1109
|
+
return options.join("\n");
|
|
1110
|
+
}
|
|
1111
|
+
generateQueryOptionsExports() {
|
|
1112
|
+
const groups = this.groupEndpointsByResource();
|
|
1113
|
+
const exports$1 = [];
|
|
1114
|
+
Object.keys(groups).forEach((resource) => {
|
|
1115
|
+
const hasQueries = groups[resource].some(
|
|
1116
|
+
({ endpoint }) => endpoint.method === "GET"
|
|
1117
|
+
);
|
|
1118
|
+
if (hasQueries) exports$1.push(` ${resource}: ${resource}QueryOptions,`);
|
|
1119
|
+
});
|
|
1120
|
+
return exports$1.join("\n");
|
|
1121
|
+
}
|
|
809
1122
|
};
|
|
810
1123
|
|
|
811
1124
|
// src/generators/index.ts
|
|
@@ -826,6 +1139,8 @@ var CodeGenerator = class {
|
|
|
826
1139
|
generators.push(new ClientGenerator(this.context));
|
|
827
1140
|
}
|
|
828
1141
|
if (this.context.config.generateHooks) {
|
|
1142
|
+
generators.push(new QueryKeysGenerator(this.context));
|
|
1143
|
+
generators.push(new QueryOptionsGenerator(this.context));
|
|
829
1144
|
generators.push(new HooksGenerator(this.context));
|
|
830
1145
|
}
|
|
831
1146
|
if (this.context.config.generateServerActions && this.context.config.provider === "nextjs") {
|