@honestjs/rpc-plugin 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -5
- package/dist/index.d.mts +59 -44
- package/dist/index.d.ts +59 -44
- package/dist/index.js +179 -137
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +170 -130
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
package/dist/index.js
CHANGED
|
@@ -42,24 +42,33 @@ __export(index_exports, {
|
|
|
42
42
|
buildFullApiPath: () => buildFullApiPath,
|
|
43
43
|
buildFullPath: () => buildFullPath,
|
|
44
44
|
camelCase: () => camelCase,
|
|
45
|
+
computeHash: () => computeHash,
|
|
45
46
|
extractNamedType: () => extractNamedType,
|
|
46
|
-
generateTypeImports: () => generateTypeImports,
|
|
47
47
|
generateTypeScriptInterface: () => generateTypeScriptInterface,
|
|
48
48
|
mapJsonSchemaTypeToTypeScript: () => mapJsonSchemaTypeToTypeScript,
|
|
49
|
-
|
|
49
|
+
readChecksum: () => readChecksum,
|
|
50
|
+
safeToString: () => safeToString,
|
|
51
|
+
writeChecksum: () => writeChecksum
|
|
50
52
|
});
|
|
51
53
|
module.exports = __toCommonJS(index_exports);
|
|
52
54
|
|
|
53
55
|
// src/rpc.plugin.ts
|
|
54
|
-
var
|
|
55
|
-
var
|
|
56
|
+
var import_fs2 = __toESM(require("fs"));
|
|
57
|
+
var import_path3 = __toESM(require("path"));
|
|
58
|
+
var import_ts_morph = require("ts-morph");
|
|
56
59
|
|
|
57
60
|
// src/constants/defaults.ts
|
|
58
61
|
var DEFAULT_OPTIONS = {
|
|
59
62
|
controllerPattern: "src/modules/*/*.controller.ts",
|
|
60
63
|
tsConfigPath: "tsconfig.json",
|
|
61
64
|
outputDir: "./generated/rpc",
|
|
62
|
-
generateOnInit: true
|
|
65
|
+
generateOnInit: true,
|
|
66
|
+
context: {
|
|
67
|
+
namespace: "rpc",
|
|
68
|
+
keys: {
|
|
69
|
+
artifact: "artifact"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
63
72
|
};
|
|
64
73
|
var LOG_PREFIX = "[ RPCPlugin ]";
|
|
65
74
|
var BUILTIN_UTILITY_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -77,29 +86,66 @@ var BUILTIN_UTILITY_TYPES = /* @__PURE__ */ new Set([
|
|
|
77
86
|
var BUILTIN_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "any", "void", "unknown"]);
|
|
78
87
|
var GENERIC_TYPES = /* @__PURE__ */ new Set(["Array", "Promise", "Partial"]);
|
|
79
88
|
|
|
80
|
-
// src/
|
|
81
|
-
var
|
|
89
|
+
// src/utils/hash-utils.ts
|
|
90
|
+
var import_crypto = require("crypto");
|
|
91
|
+
var import_fs = require("fs");
|
|
92
|
+
var import_promises = require("fs/promises");
|
|
82
93
|
var import_path = __toESM(require("path"));
|
|
94
|
+
var CHECKSUM_FILENAME = ".rpc-checksum";
|
|
95
|
+
function computeHash(filePaths) {
|
|
96
|
+
const sorted = [...filePaths].sort();
|
|
97
|
+
const hasher = (0, import_crypto.createHash)("sha256");
|
|
98
|
+
hasher.update(`files:${sorted.length}
|
|
99
|
+
`);
|
|
100
|
+
for (const filePath of sorted) {
|
|
101
|
+
hasher.update((0, import_fs.readFileSync)(filePath, "utf-8"));
|
|
102
|
+
hasher.update("\0");
|
|
103
|
+
}
|
|
104
|
+
return hasher.digest("hex");
|
|
105
|
+
}
|
|
106
|
+
function readChecksum(outputDir) {
|
|
107
|
+
const checksumPath = import_path.default.join(outputDir, CHECKSUM_FILENAME);
|
|
108
|
+
if (!(0, import_fs.existsSync)(checksumPath)) return null;
|
|
109
|
+
try {
|
|
110
|
+
const raw = (0, import_fs.readFileSync)(checksumPath, "utf-8");
|
|
111
|
+
const data = JSON.parse(raw);
|
|
112
|
+
if (typeof data.hash !== "string" || !Array.isArray(data.files)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return data;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function writeChecksum(outputDir, data) {
|
|
121
|
+
await (0, import_promises.mkdir)(outputDir, { recursive: true });
|
|
122
|
+
const checksumPath = import_path.default.join(outputDir, CHECKSUM_FILENAME);
|
|
123
|
+
await (0, import_promises.writeFile)(checksumPath, JSON.stringify(data, null, 2), "utf-8");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/services/client-generator.service.ts
|
|
127
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
128
|
+
var import_path2 = __toESM(require("path"));
|
|
83
129
|
|
|
84
130
|
// src/utils/path-utils.ts
|
|
85
131
|
function buildFullPath(basePath, parameters) {
|
|
86
132
|
if (!basePath || typeof basePath !== "string") return "/";
|
|
87
|
-
let
|
|
133
|
+
let path4 = basePath;
|
|
88
134
|
if (parameters && Array.isArray(parameters)) {
|
|
89
135
|
for (const param of parameters) {
|
|
90
136
|
if (param.data && typeof param.data === "string" && param.data.startsWith(":")) {
|
|
91
137
|
const paramName = param.data.slice(1);
|
|
92
|
-
|
|
138
|
+
path4 = path4.replace(`:${paramName}`, `\${${paramName}}`);
|
|
93
139
|
}
|
|
94
140
|
}
|
|
95
141
|
}
|
|
96
|
-
return
|
|
142
|
+
return path4;
|
|
97
143
|
}
|
|
98
144
|
function buildFullApiPath(route) {
|
|
99
145
|
const prefix = route.prefix || "";
|
|
100
146
|
const version = route.version || "";
|
|
101
147
|
const routePath = route.route || "";
|
|
102
|
-
const
|
|
148
|
+
const path4 = route.path || "";
|
|
103
149
|
let fullPath = "";
|
|
104
150
|
if (prefix && prefix !== "/") {
|
|
105
151
|
fullPath += prefix.replace(/^\/+|\/+$/g, "");
|
|
@@ -110,11 +156,12 @@ function buildFullApiPath(route) {
|
|
|
110
156
|
if (routePath && routePath !== "/") {
|
|
111
157
|
fullPath += `/${routePath.replace(/^\/+|\/+$/g, "")}`;
|
|
112
158
|
}
|
|
113
|
-
if (
|
|
114
|
-
fullPath += `/${
|
|
115
|
-
} else if (
|
|
159
|
+
if (path4 && path4 !== "/") {
|
|
160
|
+
fullPath += `/${path4.replace(/^\/+|\/+$/g, "")}`;
|
|
161
|
+
} else if (path4 === "/") {
|
|
116
162
|
fullPath += "/";
|
|
117
163
|
}
|
|
164
|
+
if (fullPath && !fullPath.startsWith("/")) fullPath = "/" + fullPath;
|
|
118
165
|
return fullPath || "/";
|
|
119
166
|
}
|
|
120
167
|
|
|
@@ -137,10 +184,10 @@ var ClientGeneratorService = class {
|
|
|
137
184
|
* Generates the TypeScript RPC client
|
|
138
185
|
*/
|
|
139
186
|
async generateClient(routes, schemas) {
|
|
140
|
-
await
|
|
187
|
+
await import_promises2.default.mkdir(this.outputDir, { recursive: true });
|
|
141
188
|
await this.generateClientFile(routes, schemas);
|
|
142
189
|
const generatedInfo = {
|
|
143
|
-
clientFile:
|
|
190
|
+
clientFile: import_path2.default.join(this.outputDir, "client.ts"),
|
|
144
191
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
145
192
|
};
|
|
146
193
|
return generatedInfo;
|
|
@@ -150,8 +197,8 @@ var ClientGeneratorService = class {
|
|
|
150
197
|
*/
|
|
151
198
|
async generateClientFile(routes, schemas) {
|
|
152
199
|
const clientContent = this.generateClientContent(routes, schemas);
|
|
153
|
-
const clientPath =
|
|
154
|
-
await
|
|
200
|
+
const clientPath = import_path2.default.join(this.outputDir, "client.ts");
|
|
201
|
+
await import_promises2.default.writeFile(clientPath, clientContent, "utf-8");
|
|
155
202
|
}
|
|
156
203
|
/**
|
|
157
204
|
* Generates the client TypeScript content with types included
|
|
@@ -310,6 +357,14 @@ export class ApiClient {
|
|
|
310
357
|
|
|
311
358
|
try {
|
|
312
359
|
const response = await this.fetchFn(url.toString(), requestOptions)
|
|
360
|
+
|
|
361
|
+
if (response.status === 204 || response.headers.get('content-length') === '0') {
|
|
362
|
+
if (!response.ok) {
|
|
363
|
+
throw new ApiError(response.status, 'Request failed')
|
|
364
|
+
}
|
|
365
|
+
return undefined as T
|
|
366
|
+
}
|
|
367
|
+
|
|
313
368
|
const responseData = await response.json()
|
|
314
369
|
|
|
315
370
|
if (!response.ok) {
|
|
@@ -452,71 +507,30 @@ ${this.generateControllerMethods(controllerGroups)}
|
|
|
452
507
|
*/
|
|
453
508
|
analyzeRouteParameters(route) {
|
|
454
509
|
const parameters = route.parameters || [];
|
|
455
|
-
const
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
return !!pathSegment && typeof pathSegment === "string" && route.path.includes(`:${pathSegment}`);
|
|
459
|
-
};
|
|
460
|
-
const pathParams = parameters.filter((p) => isInPath(p)).map((p) => ({ ...p, required: true }));
|
|
461
|
-
const rawBody = parameters.filter((p) => !isInPath(p) && method !== "get");
|
|
462
|
-
const bodyParams = rawBody.map((p) => ({
|
|
463
|
-
...p,
|
|
464
|
-
required: true
|
|
465
|
-
}));
|
|
466
|
-
const queryParams = parameters.filter((p) => !isInPath(p) && method === "get").map((p) => ({
|
|
467
|
-
...p,
|
|
468
|
-
required: p.required === true
|
|
469
|
-
// default false if not provided
|
|
470
|
-
}));
|
|
510
|
+
const pathParams = parameters.filter((p) => p.decoratorType === "param").map((p) => ({ ...p, required: true }));
|
|
511
|
+
const bodyParams = parameters.filter((p) => p.decoratorType === "body").map((p) => ({ ...p, required: true }));
|
|
512
|
+
const queryParams = parameters.filter((p) => p.decoratorType === "query").map((p) => ({ ...p, required: p.required === true }));
|
|
471
513
|
return { pathParams, queryParams, bodyParams };
|
|
472
514
|
}
|
|
473
515
|
};
|
|
474
516
|
|
|
475
517
|
// src/services/route-analyzer.service.ts
|
|
476
518
|
var import_honestjs = require("honestjs");
|
|
477
|
-
var import_ts_morph = require("ts-morph");
|
|
478
519
|
var RouteAnalyzerService = class {
|
|
479
|
-
constructor(controllerPattern, tsConfigPath) {
|
|
480
|
-
this.controllerPattern = controllerPattern;
|
|
481
|
-
this.tsConfigPath = tsConfigPath;
|
|
482
|
-
}
|
|
483
|
-
// Track projects for cleanup
|
|
484
|
-
projects = [];
|
|
485
520
|
/**
|
|
486
521
|
* Analyzes controller methods to extract type information
|
|
487
522
|
*/
|
|
488
|
-
async analyzeControllerMethods() {
|
|
523
|
+
async analyzeControllerMethods(project) {
|
|
489
524
|
const routes = import_honestjs.RouteRegistry.getRoutes();
|
|
490
525
|
if (!routes?.length) {
|
|
491
526
|
return [];
|
|
492
527
|
}
|
|
493
|
-
const project = this.createProject();
|
|
494
528
|
const controllers = this.findControllerClasses(project);
|
|
495
529
|
if (controllers.size === 0) {
|
|
496
530
|
return [];
|
|
497
531
|
}
|
|
498
532
|
return this.processRoutes(routes, controllers);
|
|
499
533
|
}
|
|
500
|
-
/**
|
|
501
|
-
* Creates a new ts-morph project
|
|
502
|
-
*/
|
|
503
|
-
createProject() {
|
|
504
|
-
const project = new import_ts_morph.Project({
|
|
505
|
-
tsConfigFilePath: this.tsConfigPath
|
|
506
|
-
});
|
|
507
|
-
project.addSourceFilesAtPaths([this.controllerPattern]);
|
|
508
|
-
this.projects.push(project);
|
|
509
|
-
return project;
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Cleanup resources to prevent memory leaks
|
|
513
|
-
*/
|
|
514
|
-
dispose() {
|
|
515
|
-
this.projects.forEach((project) => {
|
|
516
|
-
project.getSourceFiles().forEach((file) => project.removeSourceFile(file));
|
|
517
|
-
});
|
|
518
|
-
this.projects = [];
|
|
519
|
-
}
|
|
520
534
|
/**
|
|
521
535
|
* Finds controller classes in the project
|
|
522
536
|
*/
|
|
@@ -539,23 +553,17 @@ var RouteAnalyzerService = class {
|
|
|
539
553
|
*/
|
|
540
554
|
processRoutes(routes, controllers) {
|
|
541
555
|
const analyzedRoutes = [];
|
|
542
|
-
const errors = [];
|
|
543
556
|
for (const route of routes) {
|
|
544
557
|
try {
|
|
545
558
|
const extendedRoute = this.createExtendedRoute(route, controllers);
|
|
546
559
|
analyzedRoutes.push(extendedRoute);
|
|
547
560
|
} catch (routeError) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
console.error(
|
|
551
|
-
`Error processing route ${safeToString(route.controller)}.${safeToString(route.handler)}:`,
|
|
561
|
+
console.warn(
|
|
562
|
+
`${LOG_PREFIX} Skipping route ${safeToString(route.controller)}.${safeToString(route.handler)}:`,
|
|
552
563
|
routeError
|
|
553
564
|
);
|
|
554
565
|
}
|
|
555
566
|
}
|
|
556
|
-
if (errors.length > 0) {
|
|
557
|
-
throw new Error(`Failed to process ${errors.length} routes: ${errors.map((e) => e.message).join(", ")}`);
|
|
558
|
-
}
|
|
559
567
|
return analyzedRoutes;
|
|
560
568
|
}
|
|
561
569
|
/**
|
|
@@ -608,6 +616,7 @@ var RouteAnalyzerService = class {
|
|
|
608
616
|
const sortedParams = [...parameters].sort((a, b) => a.index - b.index);
|
|
609
617
|
for (const param of sortedParams) {
|
|
610
618
|
const index = param.index;
|
|
619
|
+
const decoratorType = param.name;
|
|
611
620
|
if (index < declaredParams.length) {
|
|
612
621
|
const declaredParam = declaredParams[index];
|
|
613
622
|
const paramName = declaredParam.getName();
|
|
@@ -615,6 +624,7 @@ var RouteAnalyzerService = class {
|
|
|
615
624
|
result.push({
|
|
616
625
|
index,
|
|
617
626
|
name: paramName,
|
|
627
|
+
decoratorType,
|
|
618
628
|
type: paramType,
|
|
619
629
|
required: true,
|
|
620
630
|
data: param.data,
|
|
@@ -625,6 +635,7 @@ var RouteAnalyzerService = class {
|
|
|
625
635
|
result.push({
|
|
626
636
|
index,
|
|
627
637
|
name: `param${index}`,
|
|
638
|
+
decoratorType,
|
|
628
639
|
type: param.metatype?.name || "unknown",
|
|
629
640
|
required: true,
|
|
630
641
|
data: param.data,
|
|
@@ -639,7 +650,6 @@ var RouteAnalyzerService = class {
|
|
|
639
650
|
|
|
640
651
|
// src/services/schema-generator.service.ts
|
|
641
652
|
var import_ts_json_schema_generator = require("ts-json-schema-generator");
|
|
642
|
-
var import_ts_morph2 = require("ts-morph");
|
|
643
653
|
|
|
644
654
|
// src/utils/schema-utils.ts
|
|
645
655
|
function mapJsonSchemaTypeToTypeScript(schema) {
|
|
@@ -706,32 +716,6 @@ function extractNamedType(type) {
|
|
|
706
716
|
if (BUILTIN_TYPES.has(name)) return null;
|
|
707
717
|
return name;
|
|
708
718
|
}
|
|
709
|
-
function generateTypeImports(routes) {
|
|
710
|
-
const types = /* @__PURE__ */ new Set();
|
|
711
|
-
for (const route of routes) {
|
|
712
|
-
if (route.parameters) {
|
|
713
|
-
for (const param of route.parameters) {
|
|
714
|
-
if (param.type && !["string", "number", "boolean"].includes(param.type)) {
|
|
715
|
-
const typeMatch = param.type.match(/(\w+)(?:<.*>)?/);
|
|
716
|
-
if (typeMatch) {
|
|
717
|
-
const typeName = typeMatch[1];
|
|
718
|
-
if (!BUILTIN_UTILITY_TYPES.has(typeName)) {
|
|
719
|
-
types.add(typeName);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
if (route.returns) {
|
|
726
|
-
const returnType = route.returns.replace(/Promise<(.+)>/, "$1");
|
|
727
|
-
const baseType = returnType.replace(/\[\]$/, "");
|
|
728
|
-
if (!["string", "number", "boolean", "any", "void", "unknown"].includes(baseType)) {
|
|
729
|
-
types.add(baseType);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
return Array.from(types).join(", ");
|
|
734
|
-
}
|
|
735
719
|
|
|
736
720
|
// src/services/schema-generator.service.ts
|
|
737
721
|
var SchemaGeneratorService = class {
|
|
@@ -739,37 +723,14 @@ var SchemaGeneratorService = class {
|
|
|
739
723
|
this.controllerPattern = controllerPattern;
|
|
740
724
|
this.tsConfigPath = tsConfigPath;
|
|
741
725
|
}
|
|
742
|
-
// Track projects for cleanup
|
|
743
|
-
projects = [];
|
|
744
726
|
/**
|
|
745
727
|
* Generates JSON schemas from types used in controllers
|
|
746
728
|
*/
|
|
747
|
-
async generateSchemas() {
|
|
748
|
-
const project = this.createProject();
|
|
729
|
+
async generateSchemas(project) {
|
|
749
730
|
const sourceFiles = project.getSourceFiles(this.controllerPattern);
|
|
750
731
|
const collectedTypes = this.collectTypesFromControllers(sourceFiles);
|
|
751
732
|
return this.processTypes(collectedTypes);
|
|
752
733
|
}
|
|
753
|
-
/**
|
|
754
|
-
* Creates a new ts-morph project
|
|
755
|
-
*/
|
|
756
|
-
createProject() {
|
|
757
|
-
const project = new import_ts_morph2.Project({
|
|
758
|
-
tsConfigFilePath: this.tsConfigPath
|
|
759
|
-
});
|
|
760
|
-
project.addSourceFilesAtPaths([this.controllerPattern]);
|
|
761
|
-
this.projects.push(project);
|
|
762
|
-
return project;
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* Cleanup resources to prevent memory leaks
|
|
766
|
-
*/
|
|
767
|
-
dispose() {
|
|
768
|
-
this.projects.forEach((project) => {
|
|
769
|
-
project.getSourceFiles().forEach((file) => project.removeSourceFile(file));
|
|
770
|
-
});
|
|
771
|
-
this.projects = [];
|
|
772
|
-
}
|
|
773
734
|
/**
|
|
774
735
|
* Collects types from controller files
|
|
775
736
|
*/
|
|
@@ -847,20 +808,27 @@ var RPCPlugin = class {
|
|
|
847
808
|
tsConfigPath;
|
|
848
809
|
outputDir;
|
|
849
810
|
generateOnInit;
|
|
811
|
+
contextNamespace;
|
|
812
|
+
contextArtifactKey;
|
|
850
813
|
// Services
|
|
851
814
|
routeAnalyzer;
|
|
852
815
|
schemaGenerator;
|
|
853
816
|
clientGenerator;
|
|
817
|
+
// Shared ts-morph project
|
|
818
|
+
project = null;
|
|
854
819
|
// Internal state
|
|
855
820
|
analyzedRoutes = [];
|
|
856
821
|
analyzedSchemas = [];
|
|
857
822
|
generatedInfo = null;
|
|
823
|
+
app = null;
|
|
858
824
|
constructor(options = {}) {
|
|
859
825
|
this.controllerPattern = options.controllerPattern ?? DEFAULT_OPTIONS.controllerPattern;
|
|
860
|
-
this.tsConfigPath = options.tsConfigPath ??
|
|
861
|
-
this.outputDir = options.outputDir ??
|
|
826
|
+
this.tsConfigPath = options.tsConfigPath ?? import_path3.default.resolve(process.cwd(), DEFAULT_OPTIONS.tsConfigPath);
|
|
827
|
+
this.outputDir = options.outputDir ?? import_path3.default.resolve(process.cwd(), DEFAULT_OPTIONS.outputDir);
|
|
862
828
|
this.generateOnInit = options.generateOnInit ?? DEFAULT_OPTIONS.generateOnInit;
|
|
863
|
-
this.
|
|
829
|
+
this.contextNamespace = options.context?.namespace ?? DEFAULT_OPTIONS.context.namespace;
|
|
830
|
+
this.contextArtifactKey = options.context?.keys?.artifact ?? DEFAULT_OPTIONS.context.keys.artifact;
|
|
831
|
+
this.routeAnalyzer = new RouteAnalyzerService();
|
|
864
832
|
this.schemaGenerator = new SchemaGeneratorService(this.controllerPattern, this.tsConfigPath);
|
|
865
833
|
this.clientGenerator = new ClientGeneratorService(this.outputDir);
|
|
866
834
|
this.validateConfiguration();
|
|
@@ -876,13 +844,19 @@ var RPCPlugin = class {
|
|
|
876
844
|
if (!this.tsConfigPath?.trim()) {
|
|
877
845
|
errors.push("TypeScript config path cannot be empty");
|
|
878
846
|
} else {
|
|
879
|
-
if (!
|
|
847
|
+
if (!import_fs2.default.existsSync(this.tsConfigPath)) {
|
|
880
848
|
errors.push(`TypeScript config file not found at: ${this.tsConfigPath}`);
|
|
881
849
|
}
|
|
882
850
|
}
|
|
883
851
|
if (!this.outputDir?.trim()) {
|
|
884
852
|
errors.push("Output directory cannot be empty");
|
|
885
853
|
}
|
|
854
|
+
if (!this.contextNamespace?.trim()) {
|
|
855
|
+
errors.push("Context namespace cannot be empty");
|
|
856
|
+
}
|
|
857
|
+
if (!this.contextArtifactKey?.trim()) {
|
|
858
|
+
errors.push("Context artifact key cannot be empty");
|
|
859
|
+
}
|
|
886
860
|
if (errors.length > 0) {
|
|
887
861
|
throw new Error(`Configuration validation failed: ${errors.join(", ")}`);
|
|
888
862
|
}
|
|
@@ -894,22 +868,42 @@ var RPCPlugin = class {
|
|
|
894
868
|
* Called after all modules are registered
|
|
895
869
|
*/
|
|
896
870
|
afterModulesRegistered = async (app, hono) => {
|
|
871
|
+
this.app = app;
|
|
897
872
|
if (this.generateOnInit) {
|
|
898
873
|
await this.analyzeEverything();
|
|
874
|
+
this.publishArtifact(app);
|
|
899
875
|
}
|
|
900
876
|
};
|
|
901
877
|
/**
|
|
902
878
|
* Main analysis method that coordinates all three components
|
|
903
879
|
*/
|
|
904
|
-
async analyzeEverything() {
|
|
880
|
+
async analyzeEverything(force = false) {
|
|
905
881
|
try {
|
|
906
882
|
this.log("Starting comprehensive RPC analysis...");
|
|
883
|
+
this.dispose();
|
|
884
|
+
this.project = new import_ts_morph.Project({ tsConfigFilePath: this.tsConfigPath });
|
|
885
|
+
this.project.addSourceFilesAtPaths([this.controllerPattern]);
|
|
886
|
+
const filePaths = this.project.getSourceFiles().map((f) => f.getFilePath());
|
|
887
|
+
if (!force) {
|
|
888
|
+
const currentHash = computeHash(filePaths);
|
|
889
|
+
const stored = readChecksum(this.outputDir);
|
|
890
|
+
if (stored && stored.hash === currentHash && this.outputFilesExist()) {
|
|
891
|
+
if (this.loadArtifactFromDisk()) {
|
|
892
|
+
this.log("Source files unchanged \u2014 skipping regeneration");
|
|
893
|
+
this.dispose();
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
this.log("Source files unchanged but cached artifact missing/invalid \u2014 regenerating");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
907
899
|
this.analyzedRoutes = [];
|
|
908
900
|
this.analyzedSchemas = [];
|
|
909
901
|
this.generatedInfo = null;
|
|
910
|
-
this.analyzedRoutes = await this.routeAnalyzer.analyzeControllerMethods();
|
|
911
|
-
this.analyzedSchemas = await this.schemaGenerator.generateSchemas();
|
|
902
|
+
this.analyzedRoutes = await this.routeAnalyzer.analyzeControllerMethods(this.project);
|
|
903
|
+
this.analyzedSchemas = await this.schemaGenerator.generateSchemas(this.project);
|
|
912
904
|
this.generatedInfo = await this.clientGenerator.generateClient(this.analyzedRoutes, this.analyzedSchemas);
|
|
905
|
+
await writeChecksum(this.outputDir, { hash: computeHash(filePaths), files: filePaths });
|
|
906
|
+
this.writeArtifactToDisk();
|
|
913
907
|
this.log(
|
|
914
908
|
`\u2705 RPC analysis complete: ${this.analyzedRoutes.length} routes, ${this.analyzedSchemas.length} schemas`
|
|
915
909
|
);
|
|
@@ -920,10 +914,14 @@ var RPCPlugin = class {
|
|
|
920
914
|
}
|
|
921
915
|
}
|
|
922
916
|
/**
|
|
923
|
-
* Manually trigger analysis (useful for testing or re-generation)
|
|
917
|
+
* Manually trigger analysis (useful for testing or re-generation).
|
|
918
|
+
* Defaults to force=true to bypass cache; pass false to use caching.
|
|
924
919
|
*/
|
|
925
|
-
async analyze() {
|
|
926
|
-
await this.analyzeEverything();
|
|
920
|
+
async analyze(force = true) {
|
|
921
|
+
await this.analyzeEverything(force);
|
|
922
|
+
if (this.app) {
|
|
923
|
+
this.publishArtifact(this.app);
|
|
924
|
+
}
|
|
927
925
|
}
|
|
928
926
|
/**
|
|
929
927
|
* Get the analyzed routes
|
|
@@ -943,13 +941,55 @@ var RPCPlugin = class {
|
|
|
943
941
|
getGenerationInfo() {
|
|
944
942
|
return this.generatedInfo;
|
|
945
943
|
}
|
|
944
|
+
/**
|
|
945
|
+
* Checks whether expected output files exist on disk
|
|
946
|
+
*/
|
|
947
|
+
outputFilesExist() {
|
|
948
|
+
return import_fs2.default.existsSync(import_path3.default.join(this.outputDir, "client.ts")) && import_fs2.default.existsSync(import_path3.default.join(this.outputDir, "rpc-artifact.json"));
|
|
949
|
+
}
|
|
950
|
+
getArtifactPath() {
|
|
951
|
+
return import_path3.default.join(this.outputDir, "rpc-artifact.json");
|
|
952
|
+
}
|
|
953
|
+
writeArtifactToDisk() {
|
|
954
|
+
const artifact = {
|
|
955
|
+
routes: this.analyzedRoutes,
|
|
956
|
+
schemas: this.analyzedSchemas
|
|
957
|
+
};
|
|
958
|
+
import_fs2.default.mkdirSync(this.outputDir, { recursive: true });
|
|
959
|
+
import_fs2.default.writeFileSync(this.getArtifactPath(), JSON.stringify(artifact));
|
|
960
|
+
}
|
|
961
|
+
loadArtifactFromDisk() {
|
|
962
|
+
try {
|
|
963
|
+
const raw = import_fs2.default.readFileSync(this.getArtifactPath(), "utf8");
|
|
964
|
+
const parsed = JSON.parse(raw);
|
|
965
|
+
if (!Array.isArray(parsed.routes) || !Array.isArray(parsed.schemas)) {
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
this.analyzedRoutes = parsed.routes;
|
|
969
|
+
this.analyzedSchemas = parsed.schemas;
|
|
970
|
+
this.generatedInfo = null;
|
|
971
|
+
return true;
|
|
972
|
+
} catch {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
publishArtifact(app) {
|
|
977
|
+
app.getContext().set(this.getArtifactContextKey(), {
|
|
978
|
+
routes: this.analyzedRoutes,
|
|
979
|
+
schemas: this.analyzedSchemas
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
getArtifactContextKey() {
|
|
983
|
+
return `${this.contextNamespace}.${this.contextArtifactKey}`;
|
|
984
|
+
}
|
|
946
985
|
/**
|
|
947
986
|
* Cleanup resources to prevent memory leaks
|
|
948
987
|
*/
|
|
949
988
|
dispose() {
|
|
950
|
-
this.
|
|
951
|
-
|
|
952
|
-
|
|
989
|
+
if (this.project) {
|
|
990
|
+
this.project.getSourceFiles().forEach((file) => this.project.removeSourceFile(file));
|
|
991
|
+
this.project = null;
|
|
992
|
+
}
|
|
953
993
|
}
|
|
954
994
|
// ============================================================================
|
|
955
995
|
// LOGGING UTILITIES
|
|
@@ -981,10 +1021,12 @@ var RPCPlugin = class {
|
|
|
981
1021
|
buildFullApiPath,
|
|
982
1022
|
buildFullPath,
|
|
983
1023
|
camelCase,
|
|
1024
|
+
computeHash,
|
|
984
1025
|
extractNamedType,
|
|
985
|
-
generateTypeImports,
|
|
986
1026
|
generateTypeScriptInterface,
|
|
987
1027
|
mapJsonSchemaTypeToTypeScript,
|
|
988
|
-
|
|
1028
|
+
readChecksum,
|
|
1029
|
+
safeToString,
|
|
1030
|
+
writeChecksum
|
|
989
1031
|
});
|
|
990
1032
|
//# sourceMappingURL=index.js.map
|