@zdavison/nestjs-rpc-toolkit 0.1.5 → 0.2.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/dist/bin/bootstrap.js +0 -0
- package/dist/codecs/index.d.ts +106 -0
- package/dist/codecs/index.d.ts.map +1 -0
- package/dist/codecs/index.js +146 -0
- package/dist/codecs/index.js.map +1 -0
- package/dist/generators/rpc-types-generator.d.ts +28 -13
- package/dist/generators/rpc-types-generator.d.ts.map +1 -1
- package/dist/generators/rpc-types-generator.js +296 -363
- package/dist/generators/rpc-types-generator.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/rpc/rpc-client.d.ts +62 -2
- package/dist/rpc/rpc-client.d.ts.map +1 -1
- package/dist/rpc/rpc-client.js +66 -5
- package/dist/rpc/rpc-client.js.map +1 -1
- package/dist/types/serializable.d.ts +122 -8
- package/dist/types/serializable.d.ts.map +1 -1
- package/dist/types/serializable.js +158 -0
- package/dist/types/serializable.js.map +1 -1
- package/package.json +7 -7
- package/dist/utils/package-manager.utils.d.ts +0 -7
- package/dist/utils/package-manager.utils.d.ts.map +0 -1
- package/dist/utils/package-manager.utils.js +0 -78
- package/dist/utils/package-manager.utils.js.map +0 -1
|
@@ -38,7 +38,6 @@ const ts_morph_1 = require("ts-morph");
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const glob_1 = require("glob");
|
|
41
|
-
const package_manager_utils_1 = require("../utils/package-manager.utils");
|
|
42
41
|
class RpcTypesGenerator {
|
|
43
42
|
constructor(options) {
|
|
44
43
|
this.options = options;
|
|
@@ -49,12 +48,19 @@ class RpcTypesGenerator {
|
|
|
49
48
|
this.packageFiles = new Map();
|
|
50
49
|
this.expandedPackages = [];
|
|
51
50
|
this.fileToModuleMap = new Map();
|
|
52
|
-
|
|
53
|
-
this.
|
|
54
|
-
|
|
55
|
-
this.
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
/** Maps type names to their codec fields (field name -> codec name) */
|
|
52
|
+
this.codecFields = new Map();
|
|
53
|
+
/** Pending nested type checks to resolve after all types are processed */
|
|
54
|
+
this.pendingNestedTypes = new Map();
|
|
55
|
+
/**
|
|
56
|
+
* Built-in codec mappings: source type -> { codecName, wireType }
|
|
57
|
+
* Extensible: add more entries to support additional types.
|
|
58
|
+
*/
|
|
59
|
+
this.codecMappings = {
|
|
60
|
+
'Date': { codecName: 'Date', wireType: 'string' },
|
|
61
|
+
// Future: 'BigInt': { codecName: 'BigInt', wireType: 'string' },
|
|
62
|
+
// Future: 'Buffer': { codecName: 'Buffer', wireType: 'string' },
|
|
63
|
+
};
|
|
58
64
|
// Load configuration
|
|
59
65
|
this.config = this.loadConfig();
|
|
60
66
|
// Expand wildcard patterns in package paths
|
|
@@ -166,9 +172,33 @@ class RpcTypesGenerator {
|
|
|
166
172
|
this.extractTypesFromFile(sourceFile);
|
|
167
173
|
});
|
|
168
174
|
});
|
|
175
|
+
// Third pass: resolve nested type references (fields that reference types with codec fields)
|
|
176
|
+
this.resolveNestedTypeReferences();
|
|
169
177
|
// Generate the aggregated types file
|
|
170
178
|
this.generateTypesFile();
|
|
171
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Resolve pending nested type references.
|
|
182
|
+
* For each type, check if any of its fields reference other types that have codec fields.
|
|
183
|
+
* If so, add a nested type reference (prefixed with @) to the codec fields.
|
|
184
|
+
*/
|
|
185
|
+
resolveNestedTypeReferences() {
|
|
186
|
+
this.pendingNestedTypes.forEach((pendingChecks, typeName) => {
|
|
187
|
+
pendingChecks.forEach(({ propName, typeName: nestedTypeName }) => {
|
|
188
|
+
// Check if the referenced type has codec fields
|
|
189
|
+
if (this.codecFields.has(nestedTypeName)) {
|
|
190
|
+
// Add a nested type reference to the parent type's codec fields
|
|
191
|
+
let typeCodecFields = this.codecFields.get(typeName);
|
|
192
|
+
if (!typeCodecFields) {
|
|
193
|
+
typeCodecFields = new Map();
|
|
194
|
+
this.codecFields.set(typeName, typeCodecFields);
|
|
195
|
+
}
|
|
196
|
+
// Use @ prefix to indicate a nested type reference
|
|
197
|
+
typeCodecFields.set(propName, `@${nestedTypeName}`);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}
|
|
172
202
|
scanForRpcMethods(sourceFile) {
|
|
173
203
|
sourceFile.forEachDescendant((node) => {
|
|
174
204
|
if (node.getKind() === ts_morph_1.ts.SyntaxKind.MethodDeclaration) {
|
|
@@ -190,8 +220,6 @@ class RpcTypesGenerator {
|
|
|
190
220
|
});
|
|
191
221
|
}
|
|
192
222
|
extractTypesFromFile(sourceFile) {
|
|
193
|
-
// First, extract import information
|
|
194
|
-
this.extractImports(sourceFile);
|
|
195
223
|
sourceFile.forEachDescendant((node) => {
|
|
196
224
|
if (node.getKind() === ts_morph_1.ts.SyntaxKind.InterfaceDeclaration) {
|
|
197
225
|
this.extractInterface(node, sourceFile);
|
|
@@ -207,64 +235,47 @@ class RpcTypesGenerator {
|
|
|
207
235
|
}
|
|
208
236
|
});
|
|
209
237
|
}
|
|
210
|
-
extractImports(sourceFile) {
|
|
211
|
-
const importDeclarations = sourceFile.getImportDeclarations();
|
|
212
|
-
importDeclarations.forEach(importDecl => {
|
|
213
|
-
const moduleSpecifier = importDecl.getModuleSpecifierValue();
|
|
214
|
-
// Only track imports from packages (not relative imports)
|
|
215
|
-
if (!moduleSpecifier.startsWith('.') && !moduleSpecifier.startsWith('/')) {
|
|
216
|
-
const namedImports = importDecl.getNamedImports();
|
|
217
|
-
namedImports.forEach(namedImport => {
|
|
218
|
-
const importedName = namedImport.getName();
|
|
219
|
-
this.typeToPackageMap.set(importedName, moduleSpecifier);
|
|
220
|
-
});
|
|
221
|
-
// Try to resolve package version from the source file's package.json
|
|
222
|
-
if (!this.packageVersionMap.has(moduleSpecifier)) {
|
|
223
|
-
const version = this.resolvePackageVersion(sourceFile.getFilePath(), moduleSpecifier);
|
|
224
|
-
if (version) {
|
|
225
|
-
this.packageVersionMap.set(moduleSpecifier, version);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
resolvePackageVersion(sourceFilePath, packageName) {
|
|
232
|
-
// Walk up from the source file to find package.json
|
|
233
|
-
let currentDir = path.dirname(sourceFilePath);
|
|
234
|
-
while (currentDir !== path.dirname(currentDir)) { // Stop at root
|
|
235
|
-
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
236
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
237
|
-
try {
|
|
238
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
239
|
-
// Check dependencies and devDependencies
|
|
240
|
-
const version = packageJson.dependencies?.[packageName] ||
|
|
241
|
-
packageJson.devDependencies?.[packageName];
|
|
242
|
-
if (version) {
|
|
243
|
-
return version;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
// Ignore and continue searching
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
currentDir = path.dirname(currentDir);
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
238
|
extractInterface(interfaceDeclaration, sourceFile) {
|
|
255
239
|
const name = interfaceDeclaration.getName();
|
|
256
|
-
const jsDoc = this.extractJsDoc(interfaceDeclaration);
|
|
257
|
-
let source = interfaceDeclaration.getText();
|
|
258
|
-
// Prepend JSDoc if available and not already in source
|
|
259
|
-
if (jsDoc && !source.startsWith('/**')) {
|
|
260
|
-
source = `${jsDoc}\n${source}`;
|
|
261
|
-
}
|
|
262
|
-
// Ensure the source has export keyword
|
|
263
|
-
if (!source.includes('export interface')) {
|
|
264
|
-
source = source.replace(/^(\/\*\*[\s\S]*?\*\/\n)?interface/, '$1export interface');
|
|
265
|
-
}
|
|
266
240
|
const moduleName = this.getModuleForFile(sourceFile.getFilePath());
|
|
241
|
+
const jsDoc = this.extractJsDoc(interfaceDeclaration);
|
|
267
242
|
if (name && this.isRelevantInterface(name) && !this.isInternalType(name)) {
|
|
243
|
+
// Track codec fields and transform the interface source
|
|
244
|
+
const typeCodecFields = new Map();
|
|
245
|
+
// Track nested type references (for fields whose type has codec fields)
|
|
246
|
+
const pendingNestedTypeChecks = [];
|
|
247
|
+
const properties = interfaceDeclaration.getProperties();
|
|
248
|
+
let source = interfaceDeclaration.getText();
|
|
249
|
+
// Check each property for codec-handled types
|
|
250
|
+
properties.forEach((prop) => {
|
|
251
|
+
const propName = prop.getName();
|
|
252
|
+
const typeNode = prop.getTypeNode();
|
|
253
|
+
if (typeNode) {
|
|
254
|
+
const propType = typeNode.getText();
|
|
255
|
+
const codecInfo = this.getCodecForType(propType);
|
|
256
|
+
if (codecInfo) {
|
|
257
|
+
typeCodecFields.set(propName, codecInfo.codecName);
|
|
258
|
+
// Replace the type with wire type in the source
|
|
259
|
+
const newType = codecInfo.wireType;
|
|
260
|
+
source = source.replace(new RegExp(`(${propName}\\s*[?]?\\s*:\\s*)${propType.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'g'), `$1${newType}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// Check if this is a reference to another type (potential nested type)
|
|
264
|
+
const extractedType = this.extractMainTypeName(propType);
|
|
265
|
+
if (extractedType && !this.isBuiltInType(extractedType)) {
|
|
266
|
+
pendingNestedTypeChecks.push({ propName, typeName: extractedType });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
// Store codec fields for this type
|
|
272
|
+
if (typeCodecFields.size > 0) {
|
|
273
|
+
this.codecFields.set(name, typeCodecFields);
|
|
274
|
+
}
|
|
275
|
+
// Store pending nested type checks to resolve after all types are processed
|
|
276
|
+
if (pendingNestedTypeChecks.length > 0) {
|
|
277
|
+
this.pendingNestedTypes.set(name, pendingNestedTypeChecks);
|
|
278
|
+
}
|
|
268
279
|
this.interfaces.set(name, {
|
|
269
280
|
name,
|
|
270
281
|
source,
|
|
@@ -294,6 +305,10 @@ class RpcTypesGenerator {
|
|
|
294
305
|
return result;
|
|
295
306
|
}).join(', ')}>`
|
|
296
307
|
: '';
|
|
308
|
+
// Track codec fields for this type
|
|
309
|
+
const typeCodecFields = new Map();
|
|
310
|
+
// Track nested type references (for fields whose type has codec fields)
|
|
311
|
+
const pendingNestedTypeChecks = [];
|
|
297
312
|
// Extract DTO classes as interfaces
|
|
298
313
|
const properties = classDeclaration.getProperties()
|
|
299
314
|
.filter((prop) => !prop.hasModifier(ts_morph_1.ts.SyntaxKind.PrivateKeyword))
|
|
@@ -311,11 +326,32 @@ class RpcTypesGenerator {
|
|
|
311
326
|
// Clean up the type string - remove import paths and keep it simple
|
|
312
327
|
propType = this.cleanTypeString(fullType);
|
|
313
328
|
}
|
|
329
|
+
// Check if this type needs a codec and convert to wire type
|
|
330
|
+
const codecInfo = this.getCodecForType(propType);
|
|
331
|
+
if (codecInfo) {
|
|
332
|
+
typeCodecFields.set(propName, codecInfo.codecName);
|
|
333
|
+
propType = codecInfo.wireType;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
// Check if this is a reference to another type (potential nested type)
|
|
337
|
+
const extractedType = this.extractMainTypeName(propType);
|
|
338
|
+
if (extractedType && !this.isBuiltInType(extractedType)) {
|
|
339
|
+
pendingNestedTypeChecks.push({ propName, typeName: extractedType });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
314
342
|
// Extract JSDoc for the property
|
|
315
343
|
const propJsDoc = this.extractJsDoc(prop);
|
|
316
344
|
const propJsDocStr = propJsDoc ? `${propJsDoc}\n` : '';
|
|
317
345
|
return `${propJsDocStr} ${propName}: ${propType};`;
|
|
318
346
|
});
|
|
347
|
+
// Store codec fields for this type
|
|
348
|
+
if (typeCodecFields.size > 0) {
|
|
349
|
+
this.codecFields.set(name, typeCodecFields);
|
|
350
|
+
}
|
|
351
|
+
// Store pending nested type checks to resolve after all types are processed
|
|
352
|
+
if (pendingNestedTypeChecks.length > 0) {
|
|
353
|
+
this.pendingNestedTypes.set(name, pendingNestedTypeChecks);
|
|
354
|
+
}
|
|
319
355
|
if (properties.length > 0) {
|
|
320
356
|
// Extract JSDoc for the class
|
|
321
357
|
const classJsDoc = this.extractJsDoc(classDeclaration);
|
|
@@ -330,6 +366,27 @@ class RpcTypesGenerator {
|
|
|
330
366
|
});
|
|
331
367
|
}
|
|
332
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Check if a type needs a codec and return codec info.
|
|
371
|
+
* Returns undefined if the type doesn't need a codec.
|
|
372
|
+
*/
|
|
373
|
+
getCodecForType(typeStr) {
|
|
374
|
+
const normalized = typeStr.replace(/\s/g, '');
|
|
375
|
+
// Check each known codec type
|
|
376
|
+
for (const [sourceType, codecInfo] of Object.entries(this.codecMappings)) {
|
|
377
|
+
// Match exact type or union with null/undefined
|
|
378
|
+
if (normalized === sourceType ||
|
|
379
|
+
normalized.includes(`${sourceType}|`) ||
|
|
380
|
+
normalized.includes(`|${sourceType}`)) {
|
|
381
|
+
return {
|
|
382
|
+
codecName: codecInfo.codecName,
|
|
383
|
+
// Replace the source type with wire type, preserving unions
|
|
384
|
+
wireType: typeStr.replace(new RegExp(`\\b${sourceType}\\b`, 'g'), codecInfo.wireType),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
333
390
|
extractTypeAlias(typeAliasDeclaration, sourceFile) {
|
|
334
391
|
const name = typeAliasDeclaration.getName();
|
|
335
392
|
let source = typeAliasDeclaration.getText();
|
|
@@ -401,54 +458,6 @@ class RpcTypesGenerator {
|
|
|
401
458
|
name === 'RpcGenerationConfig' ||
|
|
402
459
|
name === 'GeneratorOptions';
|
|
403
460
|
}
|
|
404
|
-
collectExternalImports(referencedTypes, genericTypeParamNames) {
|
|
405
|
-
// Map of package name -> Set of type names to import from that package
|
|
406
|
-
const externalImports = new Map();
|
|
407
|
-
const typesToCheck = new Set(referencedTypes);
|
|
408
|
-
const checkedTypes = new Set();
|
|
409
|
-
// Recursively collect all external types and their dependencies
|
|
410
|
-
while (typesToCheck.size > 0) {
|
|
411
|
-
const currentType = Array.from(typesToCheck)[0];
|
|
412
|
-
typesToCheck.delete(currentType);
|
|
413
|
-
checkedTypes.add(currentType);
|
|
414
|
-
// Skip if it's a built-in type, generic parameter, or internal type
|
|
415
|
-
if (this.isBuiltInType(currentType) || genericTypeParamNames.has(currentType) || this.isInternalType(currentType)) {
|
|
416
|
-
continue;
|
|
417
|
-
}
|
|
418
|
-
// Check if this type is defined locally (in our interfaces or enums)
|
|
419
|
-
const isLocalType = this.interfaces.has(currentType) || this.enums.has(currentType);
|
|
420
|
-
if (!isLocalType && this.typeToPackageMap.has(currentType)) {
|
|
421
|
-
// This is an external type - add to imports
|
|
422
|
-
const packageName = this.typeToPackageMap.get(currentType);
|
|
423
|
-
if (!externalImports.has(packageName)) {
|
|
424
|
-
externalImports.set(packageName, new Set());
|
|
425
|
-
}
|
|
426
|
-
externalImports.get(packageName).add(currentType);
|
|
427
|
-
// Check if any of our source interfaces reference this type and extract nested types
|
|
428
|
-
this.interfaces.forEach(interfaceDef => {
|
|
429
|
-
if (interfaceDef.source.includes(currentType)) {
|
|
430
|
-
this.extractTypeNames(interfaceDef.source).forEach(nestedType => {
|
|
431
|
-
if (!checkedTypes.has(nestedType) && !genericTypeParamNames.has(nestedType)) {
|
|
432
|
-
typesToCheck.add(nestedType);
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
else if (isLocalType) {
|
|
439
|
-
// This is a local type - check if it references other external types
|
|
440
|
-
const localDef = this.interfaces.get(currentType) || this.enums.get(currentType);
|
|
441
|
-
if (localDef) {
|
|
442
|
-
this.extractTypeNames(localDef.source).forEach(nestedType => {
|
|
443
|
-
if (!checkedTypes.has(nestedType) && !genericTypeParamNames.has(nestedType)) {
|
|
444
|
-
typesToCheck.add(nestedType);
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
return externalImports;
|
|
451
|
-
}
|
|
452
461
|
processMethod(method, sourceFile) {
|
|
453
462
|
// Check for @RpcMethod decorator
|
|
454
463
|
const rpcDecorator = method.getDecorators().find(decorator => {
|
|
@@ -568,7 +577,7 @@ class RpcTypesGenerator {
|
|
|
568
577
|
// Generate the main types file that composes all modules
|
|
569
578
|
this.generateMainTypesFile(moduleGroups);
|
|
570
579
|
}
|
|
571
|
-
generateModuleTypesFile(moduleName, methods,
|
|
580
|
+
generateModuleTypesFile(moduleName, methods, interfaces, enums) {
|
|
572
581
|
// Collect all type names referenced in RPC methods
|
|
573
582
|
const referencedTypes = new Set();
|
|
574
583
|
const genericTypeParamNames = new Set();
|
|
@@ -606,62 +615,61 @@ class RpcTypesGenerator {
|
|
|
606
615
|
});
|
|
607
616
|
}
|
|
608
617
|
});
|
|
609
|
-
// Recursively collect all transitive type dependencies (interfaces, type aliases, and enums)
|
|
610
|
-
// Keep iterating until no new types are discovered
|
|
611
|
-
const collectedTypes = new Set();
|
|
612
|
-
let typesToProcess = new Set(referencedTypes);
|
|
613
|
-
while (typesToProcess.size > 0) {
|
|
614
|
-
const newTypesToProcess = new Set();
|
|
615
|
-
typesToProcess.forEach(typeName => {
|
|
616
|
-
if (collectedTypes.has(typeName) || genericTypeParamNames.has(typeName)) {
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
collectedTypes.add(typeName);
|
|
620
|
-
// Check if this type is defined locally (interface or type alias)
|
|
621
|
-
const interfaceDef = this.interfaces.get(typeName);
|
|
622
|
-
if (interfaceDef) {
|
|
623
|
-
// Extract all type references from this interface/type alias source
|
|
624
|
-
this.extractTypeNames(interfaceDef.source).forEach(nestedType => {
|
|
625
|
-
if (!collectedTypes.has(nestedType) && !genericTypeParamNames.has(nestedType)) {
|
|
626
|
-
newTypesToProcess.add(nestedType);
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
// Check if this type is an enum
|
|
631
|
-
const enumDef = this.enums.get(typeName);
|
|
632
|
-
if (enumDef) {
|
|
633
|
-
// Enums don't have nested type references, but mark as collected
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
typesToProcess = newTypesToProcess;
|
|
637
|
-
}
|
|
638
|
-
// Update referencedTypes with all collected types
|
|
639
|
-
collectedTypes.forEach(t => referencedTypes.add(t));
|
|
640
|
-
// Collect external type imports needed
|
|
641
|
-
const externalImports = this.collectExternalImports(referencedTypes, genericTypeParamNames);
|
|
642
618
|
// Include enums that are actually referenced, from this module or others
|
|
643
619
|
const referencedEnums = [];
|
|
644
|
-
//
|
|
620
|
+
// First add enums from this module
|
|
621
|
+
enums.filter(enumDef => referencedTypes.has(enumDef.name)).forEach(enumDef => referencedEnums.push(enumDef));
|
|
622
|
+
// Then add enums from other modules that are referenced
|
|
645
623
|
this.enums.forEach(enumDef => {
|
|
646
624
|
if (referencedTypes.has(enumDef.name) &&
|
|
625
|
+
enumDef.module !== moduleName &&
|
|
647
626
|
!referencedEnums.some(existing => existing.name === enumDef.name)) {
|
|
648
627
|
referencedEnums.push(enumDef);
|
|
649
628
|
}
|
|
650
629
|
});
|
|
651
|
-
// Include interfaces
|
|
630
|
+
// Include interfaces that are actually referenced, from this module or others
|
|
652
631
|
const referencedInterfaces = [];
|
|
653
|
-
//
|
|
632
|
+
// First add interfaces from this module
|
|
633
|
+
interfaces.filter(interfaceDef => referencedTypes.has(interfaceDef.name)).forEach(interfaceDef => referencedInterfaces.push(interfaceDef));
|
|
634
|
+
// Then add interfaces from other modules that are referenced
|
|
654
635
|
this.interfaces.forEach(interfaceDef => {
|
|
655
636
|
if (referencedTypes.has(interfaceDef.name) &&
|
|
637
|
+
interfaceDef.module !== moduleName &&
|
|
656
638
|
!referencedInterfaces.some(existing => existing.name === interfaceDef.name)) {
|
|
657
639
|
referencedInterfaces.push(interfaceDef);
|
|
658
640
|
}
|
|
659
641
|
});
|
|
660
|
-
//
|
|
661
|
-
|
|
642
|
+
// Recursively collect type dependencies from referenced interfaces
|
|
643
|
+
// Keep scanning until no new types are found
|
|
644
|
+
let prevSize = 0;
|
|
645
|
+
while (referencedTypes.size !== prevSize) {
|
|
646
|
+
prevSize = referencedTypes.size;
|
|
647
|
+
// Scan referenced interfaces for additional type dependencies
|
|
648
|
+
referencedInterfaces.forEach(interfaceDef => {
|
|
649
|
+
this.extractTypeNames(interfaceDef.source).forEach(typeName => {
|
|
650
|
+
if (!genericTypeParamNames.has(typeName)) {
|
|
651
|
+
referencedTypes.add(typeName);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
// Re-check interfaces after scanning for dependencies (for nested types)
|
|
656
|
+
this.interfaces.forEach(interfaceDef => {
|
|
657
|
+
if (referencedTypes.has(interfaceDef.name) &&
|
|
658
|
+
!referencedInterfaces.some(existing => existing.name === interfaceDef.name)) {
|
|
659
|
+
referencedInterfaces.push(interfaceDef);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
// Re-check enums after scanning interfaces for dependencies
|
|
664
|
+
this.enums.forEach(enumDef => {
|
|
665
|
+
if (referencedTypes.has(enumDef.name) &&
|
|
666
|
+
!referencedEnums.some(existing => existing.name === enumDef.name)) {
|
|
667
|
+
referencedEnums.push(enumDef);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
662
670
|
// Enums should come before interfaces that use them
|
|
663
671
|
const moduleEnums = referencedEnums.map(enumDef => enumDef.source).join('\n\n');
|
|
664
|
-
const moduleInterfaces =
|
|
672
|
+
const moduleInterfaces = referencedInterfaces.map(interfaceDef => interfaceDef.source).join('\n\n');
|
|
665
673
|
// Generate domain interface for this module
|
|
666
674
|
const domainMethodDefinitions = methods.map(method => {
|
|
667
675
|
const methodNameWithoutModule = method.methodName;
|
|
@@ -678,39 +686,136 @@ ${domainMethodDefinitions}
|
|
|
678
686
|
}`;
|
|
679
687
|
// Build file content with enums before interfaces
|
|
680
688
|
const typesSection = [moduleEnums, moduleInterfaces].filter(section => section.length > 0).join('\n\n');
|
|
681
|
-
// Generate
|
|
682
|
-
const
|
|
683
|
-
externalImports.forEach((types, packageName) => {
|
|
684
|
-
const sortedTypes = Array.from(types).sort();
|
|
685
|
-
importStatements.push(`import { ${sortedTypes.join(', ')} } from '${packageName}';`);
|
|
686
|
-
// Track that this external package is used
|
|
687
|
-
this.externalPackagesUsed.add(packageName);
|
|
688
|
-
});
|
|
689
|
-
const importsSection = importStatements.length > 0 ? importStatements.join('\n') + '\n\n' : '';
|
|
689
|
+
// Generate codec field metadata for types in this module only
|
|
690
|
+
const moduleCodecFields = this.generateCodecFieldsMetadata(moduleName);
|
|
690
691
|
const fileContent = `// Auto-generated RPC types for ${moduleName.charAt(0).toUpperCase() + moduleName.slice(1)} module
|
|
691
692
|
// Do not edit this file manually - it will be overwritten
|
|
692
693
|
//
|
|
693
694
|
// IMPORTANT: All types must be JSON-serializable for TCP transport when extracted to microservices
|
|
694
695
|
|
|
695
|
-
|
|
696
|
+
// ${moduleName.charAt(0).toUpperCase() + moduleName.slice(1)} module types
|
|
696
697
|
${typesSection}
|
|
697
698
|
|
|
698
699
|
${domainInterface}
|
|
700
|
+
${moduleCodecFields}
|
|
699
701
|
`;
|
|
700
702
|
// Write to configured output directory
|
|
701
703
|
const outputPath = path.join(this.options.rootDir, this.config.outputDir, `${moduleName}.rpc.gen.ts`);
|
|
702
704
|
fs.writeFileSync(outputPath, fileContent, 'utf8');
|
|
703
705
|
}
|
|
706
|
+
/** Generate codec metadata for types belonging to this module only */
|
|
707
|
+
generateCodecFieldsMetadata(moduleName) {
|
|
708
|
+
const codecEntries = [];
|
|
709
|
+
// Only include types that belong to this module
|
|
710
|
+
this.codecFields.forEach((fields, typeName) => {
|
|
711
|
+
const interfaceDef = this.interfaces.get(typeName);
|
|
712
|
+
if (interfaceDef && interfaceDef.module === moduleName && fields.size > 0) {
|
|
713
|
+
const fieldEntries = Array.from(fields.entries())
|
|
714
|
+
.map(([fieldName, codecName]) => ` ${fieldName}: '${codecName}'`)
|
|
715
|
+
.join(',\n');
|
|
716
|
+
codecEntries.push(` ${typeName}: {\n${fieldEntries}\n }`);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
if (codecEntries.length === 0) {
|
|
720
|
+
return '';
|
|
721
|
+
}
|
|
722
|
+
return `
|
|
723
|
+
// Type metadata for automatic codec transformation
|
|
724
|
+
// Maps type names to field -> codec name mappings
|
|
725
|
+
// Used by RPC client for transparent serialization (Date <-> string, etc.)
|
|
726
|
+
export const RpcTypeInfo = {
|
|
727
|
+
${codecEntries.join(',\n')}
|
|
728
|
+
} as const;
|
|
729
|
+
`;
|
|
730
|
+
}
|
|
731
|
+
/** Generate function metadata for RPC patterns (params and returns) */
|
|
732
|
+
generateRpcFunctionInfo(moduleGroups) {
|
|
733
|
+
const entries = [];
|
|
734
|
+
Object.values(moduleGroups).forEach(methods => {
|
|
735
|
+
methods.forEach(method => {
|
|
736
|
+
// Extract param types that have codec fields
|
|
737
|
+
const paramEntries = [];
|
|
738
|
+
method.paramTypes.forEach(param => {
|
|
739
|
+
const typeName = this.extractMainTypeName(param.type);
|
|
740
|
+
if (typeName && this.codecFields.has(typeName)) {
|
|
741
|
+
paramEntries.push(` ${param.name}: '${typeName}'`);
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
// Extract return type if it has codec fields
|
|
745
|
+
const returnType = this.extractMainTypeName(method.returnType);
|
|
746
|
+
const hasReturnCodec = returnType && this.codecFields.has(returnType);
|
|
747
|
+
// Only include if there are codec fields in params or return
|
|
748
|
+
if (paramEntries.length > 0 || hasReturnCodec) {
|
|
749
|
+
const paramsObj = paramEntries.length > 0
|
|
750
|
+
? `{\n${paramEntries.join(',\n')}\n }`
|
|
751
|
+
: '{}';
|
|
752
|
+
const returnsValue = hasReturnCodec ? `'${returnType}'` : 'undefined';
|
|
753
|
+
entries.push(` '${method.pattern}': {\n params: ${paramsObj},\n returns: ${returnsValue}\n }`);
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
if (entries.length === 0) {
|
|
758
|
+
return '';
|
|
759
|
+
}
|
|
760
|
+
return `
|
|
761
|
+
// Function metadata for RPC patterns
|
|
762
|
+
// Maps patterns to their parameter and return type names for codec transformation
|
|
763
|
+
export const RpcFunctionInfo = {
|
|
764
|
+
${entries.join(',\n')}
|
|
765
|
+
} as const;
|
|
766
|
+
|
|
767
|
+
export type RpcFunctionInfoType = typeof RpcFunctionInfo;
|
|
768
|
+
`;
|
|
769
|
+
}
|
|
770
|
+
/** Extract the main type name from a type string (e.g., "User" from "Promise<User>") */
|
|
771
|
+
extractMainTypeName(typeStr) {
|
|
772
|
+
// Remove array brackets
|
|
773
|
+
let type = typeStr.replace(/\[\]/g, '');
|
|
774
|
+
// Remove Promise wrapper
|
|
775
|
+
const promiseMatch = type.match(/Promise<(.+)>/);
|
|
776
|
+
if (promiseMatch) {
|
|
777
|
+
type = promiseMatch[1];
|
|
778
|
+
}
|
|
779
|
+
// Get the first capitalized identifier
|
|
780
|
+
const match = type.match(/\b([A-Z][a-zA-Z0-9]*)\b/);
|
|
781
|
+
return match ? match[1] : undefined;
|
|
782
|
+
}
|
|
783
|
+
/** Generate imports and re-exports for codec fields from module files */
|
|
784
|
+
generateCodecFieldReExports(moduleGroups) {
|
|
785
|
+
const modulesWithCodecFields = [];
|
|
786
|
+
// Check which modules have codec fields
|
|
787
|
+
Object.keys(moduleGroups).forEach(moduleName => {
|
|
788
|
+
// Check if any types in this module have codec fields
|
|
789
|
+
const moduleTypes = Array.from(this.interfaces.entries())
|
|
790
|
+
.filter(([_, def]) => def.module === moduleName)
|
|
791
|
+
.map(([name]) => name);
|
|
792
|
+
const hasCodecFields = moduleTypes.some(typeName => this.codecFields.has(typeName));
|
|
793
|
+
if (hasCodecFields) {
|
|
794
|
+
modulesWithCodecFields.push(moduleName);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
if (modulesWithCodecFields.length === 0) {
|
|
798
|
+
return { imports: '', exports: '' };
|
|
799
|
+
}
|
|
800
|
+
// Generate imports with aliases to avoid conflicts
|
|
801
|
+
const imports = modulesWithCodecFields
|
|
802
|
+
.map(mod => `import { RpcTypeInfo as ${this.toCamelCase(mod)}TypeInfo } from './${mod}.rpc.gen';`)
|
|
803
|
+
.join('\n');
|
|
804
|
+
// Generate merged export
|
|
805
|
+
const mergeEntries = modulesWithCodecFields
|
|
806
|
+
.map(mod => ` ...${this.toCamelCase(mod)}TypeInfo`)
|
|
807
|
+
.join(',\n');
|
|
808
|
+
const exports = `
|
|
809
|
+
// Merged type metadata from all modules
|
|
810
|
+
export const RpcTypeInfo = {
|
|
811
|
+
${mergeEntries}
|
|
812
|
+
} as const;
|
|
813
|
+
`;
|
|
814
|
+
return { imports, exports };
|
|
815
|
+
}
|
|
704
816
|
generateMainTypesFile(moduleGroups) {
|
|
705
817
|
const hasModules = Object.keys(moduleGroups).length > 0;
|
|
706
|
-
// Helper to check if a type is external (imported from an npm package)
|
|
707
|
-
const isExternalType = (typeName) => {
|
|
708
|
-
return this.typeToPackageMap.has(typeName) &&
|
|
709
|
-
!this.interfaces.has(typeName) &&
|
|
710
|
-
!this.enums.has(typeName);
|
|
711
|
-
};
|
|
712
818
|
// Generate imports from module files - include domain interfaces and types
|
|
713
|
-
// but EXCLUDE external types (they're imported in module files, not exported)
|
|
714
819
|
const moduleImports = Object.keys(moduleGroups).map(moduleName => {
|
|
715
820
|
// Collect all types referenced in this module's methods
|
|
716
821
|
const referencedTypes = new Set();
|
|
@@ -745,8 +850,7 @@ ${domainInterface}
|
|
|
745
850
|
});
|
|
746
851
|
}
|
|
747
852
|
});
|
|
748
|
-
|
|
749
|
-
const typesList = Array.from(referencedTypes).filter(type => !this.isBuiltInType(type) && !this.isInternalType(type) && !isExternalType(type));
|
|
853
|
+
const typesList = Array.from(referencedTypes).filter(type => !this.isBuiltInType(type) && !this.isInternalType(type));
|
|
750
854
|
const imports = [`${this.toCamelCase(moduleName)}Domain`];
|
|
751
855
|
if (typesList.length > 0) {
|
|
752
856
|
imports.push(...typesList);
|
|
@@ -754,7 +858,6 @@ ${domainInterface}
|
|
|
754
858
|
return `import { ${imports.join(', ')} } from './${moduleName}.rpc.gen';`;
|
|
755
859
|
}).join('\n');
|
|
756
860
|
// Generate selective re-exports to avoid type conflicts
|
|
757
|
-
// EXCLUDE external types - they should be imported directly from their packages
|
|
758
861
|
const moduleReExports = Object.keys(moduleGroups).map(moduleName => {
|
|
759
862
|
// Collect all types referenced in this module's methods
|
|
760
863
|
const referencedTypes = new Set();
|
|
@@ -789,8 +892,7 @@ ${domainInterface}
|
|
|
789
892
|
});
|
|
790
893
|
}
|
|
791
894
|
});
|
|
792
|
-
|
|
793
|
-
const typesList = Array.from(referencedTypes).filter(type => !this.isBuiltInType(type) && !this.isInternalType(type) && !isExternalType(type));
|
|
895
|
+
const typesList = Array.from(referencedTypes).filter(type => !this.isBuiltInType(type) && !this.isInternalType(type));
|
|
794
896
|
const exports = [`${this.toCamelCase(moduleName)}Domain`];
|
|
795
897
|
if (typesList.length > 0) {
|
|
796
898
|
exports.push(...typesList);
|
|
@@ -799,58 +901,6 @@ ${domainInterface}
|
|
|
799
901
|
}).join('\n');
|
|
800
902
|
// Generate common type re-exports from their original modules
|
|
801
903
|
const commonTypeExports = this.generateCommonTypeExports(moduleGroups);
|
|
802
|
-
// Collect all external types used across all modules for direct import/re-export
|
|
803
|
-
const externalTypesUsed = new Map(); // package -> types
|
|
804
|
-
Object.values(moduleGroups).forEach(methods => {
|
|
805
|
-
methods.forEach(method => {
|
|
806
|
-
const genericTypeParamNames = new Set();
|
|
807
|
-
if (method.typeParameters) {
|
|
808
|
-
method.typeParameters.forEach(typeParam => {
|
|
809
|
-
genericTypeParamNames.add(typeParam.split(' ')[0]);
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
// Collect types from params and return type
|
|
813
|
-
const allTypes = new Set();
|
|
814
|
-
method.paramTypes.forEach(param => {
|
|
815
|
-
this.extractTypeNames(param.type).forEach(t => {
|
|
816
|
-
if (!genericTypeParamNames.has(t))
|
|
817
|
-
allTypes.add(t);
|
|
818
|
-
});
|
|
819
|
-
});
|
|
820
|
-
this.extractTypeNames(method.returnType).forEach(t => {
|
|
821
|
-
if (!genericTypeParamNames.has(t))
|
|
822
|
-
allTypes.add(t);
|
|
823
|
-
});
|
|
824
|
-
// Check which are external types
|
|
825
|
-
allTypes.forEach(typeName => {
|
|
826
|
-
if (isExternalType(typeName)) {
|
|
827
|
-
const packageName = this.typeToPackageMap.get(typeName);
|
|
828
|
-
if (!externalTypesUsed.has(packageName)) {
|
|
829
|
-
externalTypesUsed.set(packageName, new Set());
|
|
830
|
-
}
|
|
831
|
-
externalTypesUsed.get(packageName).add(typeName);
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
});
|
|
835
|
-
});
|
|
836
|
-
// Generate import statements for external types
|
|
837
|
-
const externalImportStatements = [];
|
|
838
|
-
externalTypesUsed.forEach((types, packageName) => {
|
|
839
|
-
const sortedTypes = Array.from(types).sort();
|
|
840
|
-
externalImportStatements.push(`import type { ${sortedTypes.join(', ')} } from '${packageName}';`);
|
|
841
|
-
});
|
|
842
|
-
const externalImportsSection = externalImportStatements.length > 0
|
|
843
|
-
? externalImportStatements.join('\n') + '\n'
|
|
844
|
-
: '';
|
|
845
|
-
// Generate re-export statements for external types
|
|
846
|
-
const externalReExportStatements = [];
|
|
847
|
-
externalTypesUsed.forEach((types, packageName) => {
|
|
848
|
-
const sortedTypes = Array.from(types).sort();
|
|
849
|
-
externalReExportStatements.push(`export type { ${sortedTypes.join(', ')} } from '${packageName}';`);
|
|
850
|
-
});
|
|
851
|
-
const externalReExportsSection = externalReExportStatements.length > 0
|
|
852
|
-
? '\n// Re-export external types from their source packages\n' + externalReExportStatements.join('\n')
|
|
853
|
-
: '';
|
|
854
904
|
// Generate AllRpcMethods type for MessageBus
|
|
855
905
|
const allRpcMethodsType = hasModules
|
|
856
906
|
? this.generateAllRpcMethodsType(moduleGroups)
|
|
@@ -868,19 +918,23 @@ ${Object.keys(moduleGroups).map(moduleName => ` ${moduleName}: ${this.toCamelCa
|
|
|
868
918
|
export interface IRpcClient {
|
|
869
919
|
// No RPC domains available
|
|
870
920
|
}`;
|
|
921
|
+
// Generate RPC function info (params and returns) for codec transformation
|
|
922
|
+
const rpcFunctionInfo = this.generateRpcFunctionInfo(moduleGroups);
|
|
923
|
+
// Generate codec field imports and re-exports from module files
|
|
924
|
+
const codecFieldImportsAndExports = this.generateCodecFieldReExports(moduleGroups);
|
|
871
925
|
const fileContent = `// Auto-generated RPC types from all modules
|
|
872
926
|
// Do not edit this file manually - it will be overwritten
|
|
873
927
|
//
|
|
874
928
|
// SERIALIZATION REQUIREMENTS:
|
|
875
929
|
// All @RpcMethod parameters and return types must be JSON-serializable for TCP transport.
|
|
876
930
|
// Avoid: functions, callbacks, Buffer, Map/Set, DOM elements, class instances, undefined
|
|
877
|
-
// Prefer: primitives, plain objects, arrays, null (instead of undefined)
|
|
931
|
+
// Prefer: primitives, plain objects, arrays, null (instead of undefined), Date (auto-converted)
|
|
878
932
|
|
|
879
|
-
${
|
|
933
|
+
${moduleImports}
|
|
934
|
+
${codecFieldImportsAndExports.imports}
|
|
880
935
|
|
|
881
936
|
// Re-export domain interfaces and types
|
|
882
937
|
${moduleReExports}
|
|
883
|
-
${externalReExportsSection}
|
|
884
938
|
|
|
885
939
|
// Re-export common types from their primary modules
|
|
886
940
|
${commonTypeExports}
|
|
@@ -888,12 +942,16 @@ ${commonTypeExports}
|
|
|
888
942
|
${allRpcMethodsType}
|
|
889
943
|
|
|
890
944
|
${rpcClientInterface}
|
|
891
|
-
|
|
945
|
+
${codecFieldImportsAndExports.exports}${rpcFunctionInfo}
|
|
892
946
|
// Usage examples:
|
|
893
|
-
// import {
|
|
947
|
+
// import { RpcTypeInfo, RpcFunctionInfo } from '@your-org/lib-rpc';
|
|
948
|
+
// import { createRpcClientProxy } from '@zdavison/nestjs-rpc-toolkit';
|
|
894
949
|
//
|
|
895
|
-
// const
|
|
896
|
-
//
|
|
950
|
+
// const rpc = createRpcClientProxy(client, {
|
|
951
|
+
// typeInfo: RpcTypeInfo,
|
|
952
|
+
// functionInfo: RpcFunctionInfo,
|
|
953
|
+
// });
|
|
954
|
+
// const user = await rpc.user.create({ ... }); // Dates auto-converted
|
|
897
955
|
`;
|
|
898
956
|
// Write to configured output directory
|
|
899
957
|
const outputPath = path.join(this.options.rootDir, this.config.outputDir, 'all.rpc.gen.ts');
|
|
@@ -915,64 +973,6 @@ ${rpcClientInterface}
|
|
|
915
973
|
console.log(` 📄 ${module}: ${methods.length} methods`);
|
|
916
974
|
});
|
|
917
975
|
}
|
|
918
|
-
// Update output package.json with missing dependencies
|
|
919
|
-
this.updateOutputPackageJson();
|
|
920
|
-
}
|
|
921
|
-
updateOutputPackageJson() {
|
|
922
|
-
if (this.externalPackagesUsed.size === 0) {
|
|
923
|
-
return; // No external packages to add
|
|
924
|
-
}
|
|
925
|
-
// Find the package.json for the output directory
|
|
926
|
-
const outputDir = path.join(this.options.rootDir, this.config.outputDir);
|
|
927
|
-
const packageJsonPath = this.findPackageJsonForOutput(outputDir);
|
|
928
|
-
if (!packageJsonPath) {
|
|
929
|
-
console.log(`⚠️ Could not find package.json for output directory ${this.config.outputDir}`);
|
|
930
|
-
console.log(` External packages used: ${Array.from(this.externalPackagesUsed).join(', ')}`);
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
try {
|
|
934
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
935
|
-
const dependencies = packageJson.dependencies || {};
|
|
936
|
-
const missingDeps = [];
|
|
937
|
-
const addedDeps = {};
|
|
938
|
-
// Check which external packages are missing
|
|
939
|
-
this.externalPackagesUsed.forEach(packageName => {
|
|
940
|
-
if (!dependencies[packageName]) {
|
|
941
|
-
missingDeps.push(packageName);
|
|
942
|
-
const version = this.packageVersionMap.get(packageName) || 'workspace:*';
|
|
943
|
-
addedDeps[packageName] = version;
|
|
944
|
-
dependencies[packageName] = version;
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
if (missingDeps.length > 0) {
|
|
948
|
-
// Update package.json with new dependencies
|
|
949
|
-
packageJson.dependencies = dependencies;
|
|
950
|
-
// Write back to file with proper formatting
|
|
951
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
|
|
952
|
-
console.log(`📦 Updated ${path.relative(this.options.rootDir, packageJsonPath)} with missing dependencies:`);
|
|
953
|
-
missingDeps.forEach(dep => {
|
|
954
|
-
console.log(` ✓ ${dep}@${addedDeps[dep]}`);
|
|
955
|
-
});
|
|
956
|
-
// Detect package manager and show appropriate install command
|
|
957
|
-
const packageManager = (0, package_manager_utils_1.detectPackageManager)(this.options.rootDir);
|
|
958
|
-
console.log(`\n⚠️ Please run '${packageManager} install' to install the new dependencies before building.\n`);
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
catch (error) {
|
|
962
|
-
console.error(`❌ Error updating package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
findPackageJsonForOutput(outputDir) {
|
|
966
|
-
// Walk up from output directory to find package.json
|
|
967
|
-
let currentDir = outputDir;
|
|
968
|
-
while (currentDir !== path.dirname(currentDir)) { // Stop at root
|
|
969
|
-
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
970
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
971
|
-
return packageJsonPath;
|
|
972
|
-
}
|
|
973
|
-
currentDir = path.dirname(currentDir);
|
|
974
|
-
}
|
|
975
|
-
return null;
|
|
976
976
|
}
|
|
977
977
|
generateParamsType(params) {
|
|
978
978
|
if (params.length === 0)
|
|
@@ -1001,15 +1001,15 @@ ${rpcClientInterface}
|
|
|
1001
1001
|
}
|
|
1002
1002
|
extractTypeNames(typeString) {
|
|
1003
1003
|
const typeNames = new Set();
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
.replace(
|
|
1008
|
-
.replace(
|
|
1004
|
+
// Strip JSDoc comments before extracting type names
|
|
1005
|
+
// This prevents matching words in comments (e.g., "User" in "from a user")
|
|
1006
|
+
const withoutJsDoc = typeString
|
|
1007
|
+
.replace(/\/\*\*[\s\S]*?\*\//g, '') // Remove /** ... */ comments
|
|
1008
|
+
.replace(/\/\/[^\n]*/g, ''); // Remove // comments
|
|
1009
1009
|
// Match type names (letters, numbers, underscore, $)
|
|
1010
1010
|
// This regex will match identifiers that could be type names
|
|
1011
1011
|
const typeNameRegex = /\b[A-Z][a-zA-Z0-9_$]*\b/g;
|
|
1012
|
-
const matches =
|
|
1012
|
+
const matches = withoutJsDoc.match(typeNameRegex);
|
|
1013
1013
|
if (matches) {
|
|
1014
1014
|
matches.forEach(match => {
|
|
1015
1015
|
// Exclude built-in types and common generic types
|
|
@@ -1131,73 +1131,6 @@ export type AllRpcMethods = {
|
|
|
1131
1131
|
${methodEntries.join('\n')}
|
|
1132
1132
|
};`;
|
|
1133
1133
|
}
|
|
1134
|
-
/**
|
|
1135
|
-
* Topologically sort types so that dependencies come before dependents.
|
|
1136
|
-
* This ensures type aliases and interfaces are defined before they are used.
|
|
1137
|
-
*/
|
|
1138
|
-
topologicalSortTypes(types, genericTypeParamNames) {
|
|
1139
|
-
if (types.length === 0)
|
|
1140
|
-
return [];
|
|
1141
|
-
// Build a dependency graph
|
|
1142
|
-
const typeNames = new Set(types.map(t => t.name));
|
|
1143
|
-
const dependencies = new Map();
|
|
1144
|
-
types.forEach(typeDef => {
|
|
1145
|
-
const deps = new Set();
|
|
1146
|
-
this.extractTypeNames(typeDef.source).forEach(depName => {
|
|
1147
|
-
// Only consider dependencies that are in our type set and not generic params
|
|
1148
|
-
if (typeNames.has(depName) && depName !== typeDef.name && !genericTypeParamNames.has(depName)) {
|
|
1149
|
-
deps.add(depName);
|
|
1150
|
-
}
|
|
1151
|
-
});
|
|
1152
|
-
dependencies.set(typeDef.name, deps);
|
|
1153
|
-
});
|
|
1154
|
-
// Kahn's algorithm for topological sort
|
|
1155
|
-
// We want dependencies to come BEFORE dependents
|
|
1156
|
-
const sorted = [];
|
|
1157
|
-
const typeMap = new Map(types.map(t => [t.name, t]));
|
|
1158
|
-
// In-degree = number of dependencies a type has (within our type set)
|
|
1159
|
-
// Types with 0 dependencies should be output first
|
|
1160
|
-
const inDegree = new Map();
|
|
1161
|
-
typeNames.forEach(name => {
|
|
1162
|
-
const deps = dependencies.get(name) || new Set();
|
|
1163
|
-
inDegree.set(name, deps.size);
|
|
1164
|
-
});
|
|
1165
|
-
// Start with types that have no dependencies
|
|
1166
|
-
const queue = [];
|
|
1167
|
-
inDegree.forEach((degree, name) => {
|
|
1168
|
-
if (degree === 0) {
|
|
1169
|
-
queue.push(name);
|
|
1170
|
-
}
|
|
1171
|
-
});
|
|
1172
|
-
while (queue.length > 0) {
|
|
1173
|
-
const name = queue.shift();
|
|
1174
|
-
const typeDef = typeMap.get(name);
|
|
1175
|
-
if (typeDef) {
|
|
1176
|
-
sorted.push(typeDef);
|
|
1177
|
-
}
|
|
1178
|
-
// For each type that depends on this one, decrement its in-degree
|
|
1179
|
-
// (because one of its dependencies has now been processed)
|
|
1180
|
-
typeNames.forEach(dependentName => {
|
|
1181
|
-
const deps = dependencies.get(dependentName);
|
|
1182
|
-
if (deps && deps.has(name)) {
|
|
1183
|
-
const newDegree = (inDegree.get(dependentName) || 1) - 1;
|
|
1184
|
-
inDegree.set(dependentName, newDegree);
|
|
1185
|
-
if (newDegree === 0) {
|
|
1186
|
-
queue.push(dependentName);
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
// If there's a cycle, just append remaining types (they have circular deps)
|
|
1192
|
-
if (sorted.length < types.length) {
|
|
1193
|
-
types.forEach(t => {
|
|
1194
|
-
if (!sorted.includes(t)) {
|
|
1195
|
-
sorted.push(t);
|
|
1196
|
-
}
|
|
1197
|
-
});
|
|
1198
|
-
}
|
|
1199
|
-
return sorted;
|
|
1200
|
-
}
|
|
1201
1134
|
}
|
|
1202
1135
|
exports.RpcTypesGenerator = RpcTypesGenerator;
|
|
1203
1136
|
//# sourceMappingURL=rpc-types-generator.js.map
|