@cushin/api-codegen 1.0.2 → 1.0.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 +403 -82
- package/dist/cli.js.map +1 -1
- package/dist/index.js +406 -85
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { cosmiconfig } from 'cosmiconfig';
|
|
2
2
|
import path6 from 'path';
|
|
3
3
|
import ky, { HTTPError } from 'ky';
|
|
4
|
-
import {
|
|
4
|
+
import { createJiti } from 'jiti';
|
|
5
5
|
import fs5 from 'fs/promises';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
6
7
|
|
|
7
8
|
// src/config/schema.ts
|
|
8
9
|
function defineConfig(config) {
|
|
@@ -183,9 +184,9 @@ var APIClient = class {
|
|
|
183
184
|
})();
|
|
184
185
|
return this.refreshPromise;
|
|
185
186
|
}
|
|
186
|
-
buildPath(
|
|
187
|
-
if (!params) return
|
|
188
|
-
let finalPath =
|
|
187
|
+
buildPath(path9, params) {
|
|
188
|
+
if (!params) return path9;
|
|
189
|
+
let finalPath = path9;
|
|
189
190
|
Object.entries(params).forEach(([key, value]) => {
|
|
190
191
|
finalPath = finalPath.replace(
|
|
191
192
|
`:${key}`,
|
|
@@ -217,7 +218,7 @@ var APIClient = class {
|
|
|
217
218
|
}
|
|
218
219
|
async request(endpoint, params, query, body) {
|
|
219
220
|
try {
|
|
220
|
-
const
|
|
221
|
+
const path9 = this.buildPath(endpoint.path, params);
|
|
221
222
|
const client = this.getClientForEndpoint(endpoint);
|
|
222
223
|
const options = {
|
|
223
224
|
method: endpoint.method
|
|
@@ -241,7 +242,7 @@ var APIClient = class {
|
|
|
241
242
|
options.json = body;
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
|
-
const response = await client(
|
|
245
|
+
const response = await client(path9, options);
|
|
245
246
|
const data = await response.json();
|
|
246
247
|
if (endpoint.response) {
|
|
247
248
|
return endpoint.response.parse(data);
|
|
@@ -383,6 +384,49 @@ var BaseGenerator = class {
|
|
|
383
384
|
return `return apiClient.${name}();`;
|
|
384
385
|
}
|
|
385
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
|
+
}
|
|
386
430
|
};
|
|
387
431
|
|
|
388
432
|
// src/generators/hooks.ts
|
|
@@ -395,7 +439,10 @@ var HooksGenerator = class extends BaseGenerator {
|
|
|
395
439
|
}
|
|
396
440
|
generateContent() {
|
|
397
441
|
const useClientDirective = this.context.config.options?.useClientDirective ?? true;
|
|
398
|
-
const
|
|
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" : ""}
|
|
399
446
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
400
447
|
import type {
|
|
401
448
|
UseQueryOptions,
|
|
@@ -410,85 +457,126 @@ import type {
|
|
|
410
457
|
ExtractQuery,
|
|
411
458
|
ExtractResponse
|
|
412
459
|
} from './types';
|
|
460
|
+
import { queryKeys } from './query-keys';
|
|
461
|
+
${this.hasQueryOptions() ? "import { apiQueryOptions } from './query-options';" : ""}
|
|
462
|
+
import { z } from 'zod';
|
|
463
|
+
import { apiConfig } from '${relativePath}';
|
|
464
|
+
|
|
465
|
+
${this.generateQueryHooks()}
|
|
466
|
+
${this.generateMutationHooks()}
|
|
413
467
|
`;
|
|
468
|
+
return content;
|
|
469
|
+
}
|
|
470
|
+
generateQueryHooks() {
|
|
414
471
|
const hooks = [];
|
|
415
|
-
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
hooks.push(this.generateMutationHook(name, endpoint));
|
|
472
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
473
|
+
([name, endpoint]) => {
|
|
474
|
+
if (endpoint.method === "GET")
|
|
475
|
+
hooks.push(this.generateQueryHook(name, endpoint));
|
|
420
476
|
}
|
|
421
|
-
|
|
422
|
-
return
|
|
477
|
+
);
|
|
478
|
+
return hooks.join("\n\n");
|
|
423
479
|
}
|
|
424
480
|
generateQueryHook(name, endpoint) {
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
const
|
|
481
|
+
const hookName = `use${this.capitalize(name)}`;
|
|
482
|
+
const resource = this.getResourceFromEndpoint(name, endpoint);
|
|
483
|
+
const optionName = this.getEndpointKeyName(name);
|
|
484
|
+
const inferParams = this.inferNonNull(
|
|
485
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
486
|
+
);
|
|
487
|
+
const inferQuery = this.inferNonNull(
|
|
488
|
+
`typeof apiConfig.endpoints.${name}.query`
|
|
489
|
+
);
|
|
490
|
+
const inferResponse = this.inferNonNull(
|
|
491
|
+
`typeof apiConfig.endpoints.${name}.response`
|
|
492
|
+
);
|
|
493
|
+
const params = [];
|
|
494
|
+
const optionParams = [];
|
|
428
495
|
const queryTags = this.getQueryTags(endpoint);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
496
|
+
if (endpoint.params) {
|
|
497
|
+
params.push(`params: ${inferParams}`);
|
|
498
|
+
optionParams.push("params");
|
|
499
|
+
}
|
|
500
|
+
if (endpoint.query) {
|
|
501
|
+
params.push(`filters?: ${inferQuery}`);
|
|
502
|
+
optionParams.push("filters");
|
|
503
|
+
}
|
|
504
|
+
params.push(`options?: {
|
|
505
|
+
enabled?: boolean;
|
|
506
|
+
select?: <TData = ${inferResponse}>(data: ${inferResponse}) => TData;
|
|
507
|
+
}`);
|
|
441
508
|
return `/**
|
|
442
509
|
* ${endpoint.description || `Query hook for ${name}`}
|
|
443
510
|
* @tags ${queryTags.join(", ") || "none"}
|
|
444
511
|
*/
|
|
445
|
-
export function ${hookName}(
|
|
446
|
-
${paramsList}
|
|
447
|
-
) {
|
|
512
|
+
export function ${hookName}(${params.join(",\n ")}) {
|
|
448
513
|
return useQuery({
|
|
449
|
-
|
|
450
|
-
queryFn: () => apiClient.${name}(${clientCallArgs.join(", ")}),
|
|
514
|
+
...apiQueryOptions.${resource}.${optionName}(${optionParams.join(", ")}),
|
|
451
515
|
...options,
|
|
452
516
|
});
|
|
453
517
|
}`;
|
|
454
518
|
}
|
|
519
|
+
generateMutationHooks() {
|
|
520
|
+
const hooks = [];
|
|
521
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
522
|
+
([name, endpoint]) => {
|
|
523
|
+
if (endpoint.method !== "GET")
|
|
524
|
+
hooks.push(this.generateMutationHook(name, endpoint));
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
return hooks.join("\n\n");
|
|
528
|
+
}
|
|
455
529
|
generateMutationHook(name, endpoint) {
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
530
|
+
const hookName = `use${this.capitalize(name)}`;
|
|
531
|
+
const resource = this.getResourceFromEndpoint(name, endpoint);
|
|
532
|
+
const inferParams = this.inferNonNull(
|
|
533
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
534
|
+
);
|
|
535
|
+
const inferBody = this.inferNonNull(
|
|
536
|
+
`typeof apiConfig.endpoints.${name}.body`
|
|
537
|
+
);
|
|
538
|
+
const inferResponse = this.inferNonNull(
|
|
539
|
+
`typeof apiConfig.endpoints.${name}.response`
|
|
540
|
+
);
|
|
541
|
+
const resourceHasQueries = this.resourceHasQueryEndpoints(resource);
|
|
542
|
+
let inputType;
|
|
543
|
+
let fnBody;
|
|
544
|
+
if (endpoint.params && endpoint.body) {
|
|
545
|
+
inputType = `{ params: ${inferParams}; body: ${inferBody}; }`;
|
|
546
|
+
fnBody = `({ params, body }: ${inputType}) => apiClient.${name}(params, body)`;
|
|
547
|
+
} else if (endpoint.params) {
|
|
548
|
+
inputType = `${inferParams}`;
|
|
549
|
+
fnBody = `(params: ${inputType}) => apiClient.${name}(params)`;
|
|
550
|
+
} else if (endpoint.body) {
|
|
551
|
+
inputType = `${inferBody}`;
|
|
552
|
+
fnBody = `(body: ${inputType}) => apiClient.${name}(body)`;
|
|
553
|
+
} else {
|
|
554
|
+
inputType = "void";
|
|
555
|
+
fnBody = `() => apiClient.${name}()`;
|
|
467
556
|
}
|
|
468
|
-
const
|
|
557
|
+
const invalidate = resourceHasQueries ? `queryClient.invalidateQueries({ queryKey: queryKeys.${resource}.all });` : "";
|
|
469
558
|
return `/**
|
|
470
559
|
* ${endpoint.description || `Mutation hook for ${name}`}
|
|
471
560
|
* @tags ${endpoint.tags?.join(", ") || "none"}
|
|
472
561
|
*/
|
|
473
|
-
export function ${hookName}(
|
|
474
|
-
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}`;
|
|
562
|
+
export function ${hookName}(options?: {
|
|
563
|
+
onSuccess?: (data: ${inferResponse}, variables: ${inputType}, context: unknown) => void;
|
|
564
|
+
onError?: (error: Error, variables: ${inputType}, context: unknown) => void;
|
|
565
|
+
onSettled?: (data: ${inferResponse} | undefined, error: Error | null, variables: ${inputType}, context: unknown) => void;
|
|
566
|
+
onMutate?: (variables: ${inputType}) => Promise<unknown> | unknown;
|
|
567
|
+
}) {
|
|
568
|
+
${invalidate ? "const queryClient = useQueryClient();" : ""}
|
|
569
|
+
return useMutation({
|
|
570
|
+
mutationFn: ${fnBody},
|
|
571
|
+
onSuccess: (data, variables, context) => {
|
|
572
|
+
${invalidate}
|
|
573
|
+
options?.onSuccess?.(data, variables, context);
|
|
574
|
+
},
|
|
575
|
+
onError: options?.onError,
|
|
576
|
+
onSettled: options?.onSettled,
|
|
577
|
+
onMutate: options?.onMutate,
|
|
578
|
+
});
|
|
579
|
+
}`;
|
|
492
580
|
}
|
|
493
581
|
};
|
|
494
582
|
var ServerActionsGenerator = class extends BaseGenerator {
|
|
@@ -583,11 +671,13 @@ import type {
|
|
|
583
671
|
} from './types';
|
|
584
672
|
`;
|
|
585
673
|
const queries = [];
|
|
586
|
-
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
587
|
-
|
|
588
|
-
|
|
674
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(
|
|
675
|
+
([name, endpoint]) => {
|
|
676
|
+
if (this.isQueryEndpoint(endpoint)) {
|
|
677
|
+
queries.push(this.generateServerQuery(name, endpoint));
|
|
678
|
+
}
|
|
589
679
|
}
|
|
590
|
-
|
|
680
|
+
);
|
|
591
681
|
return imports + "\n" + queries.join("\n\n");
|
|
592
682
|
}
|
|
593
683
|
generateServerQuery(name, endpoint) {
|
|
@@ -600,9 +690,7 @@ import type {
|
|
|
600
690
|
const clientCallArgs = [];
|
|
601
691
|
if (signature.hasParams) clientCallArgs.push("params");
|
|
602
692
|
if (signature.hasQuery) clientCallArgs.push("query");
|
|
603
|
-
const cacheKeyParts = [
|
|
604
|
-
`'${name}'`
|
|
605
|
-
];
|
|
693
|
+
const cacheKeyParts = [`'${name}'`];
|
|
606
694
|
if (signature.hasParams) cacheKeyParts.push("JSON.stringify(params)");
|
|
607
695
|
if (signature.hasQuery) cacheKeyParts.push("JSON.stringify(query)");
|
|
608
696
|
return `/**
|
|
@@ -631,13 +719,18 @@ var TypesGenerator = class extends BaseGenerator {
|
|
|
631
719
|
await fs5.writeFile(outputPath, content, "utf-8");
|
|
632
720
|
}
|
|
633
721
|
generateContent() {
|
|
722
|
+
const outputPath = path6.join(this.context.config.outputDir, "types.ts");
|
|
723
|
+
const endpointsPath = path6.join(this.context.config.endpointsPath);
|
|
724
|
+
const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
634
725
|
return `// Auto-generated type definitions
|
|
635
726
|
// Do not edit this file manually
|
|
636
727
|
|
|
637
728
|
import type { z } from 'zod';
|
|
729
|
+
import { apiConfig } from '${relativePath}';
|
|
730
|
+
|
|
638
731
|
|
|
639
732
|
// Re-export endpoint configuration types
|
|
640
|
-
export type { APIConfig, APIEndpoint, HTTPMethod } from '@
|
|
733
|
+
export type { APIConfig, APIEndpoint, HTTPMethod } from '@cushin/api-codegen/schema';
|
|
641
734
|
|
|
642
735
|
/**
|
|
643
736
|
* Type helper to extract params schema from an endpoint
|
|
@@ -670,13 +763,35 @@ export type ExtractResponse<T> = T extends { response: infer R extends z.ZodType
|
|
|
670
763
|
/**
|
|
671
764
|
* Import your API config to get typed endpoints
|
|
672
765
|
*
|
|
673
|
-
* @example
|
|
674
|
-
* import { apiConfig } from './config/endpoints';
|
|
675
|
-
* export type APIEndpoints = typeof apiConfig.endpoints;
|
|
676
766
|
*/
|
|
677
|
-
export type APIEndpoints =
|
|
767
|
+
export type APIEndpoints = typeof apiConfig.endpoints;
|
|
768
|
+
|
|
769
|
+
${this.generateEndpointTypes()}
|
|
678
770
|
`;
|
|
679
771
|
}
|
|
772
|
+
generateEndpointTypes() {
|
|
773
|
+
const types = [];
|
|
774
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(([name, endpoint]) => {
|
|
775
|
+
const cap = this.capitalize(name);
|
|
776
|
+
if (endpoint.response)
|
|
777
|
+
types.push(
|
|
778
|
+
`export type ${cap}Response = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.response`)};`
|
|
779
|
+
);
|
|
780
|
+
if (endpoint.body)
|
|
781
|
+
types.push(
|
|
782
|
+
`export type ${cap}Input = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.body`)};`
|
|
783
|
+
);
|
|
784
|
+
if (endpoint.query)
|
|
785
|
+
types.push(
|
|
786
|
+
`export type ${cap}Query = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.query`)};`
|
|
787
|
+
);
|
|
788
|
+
if (endpoint.params)
|
|
789
|
+
types.push(
|
|
790
|
+
`export type ${cap}Params = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.params`)};`
|
|
791
|
+
);
|
|
792
|
+
});
|
|
793
|
+
return types.join("\n");
|
|
794
|
+
}
|
|
680
795
|
};
|
|
681
796
|
var ClientGenerator = class extends BaseGenerator {
|
|
682
797
|
async generate() {
|
|
@@ -699,11 +814,15 @@ var ClientGenerator = class extends BaseGenerator {
|
|
|
699
814
|
}
|
|
700
815
|
generateClientContent() {
|
|
701
816
|
const useClientDirective = this.context.config.options?.useClientDirective ?? true;
|
|
817
|
+
const outputPath = path6.join(this.context.config.outputDir, "types.ts");
|
|
818
|
+
const endpointsPath = path6.join(this.context.config.endpointsPath);
|
|
819
|
+
const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
702
820
|
return `${useClientDirective ? "'use client';\n" : ""}
|
|
703
821
|
import { createAPIClient } from '@cushin/api-codegen/client';
|
|
704
822
|
import type { AuthCallbacks } from '@cushin/api-codegen/client';
|
|
705
|
-
import { apiConfig } from '
|
|
823
|
+
import { apiConfig } from '${relativePath}';
|
|
706
824
|
import type { APIEndpoints } from './types';
|
|
825
|
+
import { z } from 'zod';
|
|
707
826
|
|
|
708
827
|
// Type-safe API client methods
|
|
709
828
|
type APIClientMethods = {
|
|
@@ -733,11 +852,15 @@ type APIClientMethods = {
|
|
|
733
852
|
};
|
|
734
853
|
|
|
735
854
|
// Export singleton instance (will be initialized later)
|
|
736
|
-
export let
|
|
855
|
+
export let baseClient: APIClientMethods & {
|
|
737
856
|
refreshAuth: () => Promise<void>;
|
|
738
857
|
updateAuthCallbacks: (callbacks: AuthCallbacks) => void;
|
|
739
858
|
};
|
|
740
859
|
|
|
860
|
+
export const apiClient = {
|
|
861
|
+
${this.generateApiClientMethods()}
|
|
862
|
+
};
|
|
863
|
+
|
|
741
864
|
/**
|
|
742
865
|
* Initialize API client with auth callbacks
|
|
743
866
|
* Call this function in your auth provider setup
|
|
@@ -757,8 +880,8 @@ export let apiClient: APIClientMethods & {
|
|
|
757
880
|
* initializeAPIClient(authCallbacks);
|
|
758
881
|
*/
|
|
759
882
|
export const initializeAPIClient = (authCallbacks: AuthCallbacks) => {
|
|
760
|
-
|
|
761
|
-
return
|
|
883
|
+
baseClient = createAPIClient(apiConfig, authCallbacks) as any;
|
|
884
|
+
return baseClient;
|
|
762
885
|
};
|
|
763
886
|
|
|
764
887
|
// Export for custom usage
|
|
@@ -805,6 +928,202 @@ type APIClientMethods = {
|
|
|
805
928
|
export const serverClient = createAPIClient(apiConfig) as APIClientMethods;
|
|
806
929
|
`;
|
|
807
930
|
}
|
|
931
|
+
generateApiClientMethods() {
|
|
932
|
+
const methods = [];
|
|
933
|
+
Object.entries(this.context.apiConfig.endpoints).forEach(([name, endpoint]) => {
|
|
934
|
+
const inferParams = this.inferNonNull(
|
|
935
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
936
|
+
);
|
|
937
|
+
const inferQuery = this.inferNonNull(
|
|
938
|
+
`typeof apiConfig.endpoints.${name}.query`
|
|
939
|
+
);
|
|
940
|
+
const inferBody = this.inferNonNull(
|
|
941
|
+
`typeof apiConfig.endpoints.${name}.body`
|
|
942
|
+
);
|
|
943
|
+
const inferResponse = this.inferNonNull(
|
|
944
|
+
`typeof apiConfig.endpoints.${name}.response`
|
|
945
|
+
);
|
|
946
|
+
if (endpoint.method === "GET") {
|
|
947
|
+
if (endpoint.params && endpoint.query) {
|
|
948
|
+
methods.push(` ${name}: (params: ${inferParams}, query?: ${inferQuery}): Promise<${inferResponse}> =>
|
|
949
|
+
(baseClient as any).${name}(params, query),`);
|
|
950
|
+
} else if (endpoint.params) {
|
|
951
|
+
methods.push(` ${name}: (params: ${inferParams}): Promise<${inferResponse}> =>
|
|
952
|
+
(baseClient as any).${name}(params),`);
|
|
953
|
+
} else if (endpoint.query) {
|
|
954
|
+
methods.push(` ${name}: (query?: ${inferQuery}): Promise<${inferResponse}> =>
|
|
955
|
+
(baseClient as any).${name}(query),`);
|
|
956
|
+
} else {
|
|
957
|
+
methods.push(` ${name}: (): Promise<${inferResponse}> =>
|
|
958
|
+
(baseClient as any).${name}(),`);
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
if (endpoint.params && endpoint.body) {
|
|
962
|
+
methods.push(` ${name}: (params: ${inferParams}, body: ${inferBody}): Promise<${inferResponse}> =>
|
|
963
|
+
(baseClient as any).${name}(params, body),`);
|
|
964
|
+
} else if (endpoint.params) {
|
|
965
|
+
methods.push(` ${name}: (params: ${inferParams}): Promise<${inferResponse}> =>
|
|
966
|
+
(baseClient as any).${name}(params),`);
|
|
967
|
+
} else if (endpoint.body) {
|
|
968
|
+
methods.push(` ${name}: (body: ${inferBody}): Promise<${inferResponse}> =>
|
|
969
|
+
(baseClient as any).${name}(body),`);
|
|
970
|
+
} else {
|
|
971
|
+
methods.push(` ${name}: (): Promise<${inferResponse}> =>
|
|
972
|
+
(baseClient as any).${name}(),`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
return methods.join("\n");
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
var QueryKeysGenerator = class extends BaseGenerator {
|
|
980
|
+
async generate() {
|
|
981
|
+
const content = this.generateContent();
|
|
982
|
+
const outputPath = path6.join(
|
|
983
|
+
this.context.config.outputDir,
|
|
984
|
+
"query-keys.ts"
|
|
985
|
+
);
|
|
986
|
+
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
987
|
+
await fs5.writeFile(outputPath, content, "utf-8");
|
|
988
|
+
}
|
|
989
|
+
generateContent() {
|
|
990
|
+
const content = `// Auto-generated query keys
|
|
991
|
+
import { z } from 'zod';
|
|
992
|
+
import { apiConfig } from '../config/endpoints';
|
|
993
|
+
|
|
994
|
+
export const queryKeys = {
|
|
995
|
+
${this.generateQueryKeysContent()}
|
|
996
|
+
} as const;
|
|
997
|
+
`;
|
|
998
|
+
return content;
|
|
999
|
+
}
|
|
1000
|
+
generateQueryKeysContent() {
|
|
1001
|
+
const resourceGroups = this.groupEndpointsByResource();
|
|
1002
|
+
const keys = [];
|
|
1003
|
+
Object.entries(resourceGroups).forEach(([resource, endpoints]) => {
|
|
1004
|
+
const queryEndpoints = endpoints.filter(
|
|
1005
|
+
({ endpoint }) => endpoint.method === "GET"
|
|
1006
|
+
);
|
|
1007
|
+
if (queryEndpoints.length === 0) return;
|
|
1008
|
+
const resourceKeys = [` all: ['${resource}'] as const,`];
|
|
1009
|
+
const added = /* @__PURE__ */ new Set();
|
|
1010
|
+
queryEndpoints.forEach(({ name, endpoint }) => {
|
|
1011
|
+
const keyName = this.getEndpointKeyName(name);
|
|
1012
|
+
if (added.has(keyName)) return;
|
|
1013
|
+
const inferParams = this.inferNonNull(
|
|
1014
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
1015
|
+
);
|
|
1016
|
+
const inferQuery = this.inferNonNull(
|
|
1017
|
+
`typeof apiConfig.endpoints.${name}.query`
|
|
1018
|
+
);
|
|
1019
|
+
if (endpoint.params || endpoint.query) {
|
|
1020
|
+
const params = [];
|
|
1021
|
+
if (endpoint.params) params.push(`params?: ${inferParams}`);
|
|
1022
|
+
if (endpoint.query) params.push(`query?: ${inferQuery}`);
|
|
1023
|
+
resourceKeys.push(` ${keyName}: (${params.join(", ")}) =>
|
|
1024
|
+
['${resource}', '${keyName}', ${endpoint.params ? "params" : "undefined"}, ${endpoint.query ? "query" : "undefined"}] as const,`);
|
|
1025
|
+
} else {
|
|
1026
|
+
resourceKeys.push(
|
|
1027
|
+
` ${keyName}: () => ['${resource}', '${keyName}'] as const,`
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
added.add(keyName);
|
|
1031
|
+
});
|
|
1032
|
+
keys.push(` ${resource}: {
|
|
1033
|
+
${resourceKeys.join("\n")}
|
|
1034
|
+
},`);
|
|
1035
|
+
});
|
|
1036
|
+
return keys.join("\n");
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
var QueryOptionsGenerator = class extends BaseGenerator {
|
|
1040
|
+
async generate() {
|
|
1041
|
+
const content = this.generateContent();
|
|
1042
|
+
const outputPath = path6.join(
|
|
1043
|
+
this.context.config.outputDir,
|
|
1044
|
+
"query-options.ts"
|
|
1045
|
+
);
|
|
1046
|
+
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
1047
|
+
await fs5.writeFile(outputPath, content, "utf-8");
|
|
1048
|
+
}
|
|
1049
|
+
generateContent() {
|
|
1050
|
+
const content = `// Auto-generated query options
|
|
1051
|
+
import { queryOptions } from '@tanstack/react-query';
|
|
1052
|
+
import { apiClient } from './api-client';
|
|
1053
|
+
import { queryKeys } from './query-keys';
|
|
1054
|
+
import { z } from 'zod';
|
|
1055
|
+
import { apiConfig } from '../config/endpoints';
|
|
1056
|
+
|
|
1057
|
+
${this.generateQueryOptionsContent()}
|
|
1058
|
+
|
|
1059
|
+
export const apiQueryOptions = {
|
|
1060
|
+
${this.generateQueryOptionsExports()}
|
|
1061
|
+
} as const;
|
|
1062
|
+
`;
|
|
1063
|
+
return content;
|
|
1064
|
+
}
|
|
1065
|
+
generateQueryOptionsContent() {
|
|
1066
|
+
const groups = this.groupEndpointsByResource();
|
|
1067
|
+
const options = [];
|
|
1068
|
+
Object.entries(groups).forEach(([resource, endpoints]) => {
|
|
1069
|
+
const queries = endpoints.filter(
|
|
1070
|
+
({ endpoint }) => endpoint.method === "GET"
|
|
1071
|
+
);
|
|
1072
|
+
if (queries.length === 0) return;
|
|
1073
|
+
const resourceOptions = [];
|
|
1074
|
+
queries.forEach(({ name, endpoint }) => {
|
|
1075
|
+
const optionName = this.getEndpointKeyName(name);
|
|
1076
|
+
const inferParams = this.inferNonNull(
|
|
1077
|
+
`typeof apiConfig.endpoints.${name}.params`
|
|
1078
|
+
);
|
|
1079
|
+
const inferQuery = this.inferNonNull(
|
|
1080
|
+
`typeof apiConfig.endpoints.${name}.query`
|
|
1081
|
+
);
|
|
1082
|
+
const inferResponse = this.inferNonNull(
|
|
1083
|
+
`typeof apiConfig.endpoints.${name}.response`
|
|
1084
|
+
);
|
|
1085
|
+
const params = [];
|
|
1086
|
+
let apiCall = "";
|
|
1087
|
+
if (endpoint.params && endpoint.query) {
|
|
1088
|
+
params.push(`params: ${inferParams}`, `filters?: ${inferQuery}`);
|
|
1089
|
+
apiCall = `apiClient.${name}(params, filters)`;
|
|
1090
|
+
} else if (endpoint.params) {
|
|
1091
|
+
params.push(`params: ${inferParams}`);
|
|
1092
|
+
apiCall = `apiClient.${name}(params)`;
|
|
1093
|
+
} else if (endpoint.query) {
|
|
1094
|
+
params.push(`filters?: ${inferQuery}`);
|
|
1095
|
+
apiCall = `apiClient.${name}(filters)`;
|
|
1096
|
+
} else {
|
|
1097
|
+
apiCall = `apiClient.${name}()`;
|
|
1098
|
+
}
|
|
1099
|
+
const keyCall = this.generateQueryKeyCall(resource, name, endpoint);
|
|
1100
|
+
resourceOptions.push(` ${optionName}: (${params.join(", ")}) =>
|
|
1101
|
+
queryOptions({
|
|
1102
|
+
queryKey: ${keyCall},
|
|
1103
|
+
queryFn: (): Promise<${inferResponse}> => ${apiCall},
|
|
1104
|
+
staleTime: 1000 * 60 * 5,
|
|
1105
|
+
}),`);
|
|
1106
|
+
});
|
|
1107
|
+
options.push(
|
|
1108
|
+
`const ${resource}QueryOptions = {
|
|
1109
|
+
${resourceOptions.join("\n")}
|
|
1110
|
+
};
|
|
1111
|
+
`
|
|
1112
|
+
);
|
|
1113
|
+
});
|
|
1114
|
+
return options.join("\n");
|
|
1115
|
+
}
|
|
1116
|
+
generateQueryOptionsExports() {
|
|
1117
|
+
const groups = this.groupEndpointsByResource();
|
|
1118
|
+
const exports$1 = [];
|
|
1119
|
+
Object.keys(groups).forEach((resource) => {
|
|
1120
|
+
const hasQueries = groups[resource].some(
|
|
1121
|
+
({ endpoint }) => endpoint.method === "GET"
|
|
1122
|
+
);
|
|
1123
|
+
if (hasQueries) exports$1.push(` ${resource}: ${resource}QueryOptions,`);
|
|
1124
|
+
});
|
|
1125
|
+
return exports$1.join("\n");
|
|
1126
|
+
}
|
|
808
1127
|
};
|
|
809
1128
|
|
|
810
1129
|
// src/generators/index.ts
|
|
@@ -825,6 +1144,8 @@ var CodeGenerator = class {
|
|
|
825
1144
|
generators.push(new ClientGenerator(this.context));
|
|
826
1145
|
}
|
|
827
1146
|
if (this.context.config.generateHooks) {
|
|
1147
|
+
generators.push(new QueryKeysGenerator(this.context));
|
|
1148
|
+
generators.push(new QueryOptionsGenerator(this.context));
|
|
828
1149
|
generators.push(new HooksGenerator(this.context));
|
|
829
1150
|
}
|
|
830
1151
|
if (this.context.config.generateServerActions && this.context.config.provider === "nextjs") {
|
|
@@ -836,8 +1157,6 @@ var CodeGenerator = class {
|
|
|
836
1157
|
return generators;
|
|
837
1158
|
}
|
|
838
1159
|
};
|
|
839
|
-
|
|
840
|
-
// src/core/codegen.ts
|
|
841
1160
|
var CodegenCore = class {
|
|
842
1161
|
constructor(config) {
|
|
843
1162
|
this.config = config;
|
|
@@ -853,8 +1172,10 @@ var CodegenCore = class {
|
|
|
853
1172
|
}
|
|
854
1173
|
async loadAPIConfig() {
|
|
855
1174
|
try {
|
|
856
|
-
const
|
|
857
|
-
|
|
1175
|
+
const jiti = createJiti(fileURLToPath(import.meta.url), {
|
|
1176
|
+
interopDefault: true
|
|
1177
|
+
});
|
|
1178
|
+
const module = await jiti.import(this.config.endpointsPath);
|
|
858
1179
|
const apiConfig = module.apiConfig || module.default?.apiConfig || module.default || module;
|
|
859
1180
|
if (!apiConfig || !apiConfig.endpoints) {
|
|
860
1181
|
throw new Error(
|