@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 CHANGED
@@ -4,8 +4,9 @@ import chalk from 'chalk';
4
4
  import ora from 'ora';
5
5
  import { cosmiconfig } from 'cosmiconfig';
6
6
  import path6 from 'path';
7
- import { pathToFileURL } from 'url';
7
+ import { createJiti } from 'jiti';
8
8
  import fs5 from 'fs/promises';
9
+ import { fileURLToPath } from 'url';
9
10
 
10
11
  var explorer = cosmiconfig("api-codegen", {
11
12
  searchPlaces: [
@@ -118,6 +119,49 @@ var BaseGenerator = class {
118
119
  return `return apiClient.${name}();`;
119
120
  }
120
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
+ }
121
165
  };
122
166
 
123
167
  // src/generators/hooks.ts
@@ -130,7 +174,10 @@ var HooksGenerator = class extends BaseGenerator {
130
174
  }
131
175
  generateContent() {
132
176
  const useClientDirective = this.context.config.options?.useClientDirective ?? true;
133
- 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" : ""}
134
181
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
135
182
  import type {
136
183
  UseQueryOptions,
@@ -145,85 +192,126 @@ import type {
145
192
  ExtractQuery,
146
193
  ExtractResponse
147
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()}
148
202
  `;
203
+ return content;
204
+ }
205
+ generateQueryHooks() {
149
206
  const hooks = [];
150
- Object.entries(this.context.apiConfig.endpoints).forEach(([name, endpoint]) => {
151
- if (this.isQueryEndpoint(endpoint)) {
152
- hooks.push(this.generateQueryHook(name, endpoint));
153
- } else {
154
- 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));
155
211
  }
156
- });
157
- return imports + "\n" + hooks.join("\n\n");
212
+ );
213
+ return hooks.join("\n\n");
158
214
  }
159
215
  generateQueryHook(name, endpoint) {
160
- const hookPrefix = this.context.config.options?.hookPrefix || "use";
161
- const hookName = `${hookPrefix}${this.capitalize(name)}`;
162
- 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 = [];
163
230
  const queryTags = this.getQueryTags(endpoint);
164
- const paramDef = signature.hasParams ? `params: ${signature.paramType}` : "";
165
- const queryDef = signature.hasQuery ? `query?: ${signature.queryType}` : "";
166
- const optionsDef = `options?: Omit<UseQueryOptions<${signature.responseType}, Error, ${signature.responseType}, QueryKey>, 'queryKey' | 'queryFn'>`;
167
- const paramsList = [paramDef, queryDef, optionsDef].filter(Boolean).join(",\n ");
168
- const queryKeyParts = [
169
- ...queryTags.map((tag) => `'${tag}'`),
170
- signature.hasParams ? "params" : "undefined",
171
- signature.hasQuery ? "query" : "undefined"
172
- ];
173
- const clientCallArgs = [];
174
- if (signature.hasParams) clientCallArgs.push("params");
175
- 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
+ }`);
176
243
  return `/**
177
244
  * ${endpoint.description || `Query hook for ${name}`}
178
245
  * @tags ${queryTags.join(", ") || "none"}
179
246
  */
180
- export function ${hookName}(
181
- ${paramsList}
182
- ) {
247
+ export function ${hookName}(${params.join(",\n ")}) {
183
248
  return useQuery({
184
- queryKey: [${queryKeyParts.join(", ")}] as const,
185
- queryFn: () => apiClient.${name}(${clientCallArgs.join(", ")}),
249
+ ...apiQueryOptions.${resource}.${optionName}(${optionParams.join(", ")}),
186
250
  ...options,
187
251
  });
188
252
  }`;
189
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
+ }
190
264
  generateMutationHook(name, endpoint) {
191
- const hookPrefix = this.context.config.options?.hookPrefix || "use";
192
- const hookName = `${hookPrefix}${this.capitalize(name)}`;
193
- const signature = this.getEndpointSignature(name, endpoint);
194
- const invalidationTags = this.getInvalidationTags(endpoint);
195
- let inputType = "void";
196
- if (signature.hasParams && signature.hasBody) {
197
- inputType = `{ params: ${signature.paramType}; body: ${signature.bodyType} }`;
198
- } else if (signature.hasParams) {
199
- inputType = signature.paramType;
200
- } else if (signature.hasBody) {
201
- 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}()`;
202
291
  }
203
- 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 });` : "";
204
293
  return `/**
205
294
  * ${endpoint.description || `Mutation hook for ${name}`}
206
295
  * @tags ${endpoint.tags?.join(", ") || "none"}
207
296
  */
208
- export function ${hookName}(
209
- options?: Omit<UseMutationOptions<${signature.responseType}, Error, ${inputType}>, 'mutationFn'>
210
- ) {
211
- const queryClient = useQueryClient();
212
-
213
- return useMutation({
214
- mutationFn: ${inputType === "void" ? "() => {" : "(input) => {"}
215
- ${this.generateMutationCall(name, signature.hasParams, signature.hasBody)}
216
- },
217
- onSuccess: (data, variables, context) => {
218
- // Invalidate related queries
219
- ${invalidationQueries}
220
-
221
- // Call user's onSuccess if provided
222
- options?.onSuccess?.(data, variables, context);
223
- },
224
- ...options,
225
- });
226
- }`;
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
+ }`;
227
315
  }
228
316
  };
229
317
  var ServerActionsGenerator = class extends BaseGenerator {
@@ -318,11 +406,13 @@ import type {
318
406
  } from './types';
319
407
  `;
320
408
  const queries = [];
321
- Object.entries(this.context.apiConfig.endpoints).forEach(([name, endpoint]) => {
322
- if (this.isQueryEndpoint(endpoint)) {
323
- 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
+ }
324
414
  }
325
- });
415
+ );
326
416
  return imports + "\n" + queries.join("\n\n");
