@cushin/api-codegen 1.1.1 → 2.0.1
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 +100 -612
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +141 -7
- package/dist/index.js +101 -469
- package/dist/index.js.map +1 -1
- package/package.json +14 -19
- package/README.md +0 -435
- package/dist/cli.d.ts +0 -1
- package/dist/config/index.d.ts +0 -84
- package/dist/config/index.js +0 -69
- package/dist/config/index.js.map +0 -1
- package/dist/config/schema.d.ts +0 -43
- package/dist/config/schema.js +0 -14
- package/dist/config/schema.js.map +0 -1
- package/dist/runtime/client.d.ts +0 -40
- package/dist/runtime/client.js +0 -260
- package/dist/runtime/client.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import ora from 'ora';
|
|
5
|
-
import { cosmiconfig } from 'cosmiconfig';
|
|
6
|
-
import path6 from 'path';
|
|
7
|
-
import { createJiti } from 'jiti';
|
|
8
|
-
import fs5 from 'fs/promises';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
2
|
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { setupCLIProgram, setupGenerateCommand, setupInitCommand, setupValidateCommand } from "@cushin/codegen-cli";
|
|
6
|
+
|
|
7
|
+
// src/config/index.ts
|
|
8
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
9
|
+
import path from "path";
|
|
11
10
|
var explorer = cosmiconfig("api-codegen", {
|
|
12
11
|
searchPlaces: [
|
|
13
12
|
"api-codegen.config.js",
|
|
@@ -26,9 +25,9 @@ async function loadConfig(configPath) {
|
|
|
26
25
|
return null;
|
|
27
26
|
}
|
|
28
27
|
const userConfig = result.config;
|
|
29
|
-
const rootDir =
|
|
30
|
-
const endpointsPath =
|
|
31
|
-
const outputDir =
|
|
28
|
+
const rootDir = path.dirname(result.filepath);
|
|
29
|
+
const endpointsPath = path.resolve(rootDir, userConfig.endpoints);
|
|
30
|
+
const outputDir = path.resolve(rootDir, userConfig.output);
|
|
32
31
|
const generateHooks = userConfig.generateHooks ?? true;
|
|
33
32
|
const generateServerActions = userConfig.generateServerActions ?? userConfig.provider === "nextjs";
|
|
34
33
|
const generateServerQueries = userConfig.generateServerQueries ?? userConfig.provider === "nextjs";
|
|
@@ -70,10 +69,17 @@ function validateConfig(config) {
|
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
|
|
72
|
+
// src/core/codegen.ts
|
|
73
|
+
import { createJiti } from "jiti";
|
|
74
|
+
|
|
75
|
+
// src/generators/hooks.ts
|
|
76
|
+
import fs from "fs/promises";
|
|
77
|
+
import path2 from "path";
|
|
78
|
+
|
|
73
79
|
// src/generators/base.ts
|
|
74
80
|
var BaseGenerator = class {
|
|
75
|
-
constructor(
|
|
76
|
-
this.context =
|
|
81
|
+
constructor(context2) {
|
|
82
|
+
this.context = context2;
|
|
77
83
|
}
|
|
78
84
|
isQueryEndpoint(endpoint) {
|
|
79
85
|
return endpoint.method === "GET";
|
|
@@ -174,15 +180,15 @@ var BaseGenerator = class {
|
|
|
174
180
|
var HooksGenerator = class extends BaseGenerator {
|
|
175
181
|
async generate() {
|
|
176
182
|
const content = this.generateContent();
|
|
177
|
-
const outputPath =
|
|
178
|
-
await
|
|
179
|
-
await
|
|
183
|
+
const outputPath = path2.join(this.context.config.outputDir, "hooks.ts");
|
|
184
|
+
await fs.mkdir(path2.dirname(outputPath), { recursive: true });
|
|
185
|
+
await fs.writeFile(outputPath, content, "utf-8");
|
|
180
186
|
}
|
|
181
187
|
generateContent() {
|
|
182
188
|
const useClientDirective = this.context.config.options?.useClientDirective ?? true;
|
|
183
|
-
const outputPath =
|
|
184
|
-
const endpointsPath =
|
|
185
|
-
const relativePath =
|
|
189
|
+
const outputPath = path2.join(this.context.config.outputDir, "types.ts");
|
|
190
|
+
const endpointsPath = path2.join(this.context.config.endpointsPath);
|
|
191
|
+
const relativePath = path2.relative(path2.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
186
192
|
const content = `${useClientDirective ? "'use client';\n" : ""}
|
|
187
193
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
188
194
|
import { apiClient } from "./client";
|
|
@@ -308,12 +314,16 @@ export function ${hookName}(${params.join(",\n ")}) {
|
|
|
308
314
|
}`;
|
|
309
315
|
}
|
|
310
316
|
};
|
|
317
|
+
|
|
318
|
+
// src/generators/actions.ts
|
|
319
|
+
import fs2 from "fs/promises";
|
|
320
|
+
import path3 from "path";
|
|
311
321
|
var ServerActionsGenerator = class extends BaseGenerator {
|
|
312
322
|
async generate() {
|
|
313
323
|
const content = this.generateContent();
|
|
314
|
-
const outputPath =
|
|
315
|
-
await
|
|
316
|
-
await
|
|
324
|
+
const outputPath = path3.join(this.context.config.outputDir, "actions.ts");
|
|
325
|
+
await fs2.mkdir(path3.dirname(outputPath), { recursive: true });
|
|
326
|
+
await fs2.writeFile(outputPath, content, "utf-8");
|
|
317
327
|
}
|
|
318
328
|
generateContent() {
|
|
319
329
|
const imports = `'use server';
|
|
@@ -381,12 +391,16 @@ ${revalidateStatements}
|
|
|
381
391
|
}`;
|
|
382
392
|
}
|
|
383
393
|
};
|
|
394
|
+
|
|
395
|
+
// src/generators/queries.ts
|
|
396
|
+
import fs3 from "fs/promises";
|
|
397
|
+
import path4 from "path";
|
|
384
398
|
var ServerQueriesGenerator = class extends BaseGenerator {
|
|
385
399
|
async generate() {
|
|
386
400
|
const content = this.generateContent();
|
|
387
|
-
const outputPath =
|
|
388
|
-
await
|
|
389
|
-
await
|
|
401
|
+
const outputPath = path4.join(this.context.config.outputDir, "queries.ts");
|
|
402
|
+
await fs3.mkdir(path4.dirname(outputPath), { recursive: true });
|
|
403
|
+
await fs3.writeFile(outputPath, content, "utf-8");
|
|
390
404
|
}
|
|
391
405
|
generateContent() {
|
|
392
406
|
const imports = `import { cache } from 'react';
|
|
@@ -440,17 +454,21 @@ export const ${queryName} = cache(async (
|
|
|
440
454
|
});`;
|
|
441
455
|
}
|
|
442
456
|
};
|
|
457
|
+
|
|
458
|
+
// src/generators/types.ts
|
|
459
|
+
import fs4 from "fs/promises";
|
|
460
|
+
import path5 from "path";
|
|
443
461
|
var TypesGenerator = class extends BaseGenerator {
|
|
444
462
|
async generate() {
|
|
445
463
|
const content = this.generateContent();
|
|
446
|
-
const outputPath =
|
|
447
|
-
await
|
|
448
|
-
await
|
|
464
|
+
const outputPath = path5.join(this.context.config.outputDir, "types.ts");
|
|
465
|
+
await fs4.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
466
|
+
await fs4.writeFile(outputPath, content, "utf-8");
|
|
449
467
|
}
|
|
450
468
|
generateContent() {
|
|
451
|
-
const outputPath =
|
|
452
|
-
const endpointsPath =
|
|
453
|
-
const relativePath =
|
|
469
|
+
const outputPath = path5.join(this.context.config.outputDir, "types.ts");
|
|
470
|
+
const endpointsPath = path5.join(this.context.config.endpointsPath);
|
|
471
|
+
const relativePath = path5.relative(path5.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
454
472
|
return `// Auto-generated type definitions
|
|
455
473
|
// Do not edit this file manually
|
|
456
474
|
|
|
@@ -524,6 +542,10 @@ ${this.generateEndpointTypes()}
|
|
|
524
542
|
return types.join("\n");
|
|
525
543
|
}
|
|
526
544
|
};
|
|
545
|
+
|
|
546
|
+
// src/generators/client.ts
|
|
547
|
+
import fs5 from "fs/promises";
|
|
548
|
+
import path6 from "path";
|
|
527
549
|
var ClientGenerator = class extends BaseGenerator {
|
|
528
550
|
async generate() {
|
|
529
551
|
await this.generateClientFile();
|
|
@@ -709,20 +731,24 @@ export const serverClient = createAPIClient(apiConfig) as APIClientMethods;
|
|
|
709
731
|
return methods.join("\n");
|
|
710
732
|
}
|
|
711
733
|
};
|
|
734
|
+
|
|
735
|
+
// src/generators/query-keys.ts
|
|
736
|
+
import fs6 from "fs/promises";
|
|
737
|
+
import path7 from "path";
|
|
712
738
|
var QueryKeysGenerator = class extends BaseGenerator {
|
|
713
739
|
async generate() {
|
|
714
740
|
const content = this.generateContent();
|
|
715
|
-
const outputPath =
|
|
741
|
+
const outputPath = path7.join(
|
|
716
742
|
this.context.config.outputDir,
|
|
717
743
|
"query-keys.ts"
|
|
718
744
|
);
|
|
719
|
-
await
|
|
720
|
-
await
|
|
745
|
+
await fs6.mkdir(path7.dirname(outputPath), { recursive: true });
|
|
746
|
+
await fs6.writeFile(outputPath, content, "utf-8");
|
|
721
747
|
}
|
|
722
748
|
generateContent() {
|
|
723
|
-
const outputPath =
|
|
724
|
-
const endpointsPath =
|
|
725
|
-
const relativePath =
|
|
749
|
+
const outputPath = path7.join(this.context.config.outputDir, "types.ts");
|
|
750
|
+
const endpointsPath = path7.join(this.context.config.endpointsPath);
|
|
751
|
+
const relativePath = path7.relative(path7.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
726
752
|
const content = `// Auto-generated query keys
|
|
727
753
|
import { z } from 'zod';
|
|
728
754
|
import { apiConfig } from '${relativePath}';
|
|
@@ -772,20 +798,24 @@ ${resourceKeys.join("\n")}
|
|
|
772
798
|
return keys.join("\n");
|
|
773
799
|
}
|
|
774
800
|
};
|
|
801
|
+
|
|
802
|
+
// src/generators/query-options.ts
|
|
803
|
+
import fs7 from "fs/promises";
|
|
804
|
+
import path8 from "path";
|
|
775
805
|
var QueryOptionsGenerator = class extends BaseGenerator {
|
|
776
806
|
async generate() {
|
|
777
807
|
const content = this.generateContent();
|
|
778
|
-
const outputPath =
|
|
808
|
+
const outputPath = path8.join(
|
|
779
809
|
this.context.config.outputDir,
|
|
780
810
|
"query-options.ts"
|
|
781
811
|
);
|
|
782
|
-
await
|
|
783
|
-
await
|
|
812
|
+
await fs7.mkdir(path8.dirname(outputPath), { recursive: true });
|
|
813
|
+
await fs7.writeFile(outputPath, content, "utf-8");
|
|
784
814
|
}
|
|
785
815
|
generateContent() {
|
|
786
|
-
const outputPath =
|
|
787
|
-
const endpointsPath =
|
|
788
|
-
const relativePath =
|
|
816
|
+
const outputPath = path8.join(this.context.config.outputDir, "types.ts");
|
|
817
|
+
const endpointsPath = path8.join(this.context.config.endpointsPath);
|
|
818
|
+
const relativePath = path8.relative(path8.dirname(outputPath), endpointsPath).replace(/\\/g, "/");
|
|
789
819
|
const content = `// Auto-generated query options
|
|
790
820
|
import { queryOptions } from '@tanstack/react-query';
|
|
791
821
|
import { apiClient } from './client';
|
|
@@ -854,22 +884,26 @@ ${resourceOptions.join("\n")}
|
|
|
854
884
|
}
|
|
855
885
|
generateQueryOptionsExports() {
|
|
856
886
|
const groups = this.groupEndpointsByResource();
|
|
857
|
-
const exports
|
|
887
|
+
const exports = [];
|
|
858
888
|
Object.keys(groups).forEach((resource) => {
|
|
859
889
|
const hasQueries = groups[resource].some(
|
|
860
890
|
({ endpoint }) => endpoint.method === "GET"
|
|
861
891
|
);
|
|
862
|
-
if (hasQueries) exports
|
|
892
|
+
if (hasQueries) exports.push(` ${resource}: ${resource}QueryOptions,`);
|
|
863
893
|
});
|
|
864
|
-
return exports
|
|
894
|
+
return exports.join("\n");
|
|
865
895
|
}
|
|
866
896
|
};
|
|
897
|
+
|
|
898
|
+
// src/generators/prefetch.ts
|
|
899
|
+
import fs8 from "fs/promises";
|
|
900
|
+
import path9 from "path";
|
|
867
901
|
var PrefetchGenerator = class extends BaseGenerator {
|
|
868
902
|
async generate() {
|
|
869
903
|
const content = this.generateContent();
|
|
870
|
-
const outputPath =
|
|
871
|
-
await
|
|
872
|
-
await
|
|
904
|
+
const outputPath = path9.join(this.context.config.outputDir, "prefetchs.ts");
|
|
905
|
+
await fs8.mkdir(path9.dirname(outputPath), { recursive: true });
|
|
906
|
+
await fs8.writeFile(outputPath, content, "utf-8");
|
|
873
907
|
}
|
|
874
908
|
generateContent() {
|
|
875
909
|
const content = `// Auto-generated prefetch utilities
|
|
@@ -917,424 +951,11 @@ ${this.generatePrefetchFunctions()}
|
|
|
917
951
|
};`;
|
|
918
952
|
}
|
|
919
953
|
};
|
|
920
|
-
var SchemaGenerator = class extends BaseGenerator {
|
|
921
|
-
async generate() {
|
|
922
|
-
const content = this.generateContent();
|
|
923
|
-
const outputPath = path6.join(this.context.config.outputDir, "schema.ts");
|
|
924
|
-
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
925
|
-
await fs5.writeFile(outputPath, content, "utf-8");
|
|
926
|
-
}
|
|
927
|
-
generateContent() {
|
|
928
|
-
const content = `// Auto-generated schema definitions
|
|
929
|
-
|
|
930
|
-
import type { z } from 'zod';
|
|
931
|
-
|
|
932
|
-
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
933
|
-
|
|
934
|
-
export interface APIEndpoint {
|
|
935
|
-
path: string;
|
|
936
|
-
method: HTTPMethod;
|
|
937
|
-
baseUrl?: string;
|
|
938
|
-
params?: z.ZodType<any>;
|
|
939
|
-
query?: z.ZodType<any>;
|
|
940
|
-
body?: z.ZodType<any>;
|
|
941
|
-
response: z.ZodType<any>;
|
|
942
|
-
tags?: string[];
|
|
943
|
-
description?: string;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
export interface APIConfig {
|
|
947
|
-
baseUrl?: string;
|
|
948
|
-
endpoints: Record<string, APIEndpoint>;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
export type EndpointConfig<
|
|
952
|
-
TPath extends string = string,
|
|
953
|
-
TMethod extends HTTPMethod = HTTPMethod,
|
|
954
|
-
TParams = undefined,
|
|
955
|
-
TQuery = undefined,
|
|
956
|
-
TBody = undefined,
|
|
957
|
-
TResponse = any,
|
|
958
|
-
> = {
|
|
959
|
-
path: TPath;
|
|
960
|
-
method: TMethod;
|
|
961
|
-
baseUrl?: string;
|
|
962
|
-
params?: z.ZodType<TParams>;
|
|
963
|
-
query?: z.ZodType<TQuery>;
|
|
964
|
-
body?: z.ZodType<TBody>;
|
|
965
|
-
response: z.ZodType<TResponse>;
|
|
966
|
-
tags?: string[];
|
|
967
|
-
description?: string;
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
/**
|
|
971
|
-
* Helper function to define API configuration with type safety
|
|
972
|
-
*/
|
|
973
|
-
export function defineConfig<T extends APIConfig>(config: T): T {
|
|
974
|
-
return config;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
/**
|
|
978
|
-
* Helper function to define a single endpoint with type inference
|
|
979
|
-
*/
|
|
980
|
-
export function defineEndpoint<
|
|
981
|
-
TPath extends string,
|
|
982
|
-
TMethod extends HTTPMethod,
|
|
983
|
-
TParams = undefined,
|
|
984
|
-
TQuery = undefined,
|
|
985
|
-
TBody = undefined,
|
|
986
|
-
TResponse = any,
|
|
987
|
-
>(
|
|
988
|
-
config: EndpointConfig<TPath, TMethod, TParams, TQuery, TBody, TResponse>,
|
|
989
|
-
): EndpointConfig<TPath, TMethod, TParams, TQuery, TBody, TResponse> {
|
|
990
|
-
return config;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Helper to define multiple endpoints
|
|
995
|
-
*/
|
|
996
|
-
export function defineEndpoints<
|
|
997
|
-
T extends Record<string, APIEndpoint>,
|
|
998
|
-
>(endpoints: T): T {
|
|
999
|
-
return endpoints;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
`;
|
|
1003
|
-
return content;
|
|
1004
|
-
}
|
|
1005
|
-
};
|
|
1006
|
-
var CoreGenerator = class extends BaseGenerator {
|
|
1007
|
-
async generate() {
|
|
1008
|
-
const content = this.generateContent();
|
|
1009
|
-
const outputPath = path6.join(this.context.config.outputDir, "core.ts");
|
|
1010
|
-
await fs5.mkdir(path6.dirname(outputPath), { recursive: true });
|
|
1011
|
-
await fs5.writeFile(outputPath, content, "utf-8");
|
|
1012
|
-
}
|
|
1013
|
-
generateContent() {
|
|
1014
|
-
const content = `// Auto-generated schema definitions
|
|
1015
|
-
|
|
1016
|
-
import ky, { HTTPError } from "ky";
|
|
1017
|
-
import type { APIConfig, APIEndpoint } from "../config/schema.js";
|
|
1018
|
-
|
|
1019
|
-
export interface AuthTokens {
|
|
1020
|
-
accessToken: string;
|
|
1021
|
-
refreshToken?: string;
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
export interface AuthCallbacks {
|
|
1025
|
-
getTokens: () => Promise<AuthTokens | null>;
|
|
1026
|
-
onAuthError?: () => void;
|
|
1027
|
-
onRefreshToken?: () => Promise<void>;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
export class APIError extends Error {
|
|
1031
|
-
constructor(
|
|
1032
|
-
message: string,
|
|
1033
|
-
public status: number,
|
|
1034
|
-
public response?: any,
|
|
1035
|
-
) {
|
|
1036
|
-
super(message);
|
|
1037
|
-
this.name = "APIError";
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
export class AuthError extends APIError {
|
|
1042
|
-
constructor(message: string = "Authentication failed") {
|
|
1043
|
-
super(message, 401);
|
|
1044
|
-
this.name = "AuthError";
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
export class APIClient {
|
|
1049
|
-
private client: typeof ky;
|
|
1050
|
-
private isRefreshing = false;
|
|
1051
|
-
private refreshPromise: Promise<void> | null = null;
|
|
1052
|
-
private hooks: any;
|
|
1053
|
-
|
|
1054
|
-
constructor(
|
|
1055
|
-
private config: APIConfig,
|
|
1056
|
-
private authCallbacks?: AuthCallbacks,
|
|
1057
|
-
) {
|
|
1058
|
-
this.hooks = {
|
|
1059
|
-
beforeRequest: [
|
|
1060
|
-
async (request: Request) => {
|
|
1061
|
-
const tokens = await this.authCallbacks?.getTokens();
|
|
1062
|
-
if (tokens?.accessToken) {
|
|
1063
|
-
request.headers.set(
|
|
1064
|
-
"Authorization",
|
|
1065
|
-
\`Bearer \${tokens.accessToken}\`,
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
|
-
},
|
|
1069
|
-
],
|
|
1070
|
-
beforeRetry: [
|
|
1071
|
-
async ({ request, error, retryCount }: any) => {
|
|
1072
|
-
if (error instanceof HTTPError && error.response.status === 401) {
|
|
1073
|
-
if (retryCount === 1 && this.authCallbacks) {
|
|
1074
|
-
try {
|
|
1075
|
-
await this.refreshTokens();
|
|
1076
|
-
const tokens = await this.authCallbacks.getTokens();
|
|
1077
|
-
if (tokens?.accessToken) {
|
|
1078
|
-
request.headers.set(
|
|
1079
|
-
"Authorization",
|
|
1080
|
-
\`Bearer \${tokens.accessToken}\`,
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
} catch (refreshError) {
|
|
1084
|
-
this.authCallbacks.onAuthError?.();
|
|
1085
|
-
throw new AuthError();
|
|
1086
|
-
}
|
|
1087
|
-
} else {
|
|
1088
|
-
this.authCallbacks?.onAuthError?.();
|
|
1089
|
-
throw new AuthError();
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
},
|
|
1093
|
-
],
|
|
1094
|
-
beforeError: [
|
|
1095
|
-
async (error: any) => {
|
|
1096
|
-
const { response } = error;
|
|
1097
|
-
if (response?.body) {
|
|
1098
|
-
try {
|
|
1099
|
-
const body = await response.json();
|
|
1100
|
-
error.message =
|
|
1101
|
-
(body as Error).message || \`HTTP \${response.status}\`;
|
|
1102
|
-
} catch {
|
|
1103
|
-
// Keep original message
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
return error;
|
|
1107
|
-
},
|
|
1108
|
-
],
|
|
1109
|
-
};
|
|
1110
|
-
|
|
1111
|
-
this.client = ky.create({
|
|
1112
|
-
prefixUrl: this.config.baseUrl,
|
|
1113
|
-
headers: {
|
|
1114
|
-
"Content-Type": "application/json",
|
|
1115
|
-
},
|
|
1116
|
-
retry: {
|
|
1117
|
-
limit: 2,
|
|
1118
|
-
methods: ["get", "post", "put", "delete", "patch"],
|
|
1119
|
-
statusCodes: [401],
|
|
1120
|
-
},
|
|
1121
|
-
hooks: this.hooks,
|
|
1122
|
-
});
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
private async refreshTokens(): Promise<void> {
|
|
1126
|
-
if (!this.authCallbacks) {
|
|
1127
|
-
throw new AuthError("No auth callbacks provided");
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
if (this.isRefreshing && this.refreshPromise) {
|
|
1131
|
-
return this.refreshPromise;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
this.isRefreshing = true;
|
|
1135
|
-
|
|
1136
|
-
this.refreshPromise = (async () => {
|
|
1137
|
-
try {
|
|
1138
|
-
if (this.authCallbacks?.onRefreshToken) {
|
|
1139
|
-
await this.authCallbacks.onRefreshToken();
|
|
1140
|
-
} else {
|
|
1141
|
-
throw new AuthError("No refresh token handler provided");
|
|
1142
|
-
}
|
|
1143
|
-
} catch (error) {
|
|
1144
|
-
throw error;
|
|
1145
|
-
} finally {
|
|
1146
|
-
this.isRefreshing = false;
|
|
1147
|
-
this.refreshPromise = null;
|
|
1148
|
-
}
|
|
1149
|
-
})();
|
|
1150
|
-
|
|
1151
|
-
return this.refreshPromise;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
private buildPath(path: string, params?: Record<string, any>): string {
|
|
1155
|
-
if (!params) return path;
|
|
1156
|
-
|
|
1157
|
-
let finalPath = path;
|
|
1158
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
1159
|
-
finalPath = finalPath.replace(
|
|
1160
|
-
\`:\${key}\`,
|
|
1161
|
-
encodeURIComponent(String(value)),
|
|
1162
|
-
);
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
return finalPath;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
private getEndpointBaseUrl(endpoint: APIEndpoint): string {
|
|
1169
|
-
return endpoint.baseUrl || this.config.baseUrl!;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
private getClientForEndpoint(endpoint: APIEndpoint): typeof ky {
|
|
1173
|
-
const endpointBaseUrl = this.getEndpointBaseUrl(endpoint);
|
|
1174
|
-
|
|
1175
|
-
if (endpointBaseUrl === this.config.baseUrl) {
|
|
1176
|
-
return this.client;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
return ky.create({
|
|
1180
|
-
prefixUrl: endpointBaseUrl,
|
|
1181
|
-
headers: {
|
|
1182
|
-
"Content-Type": "application/json",
|
|
1183
|
-
},
|
|
1184
|
-
retry: {
|
|
1185
|
-
limit: 2,
|
|
1186
|
-
methods: ["get", "post", "put", "delete", "patch"],
|
|
1187
|
-
statusCodes: [401],
|
|
1188
|
-
},
|
|
1189
|
-
hooks: this.hooks,
|
|
1190
|
-
});
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
async request<T>(
|
|
1194
|
-
endpoint: APIEndpoint,
|
|
1195
|
-
params?: Record<string, any>,
|
|
1196
|
-
query?: Record<string, any>,
|
|
1197
|
-
body?: any,
|
|
1198
|
-
): Promise<T> {
|
|
1199
|
-
try {
|
|
1200
|
-
const path = this.buildPath(endpoint.path, params);
|
|
1201
|
-
const client = this.getClientForEndpoint(endpoint);
|
|
1202
|
-
|
|
1203
|
-
const options: Record<string, any> = {
|
|
1204
|
-
method: endpoint.method,
|
|
1205
|
-
};
|
|
1206
|
-
|
|
1207
|
-
if (query && Object.keys(query).length > 0) {
|
|
1208
|
-
const searchParams = new URLSearchParams();
|
|
1209
|
-
Object.entries(query).forEach(([key, value]) => {
|
|
1210
|
-
if (value !== undefined && value !== null) {
|
|
1211
|
-
searchParams.append(key, String(value));
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
if (searchParams.toString()) {
|
|
1215
|
-
options.searchParams = searchParams;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
if (body && endpoint.method !== "GET") {
|
|
1220
|
-
if (endpoint.body) {
|
|
1221
|
-
const validatedBody = endpoint.body.parse(body);
|
|
1222
|
-
options.json = validatedBody;
|
|
1223
|
-
} else {
|
|
1224
|
-
options.json = body;
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
const response = await client(path, options);
|
|
1229
|
-
const data = await response.json();
|
|
1230
|
-
|
|
1231
|
-
if (endpoint.response) {
|
|
1232
|
-
return endpoint.response.parse(data);
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
return data as T;
|
|
1236
|
-
} catch (error) {
|
|
1237
|
-
if (error instanceof HTTPError) {
|
|
1238
|
-
const errorData = await error.response.json().catch(() => ({}));
|
|
1239
|
-
throw new APIError(
|
|
1240
|
-
errorData.message || error.message,
|
|
1241
|
-
error.response.status,
|
|
1242
|
-
errorData,
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
if (error instanceof AuthError) {
|
|
1247
|
-
throw error;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
throw new APIError(
|
|
1251
|
-
error instanceof Error ? error.message : "Network error",
|
|
1252
|
-
0,
|
|
1253
|
-
);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
updateAuthCallbacks(authCallbacks: AuthCallbacks) {
|
|
1258
|
-
this.authCallbacks = authCallbacks;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
async refreshAuth(): Promise<void> {
|
|
1262
|
-
if (!this.authCallbacks) {
|
|
1263
|
-
throw new AuthError("No auth callbacks provided");
|
|
1264
|
-
}
|
|
1265
|
-
await this.refreshTokens();
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
generateMethods() {
|
|
1269
|
-
const methods: any = {};
|
|
1270
|
-
|
|
1271
|
-
Object.entries(this.config.endpoints).forEach(([name, endpoint]) => {
|
|
1272
|
-
if (endpoint.method === "GET") {
|
|
1273
|
-
if (endpoint.params && endpoint.query) {
|
|
1274
|
-
methods[name] = (params: any, query?: any): Promise<any> => {
|
|
1275
|
-
return this.request(endpoint, params, query);
|
|
1276
|
-
};
|
|
1277
|
-
} else if (endpoint.params) {
|
|
1278
|
-
methods[name] = (params: any): Promise<any> => {
|
|
1279
|
-
return this.request(endpoint, params);
|
|
1280
|
-
};
|
|
1281
|
-
} else if (endpoint.query) {
|
|
1282
|
-
methods[name] = (query?: any): Promise<any> => {
|
|
1283
|
-
return this.request(endpoint, undefined, query);
|
|
1284
|
-
};
|
|
1285
|
-
} else {
|
|
1286
|
-
methods[name] = (): Promise<any> => {
|
|
1287
|
-
return this.request(endpoint);
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
} else {
|
|
1291
|
-
if (endpoint.params && endpoint.body) {
|
|
1292
|
-
methods[name] = (params: any, body: any): Promise<any> => {
|
|
1293
|
-
return this.request(endpoint, params, undefined, body);
|
|
1294
|
-
};
|
|
1295
|
-
} else if (endpoint.params) {
|
|
1296
|
-
methods[name] = (params: any): Promise<any> => {
|
|
1297
|
-
return this.request(endpoint, params);
|
|
1298
|
-
};
|
|
1299
|
-
} else if (endpoint.body) {
|
|
1300
|
-
methods[name] = (body: any): Promise<any> => {
|
|
1301
|
-
return this.request(endpoint, undefined, undefined, body);
|
|
1302
|
-
};
|
|
1303
|
-
} else {
|
|
1304
|
-
methods[name] = (): Promise<any> => {
|
|
1305
|
-
return this.request(endpoint);
|
|
1306
|
-
};
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
});
|
|
1310
|
-
|
|
1311
|
-
return methods;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
export function createAPIClient(
|
|
1316
|
-
config: APIConfig,
|
|
1317
|
-
authCallbacks?: AuthCallbacks,
|
|
1318
|
-
) {
|
|
1319
|
-
const instance = new APIClient(config, authCallbacks);
|
|
1320
|
-
const methods = instance.generateMethods();
|
|
1321
|
-
|
|
1322
|
-
return {
|
|
1323
|
-
...methods,
|
|
1324
|
-
refreshAuth: () => instance.refreshAuth(),
|
|
1325
|
-
updateAuthCallbacks: (newCallbacks: AuthCallbacks) =>
|
|
1326
|
-
instance.updateAuthCallbacks(newCallbacks),
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1329
|
-
`;
|
|
1330
|
-
return content;
|
|
1331
|
-
}
|
|
1332
|
-
};
|
|
1333
954
|
|
|
1334
955
|
// src/generators/index.ts
|
|
1335
956
|
var CodeGenerator = class {
|
|
1336
|
-
constructor(
|
|
1337
|
-
this.context =
|
|
957
|
+
constructor(context2) {
|
|
958
|
+
this.context = context2;
|
|
1338
959
|
}
|
|
1339
960
|
async generate() {
|
|
1340
961
|
const generators = this.getGenerators();
|
|
@@ -1345,8 +966,6 @@ var CodeGenerator = class {
|
|
|
1345
966
|
getGenerators() {
|
|
1346
967
|
const generators = [];
|
|
1347
968
|
generators.push(new TypesGenerator(this.context));
|
|
1348
|
-
generators.push(new SchemaGenerator(this.context));
|
|
1349
|
-
generators.push(new CoreGenerator(this.context));
|
|
1350
969
|
if (this.context.config.generateClient) {
|
|
1351
970
|
generators.push(new ClientGenerator(this.context));
|
|
1352
971
|
}
|
|
@@ -1367,6 +986,9 @@ var CodeGenerator = class {
|
|
|
1367
986
|
return generators;
|
|
1368
987
|
}
|
|
1369
988
|
};
|
|
989
|
+
|
|
990
|
+
// src/core/codegen.ts
|
|
991
|
+
import { fileURLToPath } from "url";
|
|
1370
992
|
var CodegenCore = class {
|
|
1371
993
|
constructor(config) {
|
|
1372
994
|
this.config = config;
|
|
@@ -1400,153 +1022,19 @@ var CodegenCore = class {
|
|
|
1400
1022
|
}
|
|
1401
1023
|
}
|
|
1402
1024
|
};
|
|
1403
|
-
var program = new Command();
|
|
1404
|
-
program.name("api-codegen").description("Generate type-safe API client code from endpoint definitions").version("1.0.0");
|
|
1405
|
-
program.command("generate").alias("gen").description("Generate API client code from configuration").option("-c, --config <path>", "Path to configuration file").option("-w, --watch", "Watch for changes and regenerate").action(async (options) => {
|
|
1406
|
-
const spinner = ora("Loading configuration...").start();
|
|
1407
|
-
try {
|
|
1408
|
-
const config = await loadConfig(options.config);
|
|
1409
|
-
if (!config) {
|
|
1410
|
-
spinner.fail(
|
|
1411
|
-
chalk.red(
|
|
1412
|
-
"No configuration file found. Please create an api-codegen.config.js file."
|
|
1413
|
-
)
|
|
1414
|
-
);
|
|
1415
|
-
process.exit(1);
|
|
1416
|
-
}
|
|
1417
|
-
spinner.text = "Validating configuration...";
|
|
1418
|
-
validateConfig(config);
|
|
1419
|
-
spinner.text = "Loading API endpoints...";
|
|
1420
|
-
const codegen = new CodegenCore(config);
|
|
1421
|
-
spinner.text = "Generating code...";
|
|
1422
|
-
await codegen.execute();
|
|
1423
|
-
spinner.succeed(
|
|
1424
|
-
chalk.green(
|
|
1425
|
-
`\u2728 Code generated successfully in ${chalk.cyan(config.outputDir)}`
|
|
1426
|
-
)
|
|
1427
|
-
);
|
|
1428
|
-
console.log(chalk.dim("\nGenerated files:"));
|
|
1429
|
-
const files = await fs5.readdir(config.outputDir);
|
|
1430
|
-
files.forEach((file) => {
|
|
1431
|
-
console.log(chalk.dim(` \u2022 ${file}`));
|
|
1432
|
-
});
|
|
1433
|
-
if (options.watch) {
|
|
1434
|
-
console.log(chalk.yellow("\n\u{1F440} Watching for changes..."));
|
|
1435
|
-
spinner.info(chalk.dim("Watch mode not yet implemented"));
|
|
1436
|
-
}
|
|
1437
|
-
} catch (error) {
|
|
1438
|
-
spinner.fail(chalk.red("Failed to generate code"));
|
|
1439
|
-
console.error(
|
|
1440
|
-
chalk.red("\n" + (error instanceof Error ? error.message : String(error)))
|
|
1441
|
-
);
|
|
1442
|
-
if (error instanceof Error && error.stack) {
|
|
1443
|
-
console.error(chalk.dim(error.stack));
|
|
1444
|
-
}
|
|
1445
|
-
process.exit(1);
|
|
1446
|
-
}
|
|
1447
|
-
});
|
|
1448
|
-
program.command("init").description("Initialize a new api-codegen configuration").option("-p, --provider <provider>", "Provider type (vite or nextjs)", "vite").action(async (options) => {
|
|
1449
|
-
const spinner = ora("Creating configuration file...").start();
|
|
1450
|
-
try {
|
|
1451
|
-
const configContent = generateConfigTemplate(options.provider);
|
|
1452
|
-
const configPath = path6.join(process.cwd(), "api-codegen.config.js");
|
|
1453
|
-
try {
|
|
1454
|
-
await fs5.access(configPath);
|
|
1455
|
-
spinner.warn(
|
|
1456
|
-
chalk.yellow(
|
|
1457
|
-
"Configuration file already exists at api-codegen.config.js"
|
|
1458
|
-
)
|
|
1459
|
-
);
|
|
1460
|
-
return;
|
|
1461
|
-
} catch {
|
|
1462
|
-
}
|
|
1463
|
-
await fs5.writeFile(configPath, configContent, "utf-8");
|
|
1464
|
-
spinner.succeed(
|
|
1465
|
-
chalk.green("\u2728 Configuration file created: api-codegen.config.js")
|
|
1466
|
-
);
|
|
1467
|
-
console.log(chalk.dim("\nNext steps:"));
|
|
1468
|
-
console.log(chalk.dim(" 1. Update the endpoints path in the config"));
|
|
1469
|
-
console.log(chalk.dim(" 2. Run: npx @cushin/api-codegen generate"));
|
|
1470
|
-
} catch (error) {
|
|
1471
|
-
spinner.fail(chalk.red("Failed to create configuration file"));
|
|
1472
|
-
console.error(
|
|
1473
|
-
chalk.red("\n" + (error instanceof Error ? error.message : String(error)))
|
|
1474
|
-
);
|
|
1475
|
-
process.exit(1);
|
|
1476
|
-
}
|
|
1477
|
-
});
|
|
1478
|
-
program.command("validate").description("Validate your API endpoints configuration").option("-c, --config <path>", "Path to configuration file").action(async (options) => {
|
|
1479
|
-
const spinner = ora("Loading configuration...").start();
|
|
1480
|
-
try {
|
|
1481
|
-
const config = await loadConfig(options.config);
|
|
1482
|
-
if (!config) {
|
|
1483
|
-
spinner.fail(chalk.red("No configuration file found"));
|
|
1484
|
-
process.exit(1);
|
|
1485
|
-
}
|
|
1486
|
-
spinner.text = "Validating configuration...";
|
|
1487
|
-
validateConfig(config);
|
|
1488
|
-
spinner.text = "Loading API endpoints...";
|
|
1489
|
-
new CodegenCore(config);
|
|
1490
|
-
const apiConfigModule = await import(pathToFileURL(config.endpointsPath).href);
|
|
1491
|
-
const apiConfig = apiConfigModule.apiConfig || apiConfigModule.default?.apiConfig || apiConfigModule.default;
|
|
1492
|
-
if (!apiConfig || !apiConfig.endpoints) {
|
|
1493
|
-
throw new Error("Invalid endpoints configuration");
|
|
1494
|
-
}
|
|
1495
|
-
const endpointCount = Object.keys(apiConfig.endpoints).length;
|
|
1496
|
-
spinner.succeed(
|
|
1497
|
-
chalk.green(
|
|
1498
|
-
`\u2728 Configuration is valid! Found ${endpointCount} endpoint${endpointCount === 1 ? "" : "s"}`
|
|
1499
|
-
)
|
|
1500
|
-
);
|
|
1501
|
-
console.log(chalk.dim("\nEndpoints:"));
|
|
1502
|
-
Object.entries(apiConfig.endpoints).forEach(([name, endpoint]) => {
|
|
1503
|
-
console.log(
|
|
1504
|
-
chalk.dim(
|
|
1505
|
-
` \u2022 ${chalk.cyan(name)}: ${endpoint.method} ${endpoint.path}`
|
|
1506
|
-
)
|
|
1507
|
-
);
|
|
1508
|
-
});
|
|
1509
|
-
} catch (error) {
|
|
1510
|
-
spinner.fail(chalk.red("Validation failed"));
|
|
1511
|
-
console.error(
|
|
1512
|
-
chalk.red("\n" + (error instanceof Error ? error.message : String(error)))
|
|
1513
|
-
);
|
|
1514
|
-
process.exit(1);
|
|
1515
|
-
}
|
|
1516
|
-
});
|
|
1517
|
-
function generateConfigTemplate(provider) {
|
|
1518
|
-
return `/** @type {import('@cushin/api-codegen').UserConfig} */
|
|
1519
|
-
export default {
|
|
1520
|
-
// Provider: 'vite' or 'nextjs'
|
|
1521
|
-
provider: '${provider}',
|
|
1522
|
-
|
|
1523
|
-
// Path to your API endpoints configuration
|
|
1524
|
-
endpoints: './lib/api/config/endpoints.ts',
|
|
1525
|
-
|
|
1526
|
-
// Output directory for generated files
|
|
1527
|
-
output: './lib/api/generated',
|
|
1528
1025
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
// Advanced options
|
|
1539
|
-
options: {
|
|
1540
|
-
useClientDirective: true,
|
|
1541
|
-
hookPrefix: 'use',
|
|
1542
|
-
actionSuffix: 'Action',
|
|
1543
|
-
},
|
|
1026
|
+
// src/cli.ts
|
|
1027
|
+
import path10 from "path";
|
|
1028
|
+
var program = new Command();
|
|
1029
|
+
setupCLIProgram(program);
|
|
1030
|
+
var context = {
|
|
1031
|
+
loadConfig,
|
|
1032
|
+
validateConfig,
|
|
1033
|
+
CodegenCore,
|
|
1034
|
+
pathToFileURL: (filePath) => new URL(`file://${path10.resolve(filePath)}`)
|
|
1544
1035
|
};
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
return new URL(`file://${path6.resolve(filePath)}`);
|
|
1549
|
-
}
|
|
1036
|
+
setupGenerateCommand(program, context);
|
|
1037
|
+
setupInitCommand(program);
|
|
1038
|
+
setupValidateCommand(program, context);
|
|
1550
1039
|
program.parse();
|
|
1551
|
-
//# sourceMappingURL=cli.js.map
|
|
1552
1040
|
//# sourceMappingURL=cli.js.map
|