@cushin/api-codegen 1.0.3 → 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 CHANGED
@@ -119,6 +119,49 @@ var BaseGenerator = class {
119
119
  return `return apiClient.${name}();`;
120
120
  }
121
121
  }
122
+ inferNonNull(expr) {
123
+ return `z.infer<NonNullable<${expr}>>`;
124
+ }
125
+ toCamelCase(str) {
126
+ return str.toLowerCase().replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toLowerCase());
127
+ }
128
+ getResourceFromEndpoint(_name, endpoint) {
129
+ const tag = endpoint.tags?.find((t) => t !== "query" && t !== "mutation");
130
+ if (tag) return this.toCamelCase(tag);
131
+ const match = endpoint.path.match(/^\/([^/]+)/);
132
+ return match ? this.toCamelCase(match[1]) : "general";
133
+ }
134
+ groupEndpointsByResource() {
135
+ const groups = {};
136
+ Object.entries(this.context.apiConfig.endpoints).forEach(
137
+ ([name, endpoint]) => {
138
+ const res = this.getResourceFromEndpoint(name, endpoint);
139
+ if (!groups[res]) groups[res] = [];
140
+ groups[res].push({ name, endpoint });
141
+ }
142
+ );
143
+ return groups;
144
+ }
145
+ resourceHasQueryEndpoints(resource) {
146
+ return this.groupEndpointsByResource()[resource]?.some(
147
+ ({ endpoint }) => endpoint.method === "GET"
148
+ ) ?? false;
149
+ }
150
+ getEndpointKeyName(name) {
151
+ return name.startsWith("get") ? name[3].toLowerCase() + name.slice(4) : name;
152
+ }
153
+ generateQueryKeyCall(resource, name, endpoint) {
154
+ const key = this.getEndpointKeyName(name);
155
+ const args = [];
156
+ if (endpoint.params) args.push("params");
157
+ if (endpoint.query) args.push("filters");
158
+ return args.length ? `queryKeys.${resource}.${key}(${args.join(", ")})` : `queryKeys.${resource}.${key}()`;
159
+ }
160
+ hasQueryOptions() {
161
+ return Object.values(this.context.apiConfig.endpoints).some(
162
+ (e) => e.method === "GET"
163
+ );
164
+ }
122
165
  };
123
166
 
124
167
  // src/generators/hooks.ts
@@ -131,7 +174,10 @@ var HooksGenerator = class extends BaseGenerator {
131
174
  }