327
417
  }
328
418
  generateServerQuery(name, endpoint) {
@@ -335,9 +425,7 @@ import type {
335
425
  const clientCallArgs = [];
336
426
  if (signature.hasParams) clientCallArgs.push("params");
337
427
  if (signature.hasQuery) clientCallArgs.push("query");
338
- const cacheKeyParts = [
339
- `'${name}'`
340
- ];
428
+ const cacheKeyParts = [`'${name}'`];
341
429
  if (signature.hasParams) cacheKeyParts.push("JSON.stringify(params)");
342
430
  if (signature.hasQuery) cacheKeyParts.push("JSON.stringify(query)");
343
431
  return `/**
@@ -366,13 +454,18 @@ var TypesGenerator = class extends BaseGenerator {
366
454
  await fs5.writeFile(outputPath, content, "utf-8");
367
455
  }
368
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, "/");
369
460
  return `// Auto-generated type definitions
370
461
  // Do not edit this file manually
371
462
 
372
463
  import type { z } from 'zod';
464
+ import { apiConfig } from '${relativePath}';
465
+
373
466
 
374
467
  // Re-export endpoint configuration types
375
- export type { APIConfig, APIEndpoint, HTTPMethod } from '@vietbus/api-codegen/config';
468
+ export type { APIConfig, APIEndpoint, HTTPMethod } from '@cushin/api-codegen/schema';
376
469
 
377
470
  /**
378
471
  * Type helper to extract params schema from an endpoint
@@ -405,13 +498,35 @@ export type ExtractResponse<T> = T extends { response: infer R extends z.ZodType
405
498
  /**
406
499
  * Import your API config to get typed endpoints
407
500
  *
408
- * @example
409
- * import { apiConfig } from './config/endpoints';
410
- * export type APIEndpoints = typeof apiConfig.endpoints;
411
501
  */
412
- export type APIEndpoints = Record<string, any>;
502
+ export type APIEndpoints = typeof apiConfig.endpoints;
503
+
504
+ ${this.generateEndpointTypes()}
413
505
  `;
414
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
+ }
415
530
  };
416
531
  var ClientGenerator = class extends BaseGenerator {
417
532
  async generate() {
@@ -434,11 +549,15 @@ var ClientGenerator = class extends BaseGenerator {
434
549
  }
435
550
  generateClientContent() {
436
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, "/");
437
555
  return `${useClientDirective ? "'use client';\n" : ""}
438
556
  import { createAPIClient } from '@cushin/api-codegen/client';
439
557
  import type { AuthCallbacks } from '@cushin/api-codegen/client';
440
- import { apiConfig } from '../config/endpoints';
558
+ import { apiConfig } from '${relativePath}';
441
559
  import type { APIEndpoints } from './types';
560
+ import { z } from 'zod';
442
561
 
443
562
  // Type-safe API client methods
444
563
  type APIClientMethods = {
@@ -468,11 +587,15 @@ type APIClientMethods = {
468
587
  };
469
588
 
470
589
  // Export singleton instance (will be initialized later)
471
- export let apiClient: APIClientMethods & {
590
+ export let baseClient: APIClientMethods & {
472
591
  refreshAuth: () => Promise<void>;
473
592
  updateAuthCallbacks: (callbacks: AuthCallbacks) => void;
474
593
  };
475
594
 
595
+ export const apiClient = {
596
+ ${this.generateApiClientMethods()}
597
+ };
598
+
476
599
  /**
477
600
  * Initialize API client with auth callbacks
478
601
  * Call this function in your auth provider setup
@@ -492,8 +615,8 @@ export let apiClient: APIClientMethods & {
492
615
  * initializeAPIClient(authCallbacks);
493
616
  */
494
617
  export const initializeAPIClient = (authCallbacks: AuthCallbacks) => {
495
- apiClient = createAPIClient(apiConfig, authCallbacks) as any;
496
- return apiClient;
618
+ baseClient = createAPIClient(apiConfig, authCallbacks) as any;
619
+ return baseClient;
497
620
  };
498
621
 
499
622
  // Export for custom usage
@@ -540,6 +663,202 @@ type APIClientMethods = {
540
663
  export const serverClient = createAPIClient(apiConfig) as APIClientMethods;
541
664
  `;
542
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
+ }
543
862
  };
544
863
 
545
864
  // src/generators/index.ts
@@ -560,6 +879,8 @@ var CodeGenerator = class {
560
879
  generators.push(new ClientGenerator(this.context));
561
880
  }
562
881
  if (this.context.config.generateHooks) {
882
+ generators.push(new QueryKeysGenerator(this.context));
883
+ generators.push(new QueryOptionsGenerator(this.context));
563
884
  generators.push(new HooksGenerator(this.context));
564
885
  }
565
886
  if (this.context.config.generateServerActions && this.context.config.provider === "nextjs") {
@@ -571,8 +892,6 @@ var CodeGenerator = class {
571
892
  return generators;
572
893
  }
573
894
  };
574
-
575
- // src/core/codegen.ts
576
895
  var CodegenCore = class {
577
896
  constructor(config) {
578
897
  this.config = config;
@@ -588,8 +907,10 @@ var CodegenCore = class {
588
907
  }
589
908
  async loadAPIConfig() {
590
909
  try {
591
- const fileUrl = pathToFileURL(this.config.endpointsPath).href;
592
- const module = await import(fileUrl);
910
+ const jiti = createJiti(fileURLToPath(import.meta.url), {
911
+ interopDefault: true
912
+ });
913
+ const module = await jiti.import(this.config.endpointsPath);
593
914
  const apiConfig = module.apiConfig || module.default?.apiConfig || module.default || module;
594
915
  if (!apiConfig || !apiConfig.endpoints) {
595
916
  throw new Error(
@@ -691,7 +1012,7 @@ program.command("validate").description("Validate your API endpoints configurati
691
1012
  validateConfig(config);
692
1013
  spinner.text = "Loading API endpoints...";
693
1014
  new CodegenCore(config);
694
- const apiConfigModule = await import(pathToFileURL2(config.endpointsPath).href);
1015
+ const apiConfigModule = await import(pathToFileURL(config.endpointsPath).href);
695
1016
  const apiConfig = apiConfigModule.apiConfig || apiConfigModule.default?.apiConfig || apiConfigModule.default;
696
1017
  if (!apiConfig || !apiConfig.endpoints) {
697
1018
  throw new Error("Invalid endpoints configuration");
@@ -748,7 +1069,7 @@ export default {
748
1069
  };
749
1070
  `;
750
1071
  }
751
- function pathToFileURL2(filePath) {
1072
+ function pathToFileURL(filePath) {
752
1073
  return new URL(`file://${path6.resolve(filePath)}`);
753
1074
  }
754
1075
  program.parse();