@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 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 client with
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 | Always |
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 client file information
44
+ * Generated output information for one generator run.
45
45
  */
46
46
  interface GeneratedClientInfo {
47
- readonly clientFile: string;
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 clientGenerator;
121
+ private readonly generators;
103
122
  private project;
104
123
  private analyzedRoutes;
105
124
  private analyzedSchemas;
106
- private generatedInfo;
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
- * Service for generating TypeScript RPC clients
187
+ * Built-in generator for TypeScript RPC clients.
163
188
  */
164
- declare class ClientGeneratorService {
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, ClientGeneratorService, 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, buildFullApiPath, buildFullPath, camelCase, computeHash, extractNamedType, generateTypeScriptInterface, mapJsonSchemaTypeToTypeScript, readChecksum, safeToString, writeChecksum };
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 client file information
44
+ * Generated output information for one generator run.
45
45
  */
46
46
  interface GeneratedClientInfo {
47
- readonly clientFile: string;
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 clientGenerator;
121
+ private readonly generators;
103
122
  private project;
104
123
  private analyzedRoutes;
105
124
  private analyzedSchemas;
106
- private generatedInfo;
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
- * Service for generating TypeScript RPC clients
187
+ * Built-in generator for TypeScript RPC clients.
163
188
  */
164
- declare class ClientGeneratorService {
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, ClientGeneratorService, 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, buildFullApiPath, buildFullPath, camelCase, computeHash, extractNamedType, generateTypeScriptInterface, mapJsonSchemaTypeToTypeScript, readChecksum, safeToString, writeChecksum };
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/utils/hash-utils.ts
90
- var import_crypto = require("crypto");
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/services/client-generator.service.ts
179
- var ClientGeneratorService = class {
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 import_promises2.default.mkdir(this.outputDir, { recursive: true });
157
+ await import_promises.default.mkdir(this.outputDir, { recursive: true });
188
158
  await this.generateClientFile(routes, schemas);
189
159
  const generatedInfo = {
190
- clientFile: import_path2.default.join(this.outputDir, "client.ts"),
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 = import_path2.default.join(this.outputDir, "client.ts");
201
- await import_promises2.default.writeFile(clientPath, clientContent, "utf-8");
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
- clientGenerator;
825
+ generators;
817
826
  // Shared ts-morph project
818
827
  project = null;
819
828
  // Internal state
820
829
  analyzedRoutes = [];
821
830
  analyzedSchemas = [];
822
- generatedInfo = null;
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.clientGenerator = new ClientGeneratorService(this.outputDir);
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.generatedInfo = null;
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.generatedInfo = await this.clientGenerator.generateClient(this.analyzedRoutes, this.analyzedSchemas);
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.generatedInfo;
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
- return import_fs2.default.existsSync(import_path3.default.join(this.outputDir, "client.ts")) && import_fs2.default.existsSync(import_path3.default.join(this.outputDir, "rpc-artifact.json"));
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.generatedInfo = null;
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,