132
175
  generateContent() {
133
176
  const useClientDirective = this.context.config.options?.useClientDirective ?? true;
134
- const imports = `${useClientDirective ? "'use client';\n" : ""}
177
+ const outputPath = path6.join(this.context.config.outputDir, "types.ts");
178
+ const endpointsPath = path6.join(this.context.config.endpointsPath);
179
+ const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
180
+ const content = `${useClientDirective ? "'use client';\n" : ""}
135
181
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
136
182
  import type {
137
183
  UseQueryOptions,
@@ -146,85 +192,126 @@ import type {
146
192
  ExtractQuery,
147
193
  ExtractResponse
148
194
  } from './types';
195
+ import { queryKeys } from './query-keys';
196
+ ${this.hasQueryOptions() ? "import { apiQueryOptions } from './query-options';" : ""}
197
+ import { z } from 'zod';
198
+ import { apiConfig } from '${relativePath}';
199
+
200
+ ${this.generateQueryHooks()}
201
+ ${this.generateMutationHooks()}
149
202
  `;
203
+ return content;
204
+ }
205
+ generateQueryHooks() {
150
206
  const hooks = [];
151
- Object.entries(this.context.apiConfig.endpoints).forEach(([name, endpoint]) => {
152
- if (this.isQueryEndpoint(endpoint)) {
153
- hooks.push(this.generateQueryHook(name, endpoint));
154
- } else {
155
- hooks.push(this.generateMutationHook(name, endpoint));
207
+ Object.entries(this.context.apiConfig.endpoints).forEach(
208
+ ([name, endpoint]) => {
209
+ if (endpoint.method === "GET")
210
+ hooks.push(this.generateQueryHook(name, endpoint));
156
211
  }
157
- });
158
- return imports + "\n" + hooks.join("\n\n");
212
+ );
213
+ return hooks.join("\n\n");
159
214
  }
160
215
  generateQueryHook(name, endpoint) {
161
- const hookPrefix = this.context.config.options?.hookPrefix || "use";
162
- const hookName = `${hookPrefix}${this.capitalize(name)}`;
163
- const signature = this.getEndpointSignature(name, endpoint);
216
+ const hookName = `use${this.capitalize(name)}`;
217
+ const resource = this.getResourceFromEndpoint(name, endpoint);
218
+ const optionName = this.getEndpointKeyName(name);
219
+ const inferParams = this.inferNonNull(
220
+ `typeof apiConfig.endpoints.${name}.params`
221
+ );
222
+ const inferQuery = this.inferNonNull(
223
+ `typeof apiConfig.endpoints.${name}.query`
224
+ );
225
+ const inferResponse = this.inferNonNull(
226
+ `typeof apiConfig.endpoints.${name}.response`
227
+ );
228
+ const params = [];
229
+ const optionParams = [];
164
230
  const queryTags = this.getQueryTags(endpoint);
165
- const paramDef = signature.hasParams ? `params: ${signature.paramType}` : "";
166
- const queryDef = signature.hasQuery ? `query?: ${signature.queryType}` : "";
167
- const optionsDef = `options?: Omit<UseQueryOptions<${signature.responseType}, Error, ${signature.responseType}, QueryKey>, 'queryKey' | 'queryFn'>`;
168
- const paramsList = [paramDef, queryDef, optionsDef].filter(Boolean).join(",\n ");
169
- const queryKeyParts = [
170
- ...queryTags.map((tag) => `'${tag}'`),
171
- signature.hasParams ? "params" : "undefined",
172
- signature.hasQuery ? "query" : "undefined"
173
- ];
174
- const clientCallArgs = [];
175
- if (signature.hasParams) clientCallArgs.push("params");
176
- if (signature.hasQuery) clientCallArgs.push("query");
231
+ if (endpoint.params) {
232
+ params.push(`params: ${inferParams}`);
233
+ optionParams.push("params");
234
+ }
235
+ if (endpoint.query) {
236
+ params.push(`filters?: ${inferQuery}`);
237
+ optionParams.push("filters");
238
+ }
239
+ params.push(`options?: {
240
+ enabled?: boolean;
241
+ select?: <TData = ${inferResponse}>(data: ${inferResponse}) => TData;
242
+ }`);
177
243
  return `/**
178
244
  * ${endpoint.description || `Query hook for ${name}`}
179
245
  * @tags ${queryTags.join(", ") || "none"}
180
246
  */
181
- export function ${hookName}(
182
- ${paramsList}
183
- ) {
247
+ export function ${hookName}(${params.join(",\n ")}) {
184
248
  return useQuery({
185
- queryKey: [${queryKeyParts.join(", ")}] as const,
186
- queryFn: () => apiClient.${name}(${clientCallArgs.join(", ")}),
249
+ ...apiQueryOptions.${resource}.${optionName}(${optionParams.join(", ")}),
187
250
  ...options,
188
251
  });
189
252
  }`;
190
253
  }
254
+ generateMutationHooks() {
255
+ const hooks = [];
256
+ Object.entries(this.context.apiConfig.endpoints).forEach(
257
+ ([name, endpoint]) => {
258
+ if (endpoint.method !== "GET")
259
+ hooks.push(this.generateMutationHook(name, endpoint));
260
+ }
261
+ );
262
+ return hooks.join("\n\n");
263
+ }
191
264
  generateMutationHook(name, endpoint) {
192
- const hookPrefix = this.context.config.options?.hookPrefix || "use";
193
- const hookName = `${hookPrefix}${this.capitalize(name)}`;
194
- const signature = this.getEndpointSignature(name, endpoint);
195
- const invalidationTags = this.getInvalidationTags(endpoint);
196
- let inputType = "void";
197
- if (signature.hasParams && signature.hasBody) {
198
- inputType = `{ params: ${signature.paramType}; body: ${signature.bodyType} }`;
199
- } else if (signature.hasParams) {
200
- inputType = signature.paramType;
201
- } else if (signature.hasBody) {
202
- inputType = signature.bodyType;
265
+ const hookName = `use${this.capitalize(name)}`;
266
+ const resource = this.getResourceFromEndpoint(name, endpoint);
267
+ const inferParams = this.inferNonNull(
268
+ `typeof apiConfig.endpoints.${name}.params`
269
+ );
270
+ const inferBody = this.inferNonNull(
271
+ `typeof apiConfig.endpoints.${name}.body`
272
+ );
273
+ const inferResponse = this.inferNonNull(
274
+ `typeof apiConfig.endpoints.${name}.response`
275
+ );
276
+ const resourceHasQueries = this.resourceHasQueryEndpoints(resource);
277
+ let inputType;
278
+ let fnBody;
279
+ if (endpoint.params && endpoint.body) {
280
+ inputType = `{ params: ${inferParams}; body: ${inferBody}; }`;
281
+ fnBody = `({ params, body }: ${inputType}) => apiClient.${name}(params, body)`;
282
+ } else if (endpoint.params) {
283
+ inputType = `${inferParams}`;
284
+ fnBody = `(params: ${inputType}) => apiClient.${name}(params)`;
285
+ } else if (endpoint.body) {
286
+ inputType = `${inferBody}`;
287
+ fnBody = `(body: ${inputType}) => apiClient.${name}(body)`;
288
+ } else {
289
+ inputType = "void";
290
+ fnBody = `() => apiClient.${name}()`;
203
291
  }
204
- const invalidationQueries = invalidationTags.length > 0 ? invalidationTags.map((tag) => ` queryClient.invalidateQueries({ queryKey: ['${tag}'] });`).join("\n") : " // No automatic invalidations";
292
+ const invalidate = resourceHasQueries ? `queryClient.invalidateQueries({ queryKey: queryKeys.${resource}.all });` : "";
205
293
  return `/**
206
294
  * ${endpoint.description || `Mutation hook for ${name}`}
207
295
  * @tags ${endpoint.tags?.join(", ") || "none"}
208
296
  */
209
- export function ${hookName}(
210
- options?: Omit<UseMutationOptions<${signature.responseType}, Error, ${inputType}>, 'mutationFn'>
211
- ) {
212
- const queryClient = useQueryClient();
213
-
214
- return useMutation({
215
- mutationFn: ${inputType === "void" ? "() => {" : "(input) => {"}
216
- ${this.generateMutationCall(name, signature.hasParams, signature.hasBody)}
217
- },
218
- onSuccess: (data, variables, context) => {
219
- // Invalidate related queries
220
- ${invalidationQueries}
221
-
222
- // Call user's onSuccess if provided
223
- options?.onSuccess?.(data, variables, context);
224
- },
225
- ...options,
226
- });
227
- }`;
297
+ export function ${hookName}(options?: {
298
+ onSuccess?: (data: ${inferResponse}, variables: ${inputType}, context: unknown) => void;
299
+ onError?: (error: Error, variables: ${inputType}, context: unknown) => void;
300
+ onSettled?: (data: ${inferResponse} | undefined, error: Error | null, variables: ${inputType}, context: unknown) => void;
301
+ onMutate?: (variables: ${inputType}) => Promise<unknown> | unknown;
302
+ }) {
303
+ ${invalidate ? "const queryClient = useQueryClient();" : ""}
304
+ return useMutation({
305
+ mutationFn: ${fnBody},
306
+ onSuccess: (data, variables, context) => {
307
+ ${invalidate}
308
+ options?.onSuccess?.(data, variables, context);
309
+ },
310
+ onError: options?.onError,
311
+ onSettled: options?.onSettled,
312
+ onMutate: options?.onMutate,
313
+ });
314
+ }`;
228
315
  }
229
316
  };
