@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.
- package/dist/bin/bootstrap.d.ts +19 -0
- package/dist/bin/bootstrap.d.ts.map +1 -0
- package/dist/bin/bootstrap.js +275 -0
- package/dist/bin/bootstrap.js.map +1 -0
- package/dist/bin/init.js +15 -2
- package/dist/bin/init.js.map +1 -1
- package/dist/decorators/rpc-controller.decorator.d.ts +32 -0
- package/dist/decorators/rpc-controller.decorator.d.ts.map +1 -1
- package/dist/decorators/rpc-controller.decorator.js +56 -0
- package/dist/decorators/rpc-controller.decorator.js.map +1 -1
- package/dist/decorators/rpc-method.decorator.d.ts +22 -2
- package/dist/decorators/rpc-method.decorator.d.ts.map +1 -1
- package/dist/decorators/rpc-method.decorator.js +70 -20
- package/dist/decorators/rpc-method.decorator.js.map +1 -1
- package/dist/generators/rpc-types-generator.d.ts +3 -1
- package/dist/generators/rpc-types-generator.d.ts.map +1 -1
- package/dist/generators/rpc-types-generator.js +259 -25
- package/dist/generators/rpc-types-generator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/rpc/rpc-client.d.ts +3 -0
- package/dist/rpc/rpc-client.d.ts.map +1 -1
- package/dist/rpc/rpc-client.js +7 -2
- package/dist/rpc/rpc-client.js.map +1 -1
- package/dist/rpc/rpc-registry.d.ts.map +1 -1
- package/dist/rpc/rpc-registry.js +11 -1
- package/dist/rpc/rpc-registry.js.map +1 -1
- package/dist/rpc/typed-message-bus.d.ts +11 -0
- package/dist/rpc/typed-message-bus.d.ts.map +1 -1
- package/dist/rpc/typed-message-bus.js +2 -1
- package/dist/rpc/typed-message-bus.js.map +1 -1
- package/dist/transport/{in-memory.client.d.ts → in-process.client.d.ts} +4 -2
- package/dist/transport/in-process.client.d.ts.map +1 -0
- package/dist/transport/{in-memory.client.js → in-process.client.js} +13 -7
- package/dist/transport/in-process.client.js.map +1 -0
- package/dist/transport/{in-memory.transport.d.ts → in-process.transport.d.ts} +3 -3
- package/dist/transport/in-process.transport.d.ts.map +1 -0
- package/dist/transport/{in-memory.transport.js → in-process.transport.js} +18 -8
- package/dist/transport/in-process.transport.js.map +1 -0
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.d.ts.map +1 -1
- package/dist/transport/index.js +2 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/types/serializable.d.ts +55 -4
- package/dist/types/serializable.d.ts.map +1 -1
- package/dist/types/serializable.js +4 -0
- package/dist/types/serializable.js.map +1 -1
- package/package.json +8 -6
- package/dist/module-base/index.d.ts +0 -15
- package/dist/module-base/index.d.ts.map +0 -1
- package/dist/module-base/index.js +0 -38
- package/dist/module-base/index.js.map +0 -1
- package/dist/test-date-serialization.d.ts +0 -2
- package/dist/test-date-serialization.d.ts.map +0 -1
- package/dist/test-date-serialization.js +0 -12
- package/dist/test-date-serialization.js.map +0 -1
- package/dist/test-type-validation.d.ts +0 -2
- package/dist/test-type-validation.d.ts.map +0 -1
- package/dist/test-type-validation.js +0 -87
- package/dist/test-type-validation.js.map +0 -1
- package/dist/transport/in-memory.client.d.ts.map +0 -1
- package/dist/transport/in-memory.client.js.map +0 -1
- package/dist/transport/in-memory.transport.d.ts.map +0 -1
- package/dist/transport/in-memory.transport.js.map +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
524
|
+
if (!genericTypeParamNames.has(typeName)) {
|
|
525
|
+
referencedTypes.add(typeName);
|
|
526
|
+
}
|
|
372
527
|
});
|
|
373
528
|
});
|
|
374
529
|
this.extractTypeNames(method.returnType).forEach(typeName => {
|
|
375
|
-
|
|
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
|
-
|
|
566
|
+
if (!genericTypeParamNames.has(typeName)) {
|
|
567
|
+
referencedTypes.add(typeName);
|
|
568
|
+
}
|
|
391
569
|
});
|
|
392
570
|
});
|
|
393
571
|
this.extractTypeNames(method.returnType).forEach(typeName => {
|
|
394
|
-
|
|
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 =>
|
|
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 =>
|
|
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)) {
|