@zdavison/nestjs-rpc-toolkit 0.0.11 → 0.0.13

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.
Files changed (67) hide show
  1. package/dist/bin/bootstrap.d.ts +19 -0
  2. package/dist/bin/bootstrap.d.ts.map +1 -0
  3. package/dist/bin/bootstrap.js +275 -0
  4. package/dist/bin/bootstrap.js.map +1 -0
  5. package/dist/bin/init.js +15 -2
  6. package/dist/bin/init.js.map +1 -1
  7. package/dist/decorators/rpc-controller.decorator.d.ts +32 -0
  8. package/dist/decorators/rpc-controller.decorator.d.ts.map +1 -1
  9. package/dist/decorators/rpc-controller.decorator.js +56 -0
  10. package/dist/decorators/rpc-controller.decorator.js.map +1 -1
  11. package/dist/decorators/rpc-method.decorator.d.ts +22 -2
  12. package/dist/decorators/rpc-method.decorator.d.ts.map +1 -1
  13. package/dist/decorators/rpc-method.decorator.js +70 -20
  14. package/dist/decorators/rpc-method.decorator.js.map +1 -1
  15. package/dist/generators/rpc-types-generator.d.ts +3 -1
  16. package/dist/generators/rpc-types-generator.d.ts.map +1 -1
  17. package/dist/generators/rpc-types-generator.js +259 -25
  18. package/dist/generators/rpc-types-generator.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/rpc/rpc-client.d.ts +3 -0
  24. package/dist/rpc/rpc-client.d.ts.map +1 -1
  25. package/dist/rpc/rpc-client.js +7 -2
  26. package/dist/rpc/rpc-client.js.map +1 -1
  27. package/dist/rpc/rpc-registry.d.ts.map +1 -1
  28. package/dist/rpc/rpc-registry.js +11 -1
  29. package/dist/rpc/rpc-registry.js.map +1 -1
  30. package/dist/rpc/typed-message-bus.d.ts +11 -0
  31. package/dist/rpc/typed-message-bus.d.ts.map +1 -1
  32. package/dist/rpc/typed-message-bus.js +2 -1
  33. package/dist/rpc/typed-message-bus.js.map +1 -1
  34. package/dist/transport/{in-memory.client.d.ts → in-process.client.d.ts} +4 -2
  35. package/dist/transport/in-process.client.d.ts.map +1 -0
  36. package/dist/transport/{in-memory.client.js → in-process.client.js} +13 -7
  37. package/dist/transport/in-process.client.js.map +1 -0
  38. package/dist/transport/{in-memory.transport.d.ts → in-process.transport.d.ts} +3 -3
  39. package/dist/transport/in-process.transport.d.ts.map +1 -0
  40. package/dist/transport/{in-memory.transport.js → in-process.transport.js} +18 -8
  41. package/dist/transport/in-process.transport.js.map +1 -0
  42. package/dist/transport/index.d.ts +2 -2
  43. package/dist/transport/index.d.ts.map +1 -1
  44. package/dist/transport/index.js +2 -2
  45. package/dist/transport/index.js.map +1 -1
  46. package/dist/types/serializable.d.ts +55 -4
  47. package/dist/types/serializable.d.ts.map +1 -1
  48. package/dist/types/serializable.js +4 -0
  49. package/dist/types/serializable.js.map +1 -1
  50. package/package.json +8 -6
  51. package/dist/module-base/index.d.ts +0 -15
  52. package/dist/module-base/index.d.ts.map +0 -1
  53. package/dist/module-base/index.js +0 -38
  54. package/dist/module-base/index.js.map +0 -1
  55. package/dist/test-date-serialization.d.ts +0 -2
  56. package/dist/test-date-serialization.d.ts.map +0 -1
  57. package/dist/test-date-serialization.js +0 -12
  58. package/dist/test-date-serialization.js.map +0 -1
  59. package/dist/test-type-validation.d.ts +0 -2
  60. package/dist/test-type-validation.d.ts.map +0 -1
  61. package/dist/test-type-validation.js +0 -87
  62. package/dist/test-type-validation.js.map +0 -1
  63. package/dist/transport/in-memory.client.d.ts.map +0 -1
  64. package/dist/transport/in-memory.client.js.map +0 -1
  65. package/dist/transport/in-memory.transport.d.ts.map +0 -1
  66. package/dist/transport/in-memory.transport.js.map +0 -1
  67. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -47,8 +47,11 @@ class RpcTypesGenerator {
47
47
  this.packageFiles = new Map();
48
48
  this.expandedPackages = [];
49
49
  this.fileToModuleMap = new Map();
50
+ // Load configuration
50
51
  this.config = this.loadConfig();
52
+ // Expand wildcard patterns in package paths
51
53
  this.expandedPackages = this.expandPackagePaths(this.config.packages);
54
+ // Initialize a separate project for each package
52
55
  this.expandedPackages.forEach(packagePath => {
53
56
  this.initializePackageProject(packagePath);
54
57
  });
@@ -57,6 +60,7 @@ class RpcTypesGenerator {
57
60
  const expandedPaths = [];
58
61
  for (const packagePath of packagePaths) {
59
62
  if (packagePath.includes('*')) {
63
+ // Use glob to expand wildcard patterns
60
64
  const matches = glob_1.glob.sync(packagePath, {
61
65
  cwd: this.options.rootDir
62
66
  }).filter(match => {
@@ -66,9 +70,11 @@ class RpcTypesGenerator {
66
70
  expandedPaths.push(...matches);
67
71
  }
68
72
  else {
73
+ // Regular path, add as-is
69
74
  expandedPaths.push(packagePath);
70
75
  }
71
76
  }
77
+ // Filter out duplicates and ensure all paths exist
72
78
  const uniquePaths = [...new Set(expandedPaths)];
73
79
  return uniquePaths.filter(packagePath => {
74
80
  const fullPath = path.join(this.options.rootDir, packagePath);
@@ -81,21 +87,26 @@ class RpcTypesGenerator {
81
87
  }
82
88
  initializePackageProject(packagePath) {
83
89
  const fullPath = path.join(this.options.rootDir, packagePath);
90
+ // Find all TypeScript files in this package
84
91
  const files = glob_1.glob.sync('src/**/*.ts', {
85
92
  cwd: fullPath,
86
93
  absolute: true
87
94
  });
88
95
  this.packageFiles.set(packagePath, files);
96
+ // Find the most appropriate tsconfig for this package
89
97
  const tsConfigPath = this.findTsConfigForPackage(fullPath);
98
+ // Create a project for this package
90
99
  const project = new ts_morph_1.Project({
91
100
  tsConfigFilePath: tsConfigPath,
92
101
  });
102
+ // Add source files to the project
93
103
  files.forEach(file => {
94
104
  project.addSourceFileAtPath(file);
95
105
  });
96
106
  this.projects.set(packagePath, project);
97
107
  }
98
108
  findTsConfigForPackage(packagePath) {
109
+ // Check for package-specific tsconfig files in order of preference
99
110
  const possibleConfigs = [
100
111
  path.join(packagePath, 'tsconfig.json'),
101
112
  path.join(packagePath, 'tsconfig.build.json'),
@@ -105,15 +116,17 @@ class RpcTypesGenerator {
105
116
  return configPath;
106
117
  }
107
118
  }
119
+ // Fall back to searching for root tsconfig files
108
120
  const rootConfigs = [
109
- path.join(this.options.rootDir, 'tsconfig.base.json'),
110
121
  path.join(this.options.rootDir, 'tsconfig.json'),
122
+ path.join(this.options.rootDir, 'tsconfig.base.json'),
111
123
  ];
112
124
  for (const configPath of rootConfigs) {
113
125
  if (fs.existsSync(configPath)) {
114
126
  return configPath;
115
127
  }
116
128
  }
129
+ // If no tsconfig found, create a minimal one in memory
117
130
  throw new Error(`No tsconfig found for package ${packagePath}. Please ensure the package has a tsconfig.json or the root has tsconfig.base.json/tsconfig.json`);
118
131
  }
119
132
  loadConfig() {
@@ -124,15 +137,18 @@ class RpcTypesGenerator {
124
137
  }
125
138
  generate() {
126
139
  console.log(`🔍 Scanning ${this.projects.size} packages for RPC methods...`);
140
+ // First pass: scan for RPC methods to establish module mapping
127
141
  this.projects.forEach((project, packagePath) => {
128
142
  const sourceFiles = project.getSourceFiles();
129
143
  const relevantFiles = sourceFiles.filter(sf => !sf.getFilePath().includes('node_modules') &&
130
144
  !sf.getFilePath().includes('/dist/'));
131
145
  console.log(` 📦 ${packagePath}: scanning ${relevantFiles.length} TypeScript files`);
146
+ // First, find all RPC methods to establish file-to-module mapping
132
147
  relevantFiles.forEach(sourceFile => {
133
148
  this.scanForRpcMethods(sourceFile);
134
149
  });
135
150
  });
151
+ // Second pass: extract interfaces/DTOs with correct module associations
136
152
  this.projects.forEach((project) => {
137
153
  const sourceFiles = project.getSourceFiles();
138
154
  const relevantFiles = sourceFiles.filter(sf => !sf.getFilePath().includes('node_modules') &&
@@ -142,6 +158,7 @@ class RpcTypesGenerator {
142
158
  this.extractTypesFromFile(sourceFile);
143
159
  });
144
160
  });
161
+ // Generate the aggregated types file
145
162
  this.generateTypesFile();
146
163
  }
147
164
  scanForRpcMethods(sourceFile) {
@@ -150,9 +167,12 @@ class RpcTypesGenerator {
150
167
  const method = node;
151
168
  const rpcMethod = this.processMethod(method, sourceFile);
152
169
  if (rpcMethod) {
170
+ // Map this file to the module determined by the RPC pattern
153
171
  const module = rpcMethod.module;
172
+ // Map the entire directory to this module (since DTOs might be in separate files)
154
173
  const dir = path.dirname(sourceFile.getFilePath());
155
174
  this.fileToModuleMap.set(dir, module);
175
+ // Also map parent src directory for this module
156
176
  const srcDir = dir.replace(/\/[^\/]+$/, '');
157
177
  if (srcDir.endsWith('/src')) {
158
178
  this.fileToModuleMap.set(srcDir, module);
@@ -169,17 +189,22 @@ class RpcTypesGenerator {
169
189
  else if (node.getKind() === ts_morph_1.ts.SyntaxKind.ClassDeclaration) {
170
190
  this.extractClassAsInterface(node, sourceFile);
171
191
  }
192
+ else if (node.getKind() === ts_morph_1.ts.SyntaxKind.TypeAliasDeclaration) {
193
+ this.extractTypeAlias(node, sourceFile);
194
+ }
172
195
  });
173
196
  }
174
197
  extractInterface(interfaceDeclaration, sourceFile) {
175
198
  const name = interfaceDeclaration.getName();
176
199
  const source = interfaceDeclaration.getText();
177
200
  const moduleName = this.getModuleForFile(sourceFile.getFilePath());
201
+ const jsDoc = this.extractJsDoc(interfaceDeclaration);
178
202
  if (name && this.isRelevantInterface(name) && !this.isInternalType(name)) {
179
203
  this.interfaces.set(name, {
180
204
  name,
181
205
  source,
182
- module: moduleName
206
+ module: moduleName,
207
+ jsDoc
183
208
  });
184
209
  }
185
210
  }
@@ -187,28 +212,78 @@ class RpcTypesGenerator {
187
212
  const name = classDeclaration.getName();
188
213
  if (!name || !this.isRelevantInterface(name) || this.isInternalType(name))
189
214
  return;
215
+ // Extract generic type parameters from class
216
+ const typeParameters = classDeclaration.getTypeParameters();
217
+ const typeParamsStr = typeParameters.length > 0
218
+ ? `<${typeParameters.map((tp) => {
219
+ const tpName = tp.getName();
220
+ const constraint = tp.getConstraint();
221
+ const defaultType = tp.getDefault();
222
+ let result = tpName;
223
+ if (constraint) {
224
+ result += ` extends ${constraint.getText()}`;
225
+ }
226
+ if (defaultType) {
227
+ result += ` = ${defaultType.getText()}`;
228
+ }
229
+ return result;
230
+ }).join(', ')}>`
231
+ : '';
232
+ // Extract DTO classes as interfaces
190
233
  const properties = classDeclaration.getProperties()
191
234
  .filter((prop) => !prop.hasModifier(ts_morph_1.ts.SyntaxKind.PrivateKeyword))
192
235
  .map((prop) => {
193
236
  const propName = prop.getName();
237
+ // Get the type as declared in the source, not the resolved type
194
238
  let propType = 'any';
195
239
  const typeNode = prop.getTypeNode();
196
240
  if (typeNode) {
197
241
  propType = typeNode.getText();
198
242
  }
199
243
  else {
244
+ // Fallback: try to get a simple representation of the type
200
245
  const fullType = prop.getType().getText();
246
+ // Clean up the type string - remove import paths and keep it simple
201
247
  propType = this.cleanTypeString(fullType);
202
248
  }
203
- return ` ${propName}: ${propType};`;
249
+ // Extract JSDoc for the property
250
+ const propJsDoc = this.extractJsDoc(prop);
251
+ const propJsDocStr = propJsDoc ? `${propJsDoc}\n` : '';
252
+ return `${propJsDocStr} ${propName}: ${propType};`;
204
253
  });
205
254
  if (properties.length > 0) {
206
- const source = `export interface ${name} {\n${properties.join('\n')}\n}`;
255
+ // Extract JSDoc for the class
256
+ const classJsDoc = this.extractJsDoc(classDeclaration);
257
+ const classJsDocStr = classJsDoc ? `${classJsDoc}\n` : '';
258
+ const source = `${classJsDocStr}export interface ${name}${typeParamsStr} {\n${properties.join('\n')}\n}`;
207
259
  const moduleName = this.getModuleForFile(sourceFile.getFilePath());
208
260
  this.interfaces.set(name, {
209
261
  name,
210
262
  source,
211
- module: moduleName
263
+ module: moduleName,
264
+ jsDoc: classJsDoc
265
+ });
266
+ }
267
+ }
268
+ extractTypeAlias(typeAliasDeclaration, sourceFile) {
269
+ const name = typeAliasDeclaration.getName();
270
+ let source = typeAliasDeclaration.getText();
271
+ const moduleName = this.getModuleForFile(sourceFile.getFilePath());
272
+ const jsDoc = this.extractJsDoc(typeAliasDeclaration);
273
+ // Ensure the source has export keyword
274
+ if (!source.startsWith('export ')) {
275
+ source = `export ${source}`;
276
+ }
277
+ // Prepend JSDoc if available
278
+ if (jsDoc) {
279
+ source = `${jsDoc}\n${source}`;
280
+ }
281
+ if (name && this.isRelevantInterface(name) && !this.isInternalType(name)) {
282
+ this.interfaces.set(name, {
283
+ name,
284
+ source,
285
+ module: moduleName,
286
+ jsDoc
212
287
  });
213
288
  }
214
289
  }
@@ -216,10 +291,13 @@ class RpcTypesGenerator {
216
291
  return !this.isInternalType(name);
217
292
  }
218
293
  getModuleForFile(filePath) {
294
+ // Check if this file's directory has been mapped to a module
219
295
  const dir = path.dirname(filePath);
296
+ // First check exact directory match
220
297
  if (this.fileToModuleMap.has(dir)) {
221
298
  return this.fileToModuleMap.get(dir);
222
299
  }
300
+ // Check parent directories (DTOs might be in subdirectories)
223
301
  let currentDir = dir;
224
302
  while (currentDir.includes('/src')) {
225
303
  if (this.fileToModuleMap.has(currentDir)) {
@@ -230,12 +308,14 @@ class RpcTypesGenerator {
230
308
  return 'unknown';
231
309
  }
232
310
  isInternalType(name) {
311
+ // Filter out generator internal types
233
312
  return name === 'InterfaceDefinition' ||
234
313
  name === 'RpcMethodInfo' ||
235
314
  name === 'RpcGenerationConfig' ||
236
315
  name === 'GeneratorOptions';
237
316
  }
238
317
  processMethod(method, sourceFile) {
318
+ // Check for @RpcMethod decorator
239
319
  const rpcDecorator = method.getDecorators().find(decorator => {
240
320
  const decoratorName = decorator.getName();
241
321
  return decoratorName === 'RpcMethod';
@@ -243,6 +323,7 @@ class RpcTypesGenerator {
243
323
  if (!rpcDecorator)
244
324
  return null;
245
325
  const methodName = method.getName() || 'unknown';
326
+ // Check if this method is in a class with @RpcController decorator
246
327
  const classDeclaration = method.getParent();
247
328
  let rpcControllerDecorator = null;
248
329
  if (classDeclaration && 'getDecorators' in classDeclaration) {
@@ -250,9 +331,11 @@ class RpcTypesGenerator {
250
331
  return decorator.getName() === 'RpcController';
251
332
  });
252
333
  }
334
+ // Only process methods from classes with @RpcController decorator
253
335
  if (!rpcControllerDecorator) {
254
- return null;
336
+ return null; // Skip methods not in @RpcController classes
255
337
  }
338
+ // Generate module prefix like the @RpcController decorator does
256
339
  let modulePrefix;
257
340
  const args = rpcControllerDecorator.getArguments();
258
341
  if (args.length > 0 && args[0]) {
@@ -261,25 +344,42 @@ class RpcTypesGenerator {
261
344
  modulePrefix = arg.getLiteralValue();
262
345
  }
263
346
  else {
347
+ // Fallback to class name inference
264
348
  const className = method.getParent()?.getSymbol()?.getName() || 'unknown';
265
349
  modulePrefix = className.replace(/(Service|Application|Handler|Repository)$/, '').toLowerCase();
266
350
  }
267
351
  }
268
352
  else {
353
+ // @RpcController() without arguments - infer from class name
269
354
  const className = method.getParent()?.getSymbol()?.getName() || 'unknown';
270
355
  modulePrefix = className.replace(/(Service|Application|Handler|Repository)$/, '').toLowerCase();
271
356
  }
357
+ // Generate the pattern
272
358
  const pattern = `${modulePrefix}.${methodName}`;
359
+ // All patterns should now be prefixed (module.method), so extract module
273
360
  if (!pattern.includes('.')) {
274
361
  console.warn(`⚠️ RPC pattern '${pattern}' should have module prefix. This might be from an older decorator.`);
275
362
  return null;
276
363
  }
277
364
  const moduleName = pattern.split('.')[0];
365
+ // Extract parameter information
278
366
  const paramTypes = method.getParameters().map(param => ({
279
367
  name: param.getName(),
280
368
  type: this.cleanTypeString(param.getType().getText()),
281
369
  }));
370
+ // Extract return type
282
371
  const returnType = this.cleanReturnType(method.getReturnType().getText());
372
+ // Extract generic type parameters
373
+ const typeParameters = method.getTypeParameters().map(tp => {
374
+ const name = tp.getName();
375
+ const constraint = tp.getConstraint();
376
+ if (constraint) {
377
+ return `${name} extends ${constraint.getText()}`;
378
+ }
379
+ return name;
380
+ });
381
+ // Extract JSDoc comment
382
+ const jsDocComment = this.extractJsDoc(method);
283
383
  const rpcMethod = {
284
384
  pattern,
285
385
  methodName,
@@ -287,17 +387,22 @@ class RpcTypesGenerator {
287
387
  paramTypes,
288
388
  returnType,
289
389
  sourceFile: sourceFile.getFilePath(),
390
+ typeParameters: typeParameters.length > 0 ? typeParameters : undefined,
391
+ jsDoc: jsDocComment,
290
392
  };
291
393
  this.rpcMethods.push(rpcMethod);
292
394
  return rpcMethod;
293
395
  }
294
- extractPattern(arg) {
295
- if (arg && typeof arg.getLiteralValue === 'function') {
296
- return arg.getLiteralValue();
297
- }
298
- return null;
396
+ extractJsDoc(node) {
397
+ const jsDocs = node.getJsDocs();
398
+ if (!jsDocs || jsDocs.length === 0)
399
+ return undefined;
400
+ // Get the full text of the JSDoc comment
401
+ const jsDocText = jsDocs.map((doc) => doc.getText()).join('\n');
402
+ return jsDocText;
299
403
  }
300
404
  generateTypesFile() {
405
+ // Group methods by module
301
406
  const moduleGroups = this.rpcMethods.reduce((groups, method) => {
302
407
  if (!groups[method.module]) {
303
408
  groups[method.module] = [];
@@ -305,6 +410,7 @@ class RpcTypesGenerator {
305
410
  groups[method.module].push(method);
306
411
  return groups;
307
412
  }, {});
413
+ // Group interfaces by module
308
414
  const interfacesByModule = new Map();
309
415
  this.interfaces.forEach(interfaceDef => {
310
416
  if (!interfacesByModule.has(interfaceDef.module)) {
@@ -312,25 +418,56 @@ class RpcTypesGenerator {
312
418
  }
313
419
  interfacesByModule.get(interfaceDef.module).push(interfaceDef);
314
420
  });
421
+ // Generate separate file for each module
315
422
  Object.entries(moduleGroups).forEach(([moduleName, methods]) => {
316
423
  this.generateModuleTypesFile(moduleName, methods, interfacesByModule.get(moduleName) || []);
317
424
  });
425
+ // Generate the main types file that composes all modules
318
426
  this.generateMainTypesFile(moduleGroups);
319
427
  }
320
428
  generateModuleTypesFile(moduleName, methods, interfaces) {
429
+ // Collect all type names referenced in RPC methods
321
430
  const referencedTypes = new Set();
431
+ const genericTypeParamNames = new Set();
322
432
  methods.forEach(method => {
433
+ // Track generic type parameter names to exclude from imports
434
+ if (method.typeParameters) {
435
+ method.typeParameters.forEach(typeParam => {
436
+ // Extract just the parameter name (before 'extends' if present)
437
+ const paramName = typeParam.split(' ')[0];
438
+ genericTypeParamNames.add(paramName);
439
+ });
440
+ }
441
+ // Extract types from parameters
323
442
  method.paramTypes.forEach(param => {
324
443
  this.extractTypeNames(param.type).forEach(typeName => {
325
- referencedTypes.add(typeName);
444
+ if (!genericTypeParamNames.has(typeName)) {
445
+ referencedTypes.add(typeName);
446
+ }
326
447
  });
327
448
  });
449
+ // Extract types from return type
328
450
  this.extractTypeNames(method.returnType).forEach(typeName => {
329
- referencedTypes.add(typeName);
451
+ if (!genericTypeParamNames.has(typeName)) {
452
+ referencedTypes.add(typeName);
453
+ }
330
454
  });
455
+ // Extract types from generic type parameters (constraints only)
456
+ if (method.typeParameters) {
457
+ method.typeParameters.forEach(typeParam => {
458
+ this.extractTypeNames(typeParam).forEach(typeName => {
459
+ if (!genericTypeParamNames.has(typeName)) {
460
+ referencedTypes.add(typeName);
461
+ }
462
+ });
463
+ });
464
+ }
331
465
  });
466
+ // Include interfaces that are actually referenced, from this module or others
332
467
  const referencedInterfaces = [];
468
+ // First add interfaces from this module
333
469
  interfaces.filter(interfaceDef => referencedTypes.has(interfaceDef.name)).forEach(interfaceDef => referencedInterfaces.push(interfaceDef));
470
+ // Then add interfaces from other modules that are referenced
334
471
  this.interfaces.forEach(interfaceDef => {
335
472
  if (referencedTypes.has(interfaceDef.name) &&
336
473
  interfaceDef.module !== moduleName &&
@@ -339,10 +476,15 @@ class RpcTypesGenerator {
339
476
  }
340
477
  });
341
478
  const moduleInterfaces = referencedInterfaces.map(interfaceDef => interfaceDef.source).join('\n\n');
479
+ // Generate domain interface for this module
342
480
  const domainMethodDefinitions = methods.map(method => {
343
481
  const methodNameWithoutModule = method.methodName;
344
482
  const paramsType = this.generateParamsType(method.paramTypes);
345
- return ` ${methodNameWithoutModule}(params: ${paramsType}): Promise<${method.returnType}>;`;
483
+ const typeParams = method.typeParameters && method.typeParameters.length > 0
484
+ ? `<${method.typeParameters.join(', ')}>`
485
+ : '';
486
+ const jsDocComment = method.jsDoc ? `${method.jsDoc}\n` : '';
487
+ return `${jsDocComment} ${methodNameWithoutModule}${typeParams}(params: ${paramsType}): Promise<${method.returnType}>;`;
346
488
  }).join('\n');
347
489
  const domainInterface = `// Domain interface for ${moduleName} module
348
490
  export interface ${this.toCamelCase(moduleName)}Domain {
@@ -358,22 +500,46 @@ ${moduleInterfaces}
358
500
 
359
501
  ${domainInterface}
360
502
  `;
503
+ // Write to configured output directory
361
504
  const outputPath = path.join(this.options.rootDir, this.config.outputDir, `${moduleName}.rpc.gen.ts`);
362
505
  fs.writeFileSync(outputPath, fileContent, 'utf8');
363
506
  }
364
507
  generateMainTypesFile(moduleGroups) {
365
508
  const hasModules = Object.keys(moduleGroups).length > 0;
509
+ // Generate imports from module files - include domain interfaces and types
366
510
  const moduleImports = Object.keys(moduleGroups).map(moduleName => {
511
+ // Collect all types referenced in this module's methods
367
512
  const referencedTypes = new Set();
513
+ const genericTypeParamNames = new Set();
368
514
  moduleGroups[moduleName].forEach(method => {
515
+ // Track generic type parameter names to exclude from imports
516
+ if (method.typeParameters) {
517
+ method.typeParameters.forEach(typeParam => {
518
+ const paramName = typeParam.split(' ')[0];
519
+ genericTypeParamNames.add(paramName);
520
+ });
521
+ }
369
522
  method.paramTypes.forEach(param => {
370
523
  this.extractTypeNames(param.type).forEach(typeName => {
371
- referencedTypes.add(typeName);
524
+ if (!genericTypeParamNames.has(typeName)) {
525
+ referencedTypes.add(typeName);
526
+ }
372
527
  });
373
528
  });
374
529
  this.extractTypeNames(method.returnType).forEach(typeName => {
375
- referencedTypes.add(typeName);
530
+ if (!genericTypeParamNames.has(typeName)) {
531
+ referencedTypes.add(typeName);
532
+ }
376
533
  });
534
+ if (method.typeParameters) {
535
+ method.typeParameters.forEach(typeParam => {
536
+ this.extractTypeNames(typeParam).forEach(typeName => {
537
+ if (!genericTypeParamNames.has(typeName)) {
538
+ referencedTypes.add(typeName);
539
+ }
540
+ });
541
+ });
542
+ }
377
543
  });
378
544
  const typesList = Array.from(referencedTypes).filter(type => !this.isBuiltInType(type) && !this.isInternalType(type));
379
545
  const imports = [`${this.toCamelCase(moduleName)}Domain`];
@@ -382,17 +548,40 @@ ${domainInterface}
382
548
  }
383
549
  return `import { ${imports.join(', ')} } from './${moduleName}.rpc.gen';`;
384
550
  }).join('\n');
551
+ // Generate selective re-exports to avoid type conflicts
385
552
  const moduleReExports = Object.keys(moduleGroups).map(moduleName => {
553
+ // Collect all types referenced in this module's methods
386
554
  const referencedTypes = new Set();
555
+ const genericTypeParamNames = new Set();
387
556
  moduleGroups[moduleName].forEach(method => {
557
+ // Track generic type parameter names to exclude from exports
558
+ if (method.typeParameters) {
559
+ method.typeParameters.forEach(typeParam => {
560
+ const paramName = typeParam.split(' ')[0];
561
+ genericTypeParamNames.add(paramName);
562
+ });
563
+ }
388
564
  method.paramTypes.forEach(param => {
389
565
  this.extractTypeNames(param.type).forEach(typeName => {
390
- referencedTypes.add(typeName);
566
+ if (!genericTypeParamNames.has(typeName)) {
567
+ referencedTypes.add(typeName);
568
+ }
391
569
  });
392
570
  });
393
571
  this.extractTypeNames(method.returnType).forEach(typeName => {
394
- referencedTypes.add(typeName);
572
+ if (!genericTypeParamNames.has(typeName)) {
573
+ referencedTypes.add(typeName);
574
+ }
395
575
  });
576
+ if (method.typeParameters) {
577
+ method.typeParameters.forEach(typeParam => {
578
+ this.extractTypeNames(typeParam).forEach(typeName => {
579
+ if (!genericTypeParamNames.has(typeName)) {
580
+ referencedTypes.add(typeName);
581
+ }
582
+ });
583
+ });
584
+ }
396
585
  });
397
586
  const typesList = Array.from(referencedTypes).filter(type => !this.isBuiltInType(type) && !this.isInternalType(type));
398
587
  const exports = [`${this.toCamelCase(moduleName)}Domain`];
@@ -401,12 +590,20 @@ ${domainInterface}
401
590
  }
402
591
  return `export { ${exports.join(', ')} } from './${moduleName}.rpc.gen';`;
403
592
  }).join('\n');
593
+ // Generate common type re-exports from their original modules
404
594
  const commonTypeExports = this.generateCommonTypeExports(moduleGroups);
595
+ // Generate RPC client interface using imported domain interfaces
596
+ // Always export IRpcClient to avoid import errors, even when empty
405
597
  const rpcClientInterface = hasModules ? `
406
598
  // Domain-scoped RPC client interface
407
599
  export interface IRpcClient {
408
600
  ${Object.keys(moduleGroups).map(moduleName => ` ${moduleName}: ${this.toCamelCase(moduleName)}Domain;`).join('\n')}
409
- }` : '';
601
+ }` : `
602
+ // Empty RPC client interface (no RPC methods found yet)
603
+ // Run the type generator after adding @RpcMethod decorators to populate this
604
+ export interface IRpcClient {
605
+ // No RPC domains available
606
+ }`;
410
607
  const fileContent = `// Auto-generated RPC types from all modules
411
608
  // Do not edit this file manually - it will be overwritten
412
609
  //
@@ -430,7 +627,9 @@ ${rpcClientInterface}
430
627
  // const user = await rpc.user.findOne({ id: 'user123' });
431
628
  // const products = await rpc.product.findByOwner({ ownerId: 'user123' });
432
629
  `;
630
+ // Write to configured output directory
433
631
  const outputPath = path.join(this.options.rootDir, this.config.outputDir, 'all.rpc.gen.ts');
632
+ // Ensure directory exists
434
633
  const outputDir = path.dirname(outputPath);
435
634
  if (!fs.existsSync(outputDir)) {
436
635
  fs.mkdirSync(outputDir, { recursive: true });
@@ -456,20 +655,19 @@ ${rpcClientInterface}
456
655
  return `{ ${paramStrings.join('; ')} }`;
457
656
  }
458
657
  cleanReturnType(returnType) {
658
+ // Remove Promise wrapper if present
459
659
  let cleanType = returnType;
460
660
  const promiseMatch = returnType.match(/Promise<(.+)>/);
461
661
  if (promiseMatch) {
462
662
  cleanType = promiseMatch[1];
463
663
  }
664
+ // Remove all import paths and use simple type names
464
665
  cleanType = cleanType.replace(/import\("[^"]*"\)\./g, '');
465
666
  return cleanType;
466
667
  }
467
668
  cleanTypeString(typeStr) {
669
+ // Remove import paths and keep only the type name
468
670
  let cleanType = typeStr.replace(/import\("[^"]*"\)\./g, '');
469
- cleanType = cleanType.replace(/CreateUserDto/g, 'CreateUserDto');
470
- cleanType = cleanType.replace(/CreateProductDto/g, 'CreateProductDto');
471
- cleanType = cleanType.replace(/UpdateUserDto/g, 'Partial<CreateUserDto>');
472
- cleanType = cleanType.replace(/UpdateProductDto/g, 'Partial<CreateProductDto>');
473
671
  return cleanType;
474
672
  }
475
673
  toCamelCase(str) {
@@ -477,10 +675,13 @@ ${rpcClientInterface}
477
675
  }
478
676
  extractTypeNames(typeString) {
479
677
  const typeNames = new Set();
678
+ // Match type names (letters, numbers, underscore, $)
679
+ // This regex will match identifiers that could be type names
480
680
  const typeNameRegex = /\b[A-Z][a-zA-Z0-9_$]*\b/g;
481
681
  const matches = typeString.match(typeNameRegex);
482
682
  if (matches) {
483
683
  matches.forEach(match => {
684
+ // Exclude built-in types and common generic types
484
685
  if (!this.isBuiltInType(match)) {
485
686
  typeNames.add(match);
486
687
  }
@@ -493,22 +694,53 @@ ${rpcClientInterface}
493
694
  'Array', 'Object', 'String', 'Number', 'Boolean',
494
695
  'Promise', 'Date', 'RegExp', 'Error', 'Map', 'Set',
495
696
  'Record', 'Partial', 'Required', 'Readonly', 'Pick', 'Omit',
697
+ // Node.js types that shouldn't be imported
496
698
  'Buffer', 'Stream', 'EventEmitter', 'Socket',
699
+ // DOM types that shouldn't be imported
497
700
  'HTMLElement', 'Document', 'Window', 'Event', 'FileList', 'File', 'Blob',
701
+ // TypeScript utility types
498
702
  'Function', 'CallbackFunction'
499
703
  ];
500
704
  return builtInTypes.includes(typeName);
501
705
  }
502
706
  generateCommonTypeExports(moduleGroups) {
707
+ // Find types that are used across modules and determine their "primary" module
503
708
  const typeToModulesMap = new Map();
504
709
  const typeToOriginalModule = new Map();
710
+ // Track which types are used by which modules
505
711
  Object.entries(moduleGroups).forEach(([moduleName, methods]) => {
712
+ const genericTypeParamNames = new Set();
506
713
  methods.forEach(method => {
714
+ // Track generic type parameter names to exclude
715
+ if (method.typeParameters) {
716
+ method.typeParameters.forEach(typeParam => {
717
+ const paramName = typeParam.split(' ')[0];
718
+ genericTypeParamNames.add(paramName);
719
+ });
720
+ }
721
+ // Extract types from parameters and return types
507
722
  const allTypes = new Set();
508
723
  method.paramTypes.forEach(param => {
509
- this.extractTypeNames(param.type).forEach(typeName => allTypes.add(typeName));
724
+ this.extractTypeNames(param.type).forEach(typeName => {
725
+ if (!genericTypeParamNames.has(typeName)) {
726
+ allTypes.add(typeName);
727
+ }
728
+ });
510
729
  });
511
- this.extractTypeNames(method.returnType).forEach(typeName => allTypes.add(typeName));
730
+ this.extractTypeNames(method.returnType).forEach(typeName => {
731
+ if (!genericTypeParamNames.has(typeName)) {
732
+ allTypes.add(typeName);
733
+ }
734
+ });
735
+ if (method.typeParameters) {
736
+ method.typeParameters.forEach(typeParam => {
737
+ this.extractTypeNames(typeParam).forEach(typeName => {
738
+ if (!genericTypeParamNames.has(typeName)) {
739
+ allTypes.add(typeName);
740
+ }
741
+ });
742
+ });
743
+ }
512
744
  allTypes.forEach(typeName => {
513
745
  if (!typeToModulesMap.has(typeName)) {
514
746
  typeToModulesMap.set(typeName, new Set());
@@ -517,11 +749,13 @@ ${rpcClientInterface}
517
749
  });
518
750
  });
519
751
  });
752
+ // Find the original module for each type
520
753
  this.interfaces.forEach(interfaceDef => {
521
754
  if (!typeToOriginalModule.has(interfaceDef.name)) {
522
755
  typeToOriginalModule.set(interfaceDef.name, interfaceDef.module);
523
756
  }
524
757
  });
758
+ // Generate exports for types that are used across multiple modules
525
759
  const exports = [];
526
760
  typeToModulesMap.forEach((modules, typeName) => {
527
761
  if (modules.size > 1 && typeToOriginalModule.has(typeName)) {