230
317
  var ServerActionsGenerator = class extends BaseGenerator {
@@ -319,11 +406,13 @@ import type {
319
406
  } from './types';
320
407
  `;
321
408
  const queries = [];
322
- Object.entries(this.context.apiConfig.endpoints).forEach(([name, endpoint]) => {
323
- if (this.isQueryEndpoint(endpoint)) {
324
- queries.push(this.generateServerQuery(name, endpoint));
409
+ Object.entries(this.context.apiConfig.endpoints).forEach(
410
+ ([name, endpoint]) => {
411
+ if (this.isQueryEndpoint(endpoint)) {
412
+ queries.push(this.generateServerQuery(name, endpoint));
413
+ }
325
414
  }
326
- });
415
+ );
327
416
  return imports + "\n" + queries.join("\n\n");
328
417
  }
329
418
  generateServerQuery(name, endpoint) {
@@ -336,9 +425,7 @@ import type {
336
425
  const clientCallArgs = [];
337
426
  if (signature.hasParams) clientCallArgs.push("params");
338
427
  if (signature.hasQuery) clientCallArgs.push("query");
339
- const cacheKeyParts = [
340
- `'${name}'`
341
- ];
428
+ const cacheKeyParts = [`'${name}'`];
342
429
  if (signature.hasParams) cacheKeyParts.push("JSON.stringify(params)");
343
430
  if (signature.hasQuery) cacheKeyParts.push("JSON.stringify(query)");
344
431
  return `/**
@@ -367,13 +454,18 @@ var TypesGenerator = class extends BaseGenerator {
367
454
  await fs5.writeFile(outputPath, content, "utf-8");
368
455
  }
369
456
  generateContent() {
457
+ const outputPath = path6.join(this.context.config.outputDir, "types.ts");
458
+ const endpointsPath = path6.join(this.context.config.endpointsPath);
459
+ const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
370
460
  return `// Auto-generated type definitions
371
461
  // Do not edit this file manually
372
462
 
373
463
  import type { z } from 'zod';
464
+ import { apiConfig } from '${relativePath}';
465
+
374
466
 
375
467
  // Re-export endpoint configuration types
376
- export type { APIConfig, APIEndpoint, HTTPMethod } from '@vietbus/api-codegen/config';
468
+ export type { APIConfig, APIEndpoint, HTTPMethod } from '@cushin/api-codegen/schema';
377
469
 
378
470
  /**
379
471
  * Type helper to extract params schema from an endpoint
@@ -406,13 +498,35 @@ export type ExtractResponse<T> = T extends { response: infer R extends z.ZodType
406
498
  /**
407
499
  * Import your API config to get typed endpoints
408
500
  *
409
- * @example
410
- * import { apiConfig } from './config/endpoints';
411
- * export type APIEndpoints = typeof apiConfig.endpoints;
412
501
  */
413
- export type APIEndpoints = Record<string, any>;
502
+ export type APIEndpoints = typeof apiConfig.endpoints;
503
+
504
+ ${this.generateEndpointTypes()}
414
505
  `;
415
506
  }
507
+ generateEndpointTypes() {
508
+ const types = [];
509
+ Object.entries(this.context.apiConfig.endpoints).forEach(([name, endpoint]) => {
510
+ const cap = this.capitalize(name);
511
+ if (endpoint.response)
512
+ types.push(
513
+ `export type ${cap}Response = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.response`)};`
514
+ );
515
+ if (endpoint.body)
516
+ types.push(
517
+ `export type ${cap}Input = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.body`)};`
518
+ );
519
+ if (endpoint.query)
520
+ types.push(
521
+ `export type ${cap}Query = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.query`)};`
522
+ );
523
+ if (endpoint.params)
524
+ types.push(
525
+ `export type ${cap}Params = ${this.inferNonNull(`typeof apiConfig.endpoints.${name}.params`)};`
526
+ );
527
+ });
528
+ return types.join("\n");
529
+ }
416
530
  };
