@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 +395 -75
- package/dist/cli.js.map +1 -1
- package/dist/index.js +400 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
212
|
+
);
|
|
213
|
+
return hooks.join("\n\n");
|
|
159
214
|
}
|
|
160
215
|
generateQueryHook(name, endpoint) {
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
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
|
-
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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(
|
|
323
|
-
|
|
324
|
-
|
|
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 '@
|
|
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 =
|
|
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 '
|
|
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
|
|
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
|
-
|
|
497
|
-
return
|
|
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") {
|