@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/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 {
|
|
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
|
|
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(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
212
|
+
);
|
|
213
|
+
return hooks.join("\n\n");
|
|
158
214
|
}
|
|
159
215
|
generateQueryHook(name, endpoint) {
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
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 = [];
|
|
163
230
|
const queryTags = this.getQueryTags(endpoint);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
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
|
-
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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(
|
|
322
|
-
|
|
323
|
-
|
|
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 '@
|
|
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 =
|
|
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 '
|
|
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
|
|
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
|
-
|
|
496
|
-
return
|
|
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
|
|
592
|
-
|
|
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(
|
|
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
|
|
1072
|
+
function pathToFileURL(filePath) {
|
|
752
1073
|
return new URL(`file://${path6.resolve(filePath)}`);
|
|
753
1074
|
}
|
|
754
1075
|
program.parse();
|