417
531
  var ClientGenerator = class extends BaseGenerator {
418
532
  async generate() {
@@ -435,11 +549,15 @@ var ClientGenerator = class extends BaseGenerator {
435
549
  }
436
550
  generateClientContent() {
437
551
  const useClientDirective = this.context.config.options?.useClientDirective ?? true;
552
+ const outputPath = path6.join(this.context.config.outputDir, "types.ts");
553
+ const endpointsPath = path6.join(this.context.config.endpointsPath);
554
+ const relativePath = path6.relative(path6.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
438
555
  return `${useClientDirective ? "'use client';\n" : ""}
439
556
  import { createAPIClient } from '@cushin/api-codegen/client';
440
557
  import type { AuthCallbacks } from '@cushin/api-codegen/client';
441
- import { apiConfig } from '../config/endpoints';
558
+ import { apiConfig } from '${relativePath}';
442
559
  import type { APIEndpoints } from './types';
560
+ import { z } from 'zod';
443
561
 
444
562
  // Type-safe API client methods
445
563
  type APIClientMethods = {
@@ -469,11 +587,15 @@ type APIClientMethods = {
469
587
  };
470
588
 
471
589
  // Export singleton instance (will be initialized later)
472
- export let apiClient: APIClientMethods & {
590
+ export let baseClient: APIClientMethods & {
473
591
  refreshAuth: () => Promise<void>;
474
592
  updateAuthCallbacks: (callbacks: AuthCallbacks) => void;
475
593
  };
476
594
 
595
+ export const apiClient = {
596
+ ${this.generateApiClientMethods()}
597
+ };
598
+
477
599
  /**
478
600
  * Initialize API client with auth callbacks
479
601
  * Call this function in your auth provider setup
@@ -493,8 +615,8 @@ export let apiClient: APIClientMethods & {
493
615
  * initializeAPIClient(authCallbacks);
494
616
  */
495
617
  export const initializeAPIClient = (authCallbacks: AuthCallbacks) => {
496
- apiClient = createAPIClient(apiConfig, authCallbacks) as any;
497
- return apiClient;
618
+ baseClient = createAPIClient(apiConfig, authCallbacks) as any;
619
+ return baseClient;
498
620
  };
499
621
 
500
622
  // Export for custom usage
@@ -541,6 +663,202 @@ type APIClientMethods = {
541
663
  export const serverClient = createAPIClient(apiConfig) as APIClientMethods;
542
664
  `;
543
665
  }
666
+ generateApiClientMethods() {
667
+ const methods = [];
668
+ Object.entries(this.context.apiConfig.endpoints).forEach(([name, endpoint]) => {
669
+ const inferParams = this.inferNonNull(
670
+ `typeof apiConfig.endpoints.${name}.params`
671
+ );
672
+ const inferQuery = this.inferNonNull(
673
+ `typeof apiConfig.endpoints.${name}.query`
674
+ );
675
+ const inferBody = this.inferNonNull(
676
+ `typeof apiConfig.endpoints.${name}.body`
677
+ );
678
+ const inferResponse = this.inferNonNull(
679
+ `typeof apiConfig.endpoints.${name}.response`
680
+ );
681
+ if (endpoint.method === "GET") {
682
+ if (endpoint.params && endpoint.query) {
683
+ methods.push(` ${name}: (params: ${inferParams}, query?: ${inferQuery}): Promise<${inferResponse}> =>
684
+ (baseClient as any).${name}(params, query),`);
685
+ } else if (endpoint.params) {
686
+ methods.push(` ${name}: (params: ${inferParams}): Promise<${inferResponse}> =>
687
+ (baseClient as any).${name}(params),`);
688
+ } else if (endpoint.query) {
689
+ methods.push(` ${name}: (query?: ${inferQuery}): Promise<${inferResponse}> =>
690
+ (baseClient as any).${name}(query),`);
691
+ } else {
692
+ methods.push(` ${name}: (): Promise<${inferResponse}> =>
693
+ (baseClient as any).${name}(),`);
694
+ }
695
+ } else {
696
+ if (endpoint.params && endpoint.body) {
697
+ methods.push(` ${name}: (params: ${inferParams}, body: ${inferBody}): Promise<${inferResponse}> =>
698
+ (baseClient as any).${name}(params, body),`);
699
+ } else if (endpoint.params) {
700
+ methods.push(` ${name}: (params: ${inferParams}): Promise<${inferResponse}> =>
701
+ (baseClient as any).${name}(params),`);
702
+ } else if (endpoint.body) {
703
+ methods.push(` ${name}: (body: ${inferBody}): Promise<${inferResponse}> =>
704
+ (baseClient as any).${name}(body),`);
705
+ } else {
706
+ methods.push(` ${name}: (): Promise<${inferResponse}> =>
707
+ (baseClient as any).${name}(),`);
708
+ }
709
+ }
710
+ });
711
+ return methods.join("\n");
712
+ }
713
+ };
714
+ var QueryKeysGenerator = class extends BaseGenerator {
715
+ async generate() {
716
+ const content = this.generateContent();
717
+ const outputPath = path6.join(
718
+ this.context.config.outputDir,
719
+ "query-keys.ts"
720
+ );
721
+ await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
722
+ await fs5.writeFile(outputPath, content, "utf-8");
723
+ }
724
+ generateContent() {
725
+ const content = `// Auto-generated query keys
726
+ import { z } from 'zod';
727
+ import { apiConfig } from '../config/endpoints';
728
+
729
+ export const queryKeys = {
730
+ ${this.generateQueryKeysContent()}
731
+ } as const;
732
+ `;
733
+ return content;
734
+ }
735
+ generateQueryKeysContent() {
736
+ const resourceGroups = this.groupEndpointsByResource();
737
+ const keys = [];
738
+ Object.entries(resourceGroups).forEach(([resource, endpoints]) => {
739
+ const queryEndpoints = endpoints.filter(
740
+ ({ endpoint }) => endpoint.method === "GET"
741
+ );
742
+ if (queryEndpoints.length === 0) return;
743
+ const resourceKeys = [` all: ['${resource}'] as const,`];
744
+ const added = /* @__PURE__ */ new Set();
745
+ queryEndpoints.forEach(({ name, endpoint }) => {
746
+ const keyName = this.getEndpointKeyName(name);
747
+ if (added.has(keyName)) return;
748
+ const inferParams = this.inferNonNull(
749
+ `typeof apiConfig.endpoints.${name}.params`
750
+ );
751
+ const inferQuery = this.inferNonNull(
752
+ `typeof apiConfig.endpoints.${name}.query`
753
+ );
754
+ if (endpoint.params || endpoint.query) {
755
+ const params = [];
756
+ if (endpoint.params) params.push(`params?: ${inferParams}`);
757
+ if (endpoint.query) params.push(`query?: ${inferQuery}`);
758
+ resourceKeys.push(` ${keyName}: (${params.join(", ")}) =>
759
+ ['${resource}', '${keyName}', ${endpoint.params ? "params" : "undefined"}, ${endpoint.query ? "query" : "undefined"}] as const,`);
760
+ } else {
761
+ resourceKeys.push(
762
+ ` ${keyName}: () => ['${resource}', '${keyName}'] as const,`
763
+ );
764
+ }
765
+ added.add(keyName);
766
+ });
767
+ keys.push(` ${resource}: {
768
+ ${resourceKeys.join("\n")}
769
+ },`);
770
+ });
771
+ return keys.join("\n");
772
+ }
773
+ };
774
+ var QueryOptionsGenerator = class extends BaseGenerator {
775
+ async generate() {
776
+ const content = this.generateContent();
777
+ const outputPath = path6.join(
778
+ this.context.config.outputDir,
779
+ "query-options.ts"
780
+ );
781
+ await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
782
+ await fs5.writeFile(outputPath, content, "utf-8");
783
+ }
784
+ generateContent() {
785
+ const content = `// Auto-generated query options
786
+ import { queryOptions } from '@tanstack/react-query';
787
+ import { apiClient } from './api-client';
788
+ import { queryKeys } from './query-keys';
789
+ import { z } from 'zod';
790
+ import { apiConfig } from '../config/endpoints';
791
+
792
+ ${this.generateQueryOptionsContent()}
793
+
794
+ export const apiQueryOptions = {
795
+ ${this.generateQueryOptionsExports()}
796
+ } as const;
797
+ `;
798
+ return content;
799
+ }
800
+ generateQueryOptionsContent() {
801
+ const groups = this.groupEndpointsByResource();
802
+ const options = [];
803
+ Object.entries(groups).forEach(([resource, endpoints]) => {
804
+ const queries = endpoints.filter(
805
+ ({ endpoint }) => endpoint.method === "GET"
806
+ );
807
+ if (queries.length === 0) return;
808
+ const resourceOptions = [];
809
+ queries.forEach(({ name, endpoint }) => {
810
+ const optionName = this.getEndpointKeyName(name);
811
+ const inferParams = this.inferNonNull(
812
+ `typeof apiConfig.endpoints.${name}.params`
813
+ );
814
+ const inferQuery = this.inferNonNull(
815
+ `typeof apiConfig.endpoints.${name}.query`
816
+ );
817
+ const inferResponse = this.inferNonNull(
818
+ `typeof apiConfig.endpoints.${name}.response`
819
+ );
820
+ const params = [];
821
+ let apiCall = "";
822
+ if (endpoint.params && endpoint.query) {
823
+ params.push(`params: ${inferParams}`, `filters?: ${inferQuery}`);
824
+ apiCall = `apiClient.${name}(params, filters)`;
825
+ } else if (endpoint.params) {
826
+ params.push(`params: ${inferParams}`);
827
+ apiCall = `apiClient.${name}(params)`;
828
+ } else if (endpoint.query) {
829
+ params.push(`filters?: ${inferQuery}`);
830
+ apiCall = `apiClient.${name}(filters)`;
831
+ } else {
832
+ apiCall = `apiClient.${name}()`;
833
+ }
834
+ const keyCall = this.generateQueryKeyCall(resource, name, endpoint);
835
+ resourceOptions.push(` ${optionName}: (${params.join(", ")}) =>
836
+ queryOptions({
837
+ queryKey: ${keyCall},
838
+ queryFn: (): Promise<${inferResponse}> => ${apiCall},
839
+ staleTime: 1000 * 60 * 5,
840
+ }),`);
841
+ });
842
+ options.push(
843
+ `const ${resource}QueryOptions = {
844
+ ${resourceOptions.join("\n")}
845
+ };
846
+ `
847
+ );
848
+ });
849
+ return options.join("\n");
850
+ }
851
+ generateQueryOptionsExports() {
852
+ const groups = this.groupEndpointsByResource();
853
+ const exports$1 = [];
854
+ Object.keys(groups).forEach((resource) => {
855
+ const hasQueries = groups[resource].some(
856
+ ({ endpoint }) => endpoint.method === "GET"
857
+ );
858
+ if (hasQueries) exports$1.push(` ${resource}: ${resource}QueryOptions,`);
859
+ });
860
+ return exports$1.join("\n");
861
+ }
544
862
  };
545
863
 
546
864
  // src/generators/index.ts
@@ -561,6 +879,8 @@ var CodeGenerator = class {
561
879
  generators.push(new ClientGenerator(this.context));
562
880
  }
563
881
  if (this.context.config.generateHooks) {
882
+ generators.push(new QueryKeysGenerator(this.context));
883
+ generators.push(new QueryOptionsGenerator(this.context));
564
884
  generators.push(new HooksGenerator(this.context));
565
885
  }
566
886
  if (this.context.config.generateServerActions && this.context.config.provider === "nextjs") {