@honestjs/rpc-plugin 1.4.1 → 1.5.0
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/README.md +18 -3
- package/dist/index.d.mts +45 -15
- package/dist/index.d.ts +45 -15
- package/dist/index.js +108 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +105 -60
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# RPC Plugin
|
|
2
2
|
|
|
3
|
-
The RPC Plugin automatically analyzes your HonestJS controllers and generates a fully-typed TypeScript RPC
|
|
4
|
-
proper parameter typing.
|
|
3
|
+
The RPC Plugin automatically analyzes your HonestJS controllers and, by default, generates a fully-typed TypeScript RPC
|
|
4
|
+
client with proper parameter typing. You can also provide custom generators.
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
7
7
|
|
|
@@ -35,6 +35,7 @@ interface RPCPluginOptions {
|
|
|
35
35
|
readonly tsConfigPath?: string // Path to tsconfig.json (default: 'tsconfig.json')
|
|
36
36
|
readonly outputDir?: string // Output directory for generated files (default: './generated/rpc')
|
|
37
37
|
readonly generateOnInit?: boolean // Generate files on initialization (default: true)
|
|
38
|
+
readonly generators?: readonly RPCGenerator[] // Optional list of generators to execute
|
|
38
39
|
readonly context?: {
|
|
39
40
|
readonly namespace?: string // Default: 'rpc'
|
|
40
41
|
readonly keys?: {
|
|
@@ -44,6 +45,20 @@ interface RPCPluginOptions {
|
|
|
44
45
|
}
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
### Generator Behavior
|
|
49
|
+
|
|
50
|
+
- If `generators` is omitted, the plugin uses the built-in `TypeScriptClientGenerator` by default.
|
|
51
|
+
- If `generators` is provided, only those generators are executed.
|
|
52
|
+
- You can still use the built-in TypeScript client generator explicitly:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { RPCPlugin, TypeScriptClientGenerator } from '@honestjs/rpc-plugin'
|
|
56
|
+
|
|
57
|
+
new RPCPlugin({
|
|
58
|
+
generators: [new TypeScriptClientGenerator('./generated/rpc')]
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
47
62
|
## Application Context Artifact
|
|
48
63
|
|
|
49
64
|
After analysis, RPC plugin publishes this artifact to the application context:
|
|
@@ -71,7 +86,7 @@ The plugin generates files in the output directory (default: `./generated/rpc`):
|
|
|
71
86
|
|
|
72
87
|
| File | Description | When generated |
|
|
73
88
|
| --------------- | -------------------------------------------- | --------------------- |
|
|
74
|
-
| `client.ts` | Type-safe RPC client with all DTOs |
|
|
89
|
+
| `client.ts` | Type-safe RPC client with all DTOs | When TypeScript generator runs |
|
|
75
90
|
| `.rpc-checksum` | Hash of source files for incremental caching | Always |
|
|
76
91
|
| `rpc-artifact.json` | Serialized routes/schemas artifact for cache-backed context publishing | Always |
|
|
77
92
|
|
package/dist/index.d.mts
CHANGED
|
@@ -41,10 +41,12 @@ interface SchemaInfo {
|
|
|
41
41
|
readonly typescriptType?: string;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* Generated
|
|
44
|
+
* Generated output information for one generator run.
|
|
45
45
|
*/
|
|
46
46
|
interface GeneratedClientInfo {
|
|
47
|
-
readonly
|
|
47
|
+
readonly generator: string;
|
|
48
|
+
readonly clientFile?: string;
|
|
49
|
+
readonly outputFiles?: readonly string[];
|
|
48
50
|
readonly generatedAt: string;
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -72,6 +74,22 @@ declare class ApiError extends Error {
|
|
|
72
74
|
constructor(statusCode: number, message: string);
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Context passed to each RPC generator.
|
|
79
|
+
*/
|
|
80
|
+
interface RPCGeneratorContext {
|
|
81
|
+
readonly outputDir: string;
|
|
82
|
+
readonly routes: readonly ExtendedRouteInfo[];
|
|
83
|
+
readonly schemas: readonly SchemaInfo[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Contract for custom RPC generators.
|
|
87
|
+
*/
|
|
88
|
+
interface RPCGenerator {
|
|
89
|
+
readonly name: string;
|
|
90
|
+
generate(context: RPCGeneratorContext): Promise<GeneratedClientInfo>;
|
|
91
|
+
}
|
|
92
|
+
|
|
75
93
|
/**
|
|
76
94
|
* Configuration options for the RPCPlugin
|
|
77
95
|
*/
|
|
@@ -80,6 +98,7 @@ interface RPCPluginOptions {
|
|
|
80
98
|
readonly tsConfigPath?: string;
|
|
81
99
|
readonly outputDir?: string;
|
|
82
100
|
readonly generateOnInit?: boolean;
|
|
101
|
+
readonly generators?: readonly RPCGenerator[];
|
|
83
102
|
readonly context?: {
|
|
84
103
|
readonly namespace?: string;
|
|
85
104
|
readonly keys?: {
|
|
@@ -99,11 +118,11 @@ declare class RPCPlugin implements IPlugin {
|
|
|
99
118
|
private readonly contextArtifactKey;
|
|
100
119
|
private readonly routeAnalyzer;
|
|
101
120
|
private readonly schemaGenerator;
|
|
102
|
-
private readonly
|
|
121
|
+
private readonly generators;
|
|
103
122
|
private project;
|
|
104
123
|
private analyzedRoutes;
|
|
105
124
|
private analyzedSchemas;
|
|
106
|
-
private
|
|
125
|
+
private generatedInfos;
|
|
107
126
|
private app;
|
|
108
127
|
constructor(options?: RPCPluginOptions);
|
|
109
128
|
/**
|
|
@@ -135,6 +154,10 @@ declare class RPCPlugin implements IPlugin {
|
|
|
135
154
|
* Get the generation info
|
|
136
155
|
*/
|
|
137
156
|
getGenerationInfo(): GeneratedClientInfo | null;
|
|
157
|
+
/**
|
|
158
|
+
* Get all generation infos
|
|
159
|
+
*/
|
|
160
|
+
getGenerationInfos(): readonly GeneratedClientInfo[];
|
|
138
161
|
/**
|
|
139
162
|
* Checks whether expected output files exist on disk
|
|
140
163
|
*/
|
|
@@ -142,6 +165,8 @@ declare class RPCPlugin implements IPlugin {
|
|
|
142
165
|
private getArtifactPath;
|
|
143
166
|
private writeArtifactToDisk;
|
|
144
167
|
private loadArtifactFromDisk;
|
|
168
|
+
private runGenerators;
|
|
169
|
+
private hasTypeScriptGenerator;
|
|
145
170
|
private publishArtifact;
|
|
146
171
|
private getArtifactContextKey;
|
|
147
172
|
/**
|
|
@@ -159,41 +184,46 @@ declare class RPCPlugin implements IPlugin {
|
|
|
159
184
|
}
|
|
160
185
|
|
|
161
186
|
/**
|
|
162
|
-
*
|
|
187
|
+
* Built-in generator for TypeScript RPC clients.
|
|
163
188
|
*/
|
|
164
|
-
declare class
|
|
189
|
+
declare class TypeScriptClientGenerator implements RPCGenerator {
|
|
165
190
|
private readonly outputDir;
|
|
191
|
+
readonly name = "typescript-client";
|
|
166
192
|
constructor(outputDir: string);
|
|
167
193
|
/**
|
|
168
|
-
* Generates the TypeScript RPC client
|
|
194
|
+
* Generates the TypeScript RPC client.
|
|
195
|
+
*/
|
|
196
|
+
generate(context: RPCGeneratorContext): Promise<GeneratedClientInfo>;
|
|
197
|
+
/**
|
|
198
|
+
* Generates the TypeScript RPC client.
|
|
169
199
|
*/
|
|
170
200
|
generateClient(routes: readonly ExtendedRouteInfo[], schemas: readonly SchemaInfo[]): Promise<GeneratedClientInfo>;
|
|
171
201
|
/**
|
|
172
|
-
* Generates the main client file with types included
|
|
202
|
+
* Generates the main client file with types included.
|
|
173
203
|
*/
|
|
174
204
|
private generateClientFile;
|
|
175
205
|
/**
|
|
176
|
-
* Generates the client TypeScript content with types included
|
|
206
|
+
* Generates the client TypeScript content with types included.
|
|
177
207
|
*/
|
|
178
208
|
private generateClientContent;
|
|
179
209
|
/**
|
|
180
|
-
* Generates controller methods for the client
|
|
210
|
+
* Generates controller methods for the client.
|
|
181
211
|
*/
|
|
182
212
|
private generateControllerMethods;
|
|
183
213
|
/**
|
|
184
|
-
* Extracts the proper return type from route analysis
|
|
214
|
+
* Extracts the proper return type from route analysis.
|
|
185
215
|
*/
|
|
186
216
|
private extractReturnType;
|
|
187
217
|
/**
|
|
188
|
-
* Generates schema types from integrated schema generation
|
|
218
|
+
* Generates schema types from integrated schema generation.
|
|
189
219
|
*/
|
|
190
220
|
private generateSchemaTypes;
|
|
191
221
|
/**
|
|
192
|
-
* Groups routes by controller for better organization
|
|
222
|
+
* Groups routes by controller for better organization.
|
|
193
223
|
*/
|
|
194
224
|
private groupRoutesByController;
|
|
195
225
|
/**
|
|
196
|
-
* Analyzes route parameters to determine their types and usage
|
|
226
|
+
* Analyzes route parameters to determine their types and usage.
|
|
197
227
|
*/
|
|
198
228
|
private analyzeRouteParameters;
|
|
199
229
|
}
|
|
@@ -343,4 +373,4 @@ declare const BUILTIN_TYPES: Set<string>;
|
|
|
343
373
|
*/
|
|
344
374
|
declare const GENERIC_TYPES: Set<string>;
|
|
345
375
|
|
|
346
|
-
export { ApiError, BUILTIN_TYPES, BUILTIN_UTILITY_TYPES, type ChecksumData,
|
|
376
|
+
export { ApiError, BUILTIN_TYPES, BUILTIN_UTILITY_TYPES, type ChecksumData, type ControllerGroups, DEFAULT_OPTIONS, type ExtendedRouteInfo, type FetchFunction, GENERIC_TYPES, type GeneratedClientInfo, LOG_PREFIX, type ParameterMetadataWithType, RPCPlugin, type RPCPluginOptions, type RequestOptions, RouteAnalyzerService, type RouteParameter, type RoutePathInput, SchemaGeneratorService, type SchemaInfo, TypeScriptClientGenerator, buildFullApiPath, buildFullPath, camelCase, computeHash, extractNamedType, generateTypeScriptInterface, mapJsonSchemaTypeToTypeScript, readChecksum, safeToString, writeChecksum };
|
package/dist/index.d.ts
CHANGED
|
@@ -41,10 +41,12 @@ interface SchemaInfo {
|
|
|
41
41
|
readonly typescriptType?: string;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
* Generated
|
|
44
|
+
* Generated output information for one generator run.
|
|
45
45
|
*/
|
|
46
46
|
interface GeneratedClientInfo {
|
|
47
|
-
readonly
|
|
47
|
+
readonly generator: string;
|
|
48
|
+
readonly clientFile?: string;
|
|
49
|
+
readonly outputFiles?: readonly string[];
|
|
48
50
|
readonly generatedAt: string;
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -72,6 +74,22 @@ declare class ApiError extends Error {
|
|
|
72
74
|
constructor(statusCode: number, message: string);
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Context passed to each RPC generator.
|
|
79
|
+
*/
|
|
80
|
+
interface RPCGeneratorContext {
|
|
81
|
+
readonly outputDir: string;
|
|
82
|
+
readonly routes: readonly ExtendedRouteInfo[];
|
|
83
|
+
readonly schemas: readonly SchemaInfo[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Contract for custom RPC generators.
|
|
87
|
+
*/
|
|
88
|
+
interface RPCGenerator {
|
|
89
|
+
readonly name: string;
|
|
90
|
+
generate(context: RPCGeneratorContext): Promise<GeneratedClientInfo>;
|
|
91
|
+
}
|
|
92
|
+
|
|
75
93
|
/**
|
|
76
94
|
* Configuration options for the RPCPlugin
|
|
77
95
|
*/
|
|
@@ -80,6 +98,7 @@ interface RPCPluginOptions {
|
|
|
80
98
|
readonly tsConfigPath?: string;
|
|
81
99
|
readonly outputDir?: string;
|
|
82
100
|
readonly generateOnInit?: boolean;
|
|
101
|
+
readonly generators?: readonly RPCGenerator[];
|
|
83
102
|
readonly context?: {
|
|
84
103
|
readonly namespace?: string;
|
|
85
104
|
readonly keys?: {
|
|
@@ -99,11 +118,11 @@ declare class RPCPlugin implements IPlugin {
|
|
|
99
118
|
private readonly contextArtifactKey;
|
|
100
119
|
private readonly routeAnalyzer;
|
|
101
120
|
private readonly schemaGenerator;
|
|
102
|
-
private readonly
|
|
121
|
+
private readonly generators;
|
|
103
122
|
private project;
|
|
104
123
|
private analyzedRoutes;
|
|
105
124
|
private analyzedSchemas;
|
|
106
|
-
private
|
|
125
|
+
private generatedInfos;
|
|
107
126
|
private app;
|
|
108
127
|
constructor(options?: RPCPluginOptions);
|
|
109
128
|
/**
|
|
@@ -135,6 +154,10 @@ declare class RPCPlugin implements IPlugin {
|
|
|
135
154
|
* Get the generation info
|
|
136
155
|
*/
|
|
137
156
|
getGenerationInfo(): GeneratedClientInfo | null;
|
|
157
|
+
/**
|
|
158
|
+
* Get all generation infos
|
|
159
|
+
*/
|
|
160
|
+
getGenerationInfos(): readonly GeneratedClientInfo[];
|
|
138
161
|
/**
|
|
139
162
|
* Checks whether expected output files exist on disk
|
|
140
163
|
*/
|
|
@@ -142,6 +165,8 @@ declare class RPCPlugin implements IPlugin {
|
|
|
142
165
|
private getArtifactPath;
|
|
143
166
|
private writeArtifactToDisk;
|
|
144
167
|
private loadArtifactFromDisk;
|
|
168
|
+
private runGenerators;
|
|
169
|
+
private hasTypeScriptGenerator;
|
|
145
170
|
private publishArtifact;
|
|
146
171
|
private getArtifactContextKey;
|
|
147
172
|
/**
|
|
@@ -159,41 +184,46 @@ declare class RPCPlugin implements IPlugin {
|
|
|
159
184
|
}
|
|
160
185
|
|
|
161
186
|
/**
|
|
162
|
-
*
|
|
187
|
+
* Built-in generator for TypeScript RPC clients.
|
|
163
188
|
*/
|
|
164
|
-
declare class
|
|
189
|
+
declare class TypeScriptClientGenerator implements RPCGenerator {
|
|
165
190
|
private readonly outputDir;
|
|
191
|
+
readonly name = "typescript-client";
|
|
166
192
|
constructor(outputDir: string);
|
|
167
193
|
/**
|
|
168
|
-
* Generates the TypeScript RPC client
|
|
194
|
+
* Generates the TypeScript RPC client.
|
|
195
|
+
*/
|
|
196
|
+
generate(context: RPCGeneratorContext): Promise<GeneratedClientInfo>;
|
|
197
|
+
/**
|
|
198
|
+
* Generates the TypeScript RPC client.
|
|
169
199
|
*/
|
|
170
200
|
generateClient(routes: readonly ExtendedRouteInfo[], schemas: readonly SchemaInfo[]): Promise<GeneratedClientInfo>;
|
|
171
201
|
/**
|
|
172
|
-
* Generates the main client file with types included
|
|
202
|
+
* Generates the main client file with types included.
|
|
173
203
|
*/
|
|
174
204
|
private generateClientFile;
|
|
175
205
|
/**
|
|
176
|
-
* Generates the client TypeScript content with types included
|
|
206
|
+
* Generates the client TypeScript content with types included.
|
|
177
207
|
*/
|
|
178
208
|
private generateClientContent;
|
|
179
209
|
/**
|
|
180
|
-
* Generates controller methods for the client
|
|
210
|
+
* Generates controller methods for the client.
|
|
181
211
|
*/
|
|
182
212
|
private generateControllerMethods;
|
|
183
213
|
/**
|
|
184
|
-
* Extracts the proper return type from route analysis
|
|
214
|
+
* Extracts the proper return type from route analysis.
|
|
185
215
|
*/
|
|
186
216
|
private extractReturnType;
|
|
187
217
|
/**
|
|
188
|
-
* Generates schema types from integrated schema generation
|
|
218
|
+
* Generates schema types from integrated schema generation.
|
|
189
219
|
*/
|
|
190
220
|
private generateSchemaTypes;
|
|
191
221
|
/**
|
|
192
|
-
* Groups routes by controller for better organization
|
|
222
|
+
* Groups routes by controller for better organization.
|
|
193
223
|
*/
|
|
194
224
|
private groupRoutesByController;
|
|
195
225
|
/**
|
|
196
|
-
* Analyzes route parameters to determine their types and usage
|
|
226
|
+
* Analyzes route parameters to determine their types and usage.
|
|
197
227
|
*/
|
|
198
228
|
private analyzeRouteParameters;
|
|
199
229
|
}
|
|
@@ -343,4 +373,4 @@ declare const BUILTIN_TYPES: Set<string>;
|
|
|
343
373
|
*/
|
|
344
374
|
declare const GENERIC_TYPES: Set<string>;
|
|
345
375
|
|
|
346
|
-
export { ApiError, BUILTIN_TYPES, BUILTIN_UTILITY_TYPES, type ChecksumData,
|
|
376
|
+
export { ApiError, BUILTIN_TYPES, BUILTIN_UTILITY_TYPES, type ChecksumData, type ControllerGroups, DEFAULT_OPTIONS, type ExtendedRouteInfo, type FetchFunction, GENERIC_TYPES, type GeneratedClientInfo, LOG_PREFIX, type ParameterMetadataWithType, RPCPlugin, type RPCPluginOptions, type RequestOptions, RouteAnalyzerService, type RouteParameter, type RoutePathInput, SchemaGeneratorService, type SchemaInfo, TypeScriptClientGenerator, buildFullApiPath, buildFullPath, camelCase, computeHash, extractNamedType, generateTypeScriptInterface, mapJsonSchemaTypeToTypeScript, readChecksum, safeToString, writeChecksum };
|
package/dist/index.js
CHANGED
|
@@ -32,13 +32,13 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
BUILTIN_TYPES: () => BUILTIN_TYPES,
|
|
34
34
|
BUILTIN_UTILITY_TYPES: () => BUILTIN_UTILITY_TYPES,
|
|
35
|
-
ClientGeneratorService: () => ClientGeneratorService,
|
|
36
35
|
DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
|
|
37
36
|
GENERIC_TYPES: () => GENERIC_TYPES,
|
|
38
37
|
LOG_PREFIX: () => LOG_PREFIX,
|
|
39
38
|
RPCPlugin: () => RPCPlugin,
|
|
40
39
|
RouteAnalyzerService: () => RouteAnalyzerService,
|
|
41
40
|
SchemaGeneratorService: () => SchemaGeneratorService,
|
|
41
|
+
TypeScriptClientGenerator: () => TypeScriptClientGenerator,
|
|
42
42
|
buildFullApiPath: () => buildFullApiPath,
|
|
43
43
|
buildFullPath: () => buildFullPath,
|
|
44
44
|
camelCase: () => camelCase,
|
|
@@ -86,46 +86,9 @@ var BUILTIN_UTILITY_TYPES = /* @__PURE__ */ new Set([
|
|
|
86
86
|
var BUILTIN_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "any", "void", "unknown"]);
|
|
87
87
|
var GENERIC_TYPES = /* @__PURE__ */ new Set(["Array", "Promise", "Partial"]);
|
|
88
88
|
|
|
89
|
-
// src/
|
|
90
|
-
var
|
|
91
|
-
var import_fs = require("fs");
|
|
92
|
-
var import_promises = require("fs/promises");
|
|
89
|
+
// src/generators/typescript-client.generator.ts
|
|
90
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
93
91
|
var import_path = __toESM(require("path"));
|
|
94
|
-
var CHECKSUM_FILENAME = ".rpc-checksum";
|
|
95
|
-
function computeHash(filePaths) {
|
|
96
|
-
const sorted = [...filePaths].sort();
|
|
97
|
-
const hasher = (0, import_crypto.createHash)("sha256");
|
|
98
|
-
hasher.update(`files:${sorted.length}
|
|
99
|
-
`);
|
|
100
|
-
for (const filePath of sorted) {
|
|
101
|
-
hasher.update((0, import_fs.readFileSync)(filePath, "utf-8"));
|
|
102
|
-
hasher.update("\0");
|
|
103
|
-
}
|
|
104
|
-
return hasher.digest("hex");
|
|
105
|
-
}
|
|
106
|
-
function readChecksum(outputDir) {
|
|
107
|
-
const checksumPath = import_path.default.join(outputDir, CHECKSUM_FILENAME);
|
|
108
|
-
if (!(0, import_fs.existsSync)(checksumPath)) return null;
|
|
109
|
-
try {
|
|
110
|
-
const raw = (0, import_fs.readFileSync)(checksumPath, "utf-8");
|
|
111
|
-
const data = JSON.parse(raw);
|
|
112
|
-
if (typeof data.hash !== "string" || !Array.isArray(data.files)) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
return data;
|
|
116
|
-
} catch {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async function writeChecksum(outputDir, data) {
|
|
121
|
-
await (0, import_promises.mkdir)(outputDir, { recursive: true });
|
|
122
|
-
const checksumPath = import_path.default.join(outputDir, CHECKSUM_FILENAME);
|
|
123
|
-
await (0, import_promises.writeFile)(checksumPath, JSON.stringify(data, null, 2), "utf-8");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// src/services/client-generator.service.ts
|
|
127
|
-
var import_promises2 = __toESM(require("fs/promises"));
|
|
128
|
-
var import_path2 = __toESM(require("path"));
|
|
129
92
|
|
|
130
93
|
// src/utils/path-utils.ts
|
|
131
94
|
function buildFullPath(basePath, parameters) {
|
|
@@ -175,33 +138,42 @@ function camelCase(str) {
|
|
|
175
138
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
176
139
|
}
|
|
177
140
|
|
|
178
|
-
// src/
|
|
179
|
-
var
|
|
141
|
+
// src/generators/typescript-client.generator.ts
|
|
142
|
+
var TypeScriptClientGenerator = class {
|
|
180
143
|
constructor(outputDir) {
|
|
181
144
|
this.outputDir = outputDir;
|
|
182
145
|
}
|
|
146
|
+
name = "typescript-client";
|
|
183
147
|
/**
|
|
184
|
-
* Generates the TypeScript RPC client
|
|
148
|
+
* Generates the TypeScript RPC client.
|
|
149
|
+
*/
|
|
150
|
+
async generate(context) {
|
|
151
|
+
return this.generateClient(context.routes, context.schemas);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Generates the TypeScript RPC client.
|
|
185
155
|
*/
|
|
186
156
|
async generateClient(routes, schemas) {
|
|
187
|
-
await
|
|
157
|
+
await import_promises.default.mkdir(this.outputDir, { recursive: true });
|
|
188
158
|
await this.generateClientFile(routes, schemas);
|
|
189
159
|
const generatedInfo = {
|
|
190
|
-
|
|
160
|
+
generator: this.name,
|
|
161
|
+
clientFile: import_path.default.join(this.outputDir, "client.ts"),
|
|
162
|
+
outputFiles: [import_path.default.join(this.outputDir, "client.ts")],
|
|
191
163
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
192
164
|
};
|
|
193
165
|
return generatedInfo;
|
|
194
166
|
}
|
|
195
167
|
/**
|
|
196
|
-
* Generates the main client file with types included
|
|
168
|
+
* Generates the main client file with types included.
|
|
197
169
|
*/
|
|
198
170
|
async generateClientFile(routes, schemas) {
|
|
199
171
|
const clientContent = this.generateClientContent(routes, schemas);
|
|
200
|
-
const clientPath =
|
|
201
|
-
await
|
|
172
|
+
const clientPath = import_path.default.join(this.outputDir, "client.ts");
|
|
173
|
+
await import_promises.default.writeFile(clientPath, clientContent, "utf-8");
|
|
202
174
|
}
|
|
203
175
|
/**
|
|
204
|
-
* Generates the client TypeScript content with types included
|
|
176
|
+
* Generates the client TypeScript content with types included.
|
|
205
177
|
*/
|
|
206
178
|
generateClientContent(routes, schemas) {
|
|
207
179
|
const controllerGroups = this.groupRoutesByController(routes);
|
|
@@ -385,7 +357,7 @@ ${this.generateControllerMethods(controllerGroups)}
|
|
|
385
357
|
`;
|
|
386
358
|
}
|
|
387
359
|
/**
|
|
388
|
-
* Generates controller methods for the client
|
|
360
|
+
* Generates controller methods for the client.
|
|
389
361
|
*/
|
|
390
362
|
generateControllerMethods(controllerGroups) {
|
|
391
363
|
let methods = "";
|
|
@@ -461,7 +433,7 @@ ${this.generateControllerMethods(controllerGroups)}
|
|
|
461
433
|
return methods;
|
|
462
434
|
}
|
|
463
435
|
/**
|
|
464
|
-
* Extracts the proper return type from route analysis
|
|
436
|
+
* Extracts the proper return type from route analysis.
|
|
465
437
|
*/
|
|
466
438
|
extractReturnType(returns) {
|
|
467
439
|
if (!returns) return "any";
|
|
@@ -472,7 +444,7 @@ ${this.generateControllerMethods(controllerGroups)}
|
|
|
472
444
|
return returns;
|
|
473
445
|
}
|
|
474
446
|
/**
|
|
475
|
-
* Generates schema types from integrated schema generation
|
|
447
|
+
* Generates schema types from integrated schema generation.
|
|
476
448
|
*/
|
|
477
449
|
generateSchemaTypes(schemas) {
|
|
478
450
|
if (schemas.length === 0) {
|
|
@@ -489,7 +461,7 @@ ${this.generateControllerMethods(controllerGroups)}
|
|
|
489
461
|
return content;
|
|
490
462
|
}
|
|
491
463
|
/**
|
|
492
|
-
* Groups routes by controller for better organization
|
|
464
|
+
* Groups routes by controller for better organization.
|
|
493
465
|
*/
|
|
494
466
|
groupRoutesByController(routes) {
|
|
495
467
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -503,7 +475,7 @@ ${this.generateControllerMethods(controllerGroups)}
|
|
|
503
475
|
return groups;
|
|
504
476
|
}
|
|
505
477
|
/**
|
|
506
|
-
* Analyzes route parameters to determine their types and usage
|
|
478
|
+
* Analyzes route parameters to determine their types and usage.
|
|
507
479
|
*/
|
|
508
480
|
analyzeRouteParameters(route) {
|
|
509
481
|
const parameters = route.parameters || [];
|
|
@@ -514,6 +486,43 @@ ${this.generateControllerMethods(controllerGroups)}
|
|
|
514
486
|
}
|
|
515
487
|
};
|
|
516
488
|
|
|
489
|
+
// src/utils/hash-utils.ts
|
|
490
|
+
var import_crypto = require("crypto");
|
|
491
|
+
var import_fs = require("fs");
|
|
492
|
+
var import_promises2 = require("fs/promises");
|
|
493
|
+
var import_path2 = __toESM(require("path"));
|
|
494
|
+
var CHECKSUM_FILENAME = ".rpc-checksum";
|
|
495
|
+
function computeHash(filePaths) {
|
|
496
|
+
const sorted = [...filePaths].sort();
|
|
497
|
+
const hasher = (0, import_crypto.createHash)("sha256");
|
|
498
|
+
hasher.update(`files:${sorted.length}
|
|
499
|
+
`);
|
|
500
|
+
for (const filePath of sorted) {
|
|
501
|
+
hasher.update((0, import_fs.readFileSync)(filePath, "utf-8"));
|
|
502
|
+
hasher.update("\0");
|
|
503
|
+
}
|
|
504
|
+
return hasher.digest("hex");
|
|
505
|
+
}
|
|
506
|
+
function readChecksum(outputDir) {
|
|
507
|
+
const checksumPath = import_path2.default.join(outputDir, CHECKSUM_FILENAME);
|
|
508
|
+
if (!(0, import_fs.existsSync)(checksumPath)) return null;
|
|
509
|
+
try {
|
|
510
|
+
const raw = (0, import_fs.readFileSync)(checksumPath, "utf-8");
|
|
511
|
+
const data = JSON.parse(raw);
|
|
512
|
+
if (typeof data.hash !== "string" || !Array.isArray(data.files)) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
return data;
|
|
516
|
+
} catch {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async function writeChecksum(outputDir, data) {
|
|
521
|
+
await (0, import_promises2.mkdir)(outputDir, { recursive: true });
|
|
522
|
+
const checksumPath = import_path2.default.join(outputDir, CHECKSUM_FILENAME);
|
|
523
|
+
await (0, import_promises2.writeFile)(checksumPath, JSON.stringify(data, null, 2), "utf-8");
|
|
524
|
+
}
|
|
525
|
+
|
|
517
526
|
// src/services/route-analyzer.service.ts
|
|
518
527
|
var import_honestjs = require("honestjs");
|
|
519
528
|
var RouteAnalyzerService = class {
|
|
@@ -813,13 +822,13 @@ var RPCPlugin = class {
|
|
|
813
822
|
// Services
|
|
814
823
|
routeAnalyzer;
|
|
815
824
|
schemaGenerator;
|
|
816
|
-
|
|
825
|
+
generators;
|
|
817
826
|
// Shared ts-morph project
|
|
818
827
|
project = null;
|
|
819
828
|
// Internal state
|
|
820
829
|
analyzedRoutes = [];
|
|
821
830
|
analyzedSchemas = [];
|
|
822
|
-
|
|
831
|
+
generatedInfos = [];
|
|
823
832
|
app = null;
|
|
824
833
|
constructor(options = {}) {
|
|
825
834
|
this.controllerPattern = options.controllerPattern ?? DEFAULT_OPTIONS.controllerPattern;
|
|
@@ -830,7 +839,7 @@ var RPCPlugin = class {
|
|
|
830
839
|
this.contextArtifactKey = options.context?.keys?.artifact ?? DEFAULT_OPTIONS.context.keys.artifact;
|
|
831
840
|
this.routeAnalyzer = new RouteAnalyzerService();
|
|
832
841
|
this.schemaGenerator = new SchemaGeneratorService(this.controllerPattern, this.tsConfigPath);
|
|
833
|
-
this.
|
|
842
|
+
this.generators = options.generators ?? [new TypeScriptClientGenerator(this.outputDir)];
|
|
834
843
|
this.validateConfiguration();
|
|
835
844
|
}
|
|
836
845
|
/**
|
|
@@ -857,6 +866,14 @@ var RPCPlugin = class {
|
|
|
857
866
|
if (!this.contextArtifactKey?.trim()) {
|
|
858
867
|
errors.push("Context artifact key cannot be empty");
|
|
859
868
|
}
|
|
869
|
+
for (const generator of this.generators) {
|
|
870
|
+
if (!generator.name?.trim()) {
|
|
871
|
+
errors.push("Generator name cannot be empty");
|
|
872
|
+
}
|
|
873
|
+
if (typeof generator.generate !== "function") {
|
|
874
|
+
errors.push(`Generator "${generator.name || "unknown"}" must implement generate(context)`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
860
877
|
if (errors.length > 0) {
|
|
861
878
|
throw new Error(`Configuration validation failed: ${errors.join(", ")}`);
|
|
862
879
|
}
|
|
@@ -898,10 +915,10 @@ var RPCPlugin = class {
|
|
|
898
915
|
}
|
|
899
916
|
this.analyzedRoutes = [];
|
|
900
917
|
this.analyzedSchemas = [];
|
|
901
|
-
this.
|
|
918
|
+
this.generatedInfos = [];
|
|
902
919
|
this.analyzedRoutes = await this.routeAnalyzer.analyzeControllerMethods(this.project);
|
|
903
920
|
this.analyzedSchemas = await this.schemaGenerator.generateSchemas(this.project);
|
|
904
|
-
this.
|
|
921
|
+
this.generatedInfos = await this.runGenerators();
|
|
905
922
|
await writeChecksum(this.outputDir, { hash: computeHash(filePaths), files: filePaths });
|
|
906
923
|
this.writeArtifactToDisk();
|
|
907
924
|
this.log(
|
|
@@ -939,13 +956,25 @@ var RPCPlugin = class {
|
|
|
939
956
|
* Get the generation info
|
|
940
957
|
*/
|
|
941
958
|
getGenerationInfo() {
|
|
942
|
-
return this.
|
|
959
|
+
return this.generatedInfos[0] ?? null;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Get all generation infos
|
|
963
|
+
*/
|
|
964
|
+
getGenerationInfos() {
|
|
965
|
+
return this.generatedInfos;
|
|
943
966
|
}
|
|
944
967
|
/**
|
|
945
968
|
* Checks whether expected output files exist on disk
|
|
946
969
|
*/
|
|
947
970
|
outputFilesExist() {
|
|
948
|
-
|
|
971
|
+
if (!import_fs2.default.existsSync(import_path3.default.join(this.outputDir, "rpc-artifact.json"))) {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
if (!this.hasTypeScriptGenerator()) {
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
return import_fs2.default.existsSync(import_path3.default.join(this.outputDir, "client.ts"));
|
|
949
978
|
}
|
|
950
979
|
getArtifactPath() {
|
|
951
980
|
return import_path3.default.join(this.outputDir, "rpc-artifact.json");
|
|
@@ -967,12 +996,28 @@ var RPCPlugin = class {
|
|
|
967
996
|
}
|
|
968
997
|
this.analyzedRoutes = parsed.routes;
|
|
969
998
|
this.analyzedSchemas = parsed.schemas;
|
|
970
|
-
this.
|
|
999
|
+
this.generatedInfos = [];
|
|
971
1000
|
return true;
|
|
972
1001
|
} catch {
|
|
973
1002
|
return false;
|
|
974
1003
|
}
|
|
975
1004
|
}
|
|
1005
|
+
async runGenerators() {
|
|
1006
|
+
const results = [];
|
|
1007
|
+
for (const generator of this.generators) {
|
|
1008
|
+
this.log(`Running generator: ${generator.name}`);
|
|
1009
|
+
const result = await generator.generate({
|
|
1010
|
+
outputDir: this.outputDir,
|
|
1011
|
+
routes: this.analyzedRoutes,
|
|
1012
|
+
schemas: this.analyzedSchemas
|
|
1013
|
+
});
|
|
1014
|
+
results.push(result);
|
|
1015
|
+
}
|
|
1016
|
+
return results;
|
|
1017
|
+
}
|
|
1018
|
+
hasTypeScriptGenerator() {
|
|
1019
|
+
return this.generators.some((generator) => generator.name === "typescript-client");
|
|
1020
|
+
}
|
|
976
1021
|
publishArtifact(app) {
|
|
977
1022
|
app.getContext().set(this.getArtifactContextKey(), {
|
|
978
1023
|
routes: this.analyzedRoutes,
|
|
@@ -1011,13 +1056,13 @@ var RPCPlugin = class {
|
|
|
1011
1056
|
0 && (module.exports = {
|
|
1012
1057
|
BUILTIN_TYPES,
|
|
1013
1058
|
BUILTIN_UTILITY_TYPES,
|
|
1014
|
-
ClientGeneratorService,
|
|
1015
1059
|
DEFAULT_OPTIONS,
|
|
1016
1060
|
GENERIC_TYPES,
|
|
1017
1061
|
LOG_PREFIX,
|
|
1018
1062
|
RPCPlugin,
|
|
1019
1063
|
RouteAnalyzerService,
|
|
1020
1064
|
SchemaGeneratorService,
|
|
1065
|
+
TypeScriptClientGenerator,
|
|
1021
1066
|
buildFullApiPath,
|
|
1022
1067
|
buildFullPath,
|
|
1023
1068
|
camelCase,
|