@honestjs/rpc-plugin 1.0.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/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/index.d.mts +337 -0
- package/dist/index.d.ts +337 -0
- package/dist/index.js +981 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +928 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
BUILTIN_TYPES: () => BUILTIN_TYPES,
|
|
34
|
+
BUILTIN_UTILITY_TYPES: () => BUILTIN_UTILITY_TYPES,
|
|
35
|
+
ClientGeneratorService: () => ClientGeneratorService,
|
|
36
|
+
DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
|
|
37
|
+
GENERIC_TYPES: () => GENERIC_TYPES,
|
|
38
|
+
LOG_PREFIX: () => LOG_PREFIX,
|
|
39
|
+
RPCPlugin: () => RPCPlugin,
|
|
40
|
+
RouteAnalyzerService: () => RouteAnalyzerService,
|
|
41
|
+
SchemaGeneratorService: () => SchemaGeneratorService,
|
|
42
|
+
buildFullApiPath: () => buildFullApiPath,
|
|
43
|
+
buildFullPath: () => buildFullPath,
|
|
44
|
+
camelCase: () => camelCase,
|
|
45
|
+
extractNamedType: () => extractNamedType,
|
|
46
|
+
generateTypeImports: () => generateTypeImports,
|
|
47
|
+
generateTypeScriptInterface: () => generateTypeScriptInterface,
|
|
48
|
+
mapJsonSchemaTypeToTypeScript: () => mapJsonSchemaTypeToTypeScript,
|
|
49
|
+
safeToString: () => safeToString
|
|
50
|
+
});
|
|
51
|
+
module.exports = __toCommonJS(index_exports);
|
|
52
|
+
|
|
53
|
+
// src/rpc.plugin.ts
|
|
54
|
+
var import_fs = __toESM(require("fs"));
|
|
55
|
+
var import_path2 = __toESM(require("path"));
|
|
56
|
+
|
|
57
|
+
// src/constants/defaults.ts
|
|
58
|
+
var DEFAULT_OPTIONS = {
|
|
59
|
+
controllerPattern: "src/modules/*/*.controller.ts",
|
|
60
|
+
tsConfigPath: "tsconfig.json",
|
|
61
|
+
outputDir: "./generated/rpc",
|
|
62
|
+
generateOnInit: true
|
|
63
|
+
};
|
|
64
|
+
var LOG_PREFIX = "[ RPCPlugin ]";
|
|
65
|
+
var BUILTIN_UTILITY_TYPES = /* @__PURE__ */ new Set([
|
|
66
|
+
"Partial",
|
|
67
|
+
"Required",
|
|
68
|
+
"Readonly",
|
|
69
|
+
"Pick",
|
|
70
|
+
"Omit",
|
|
71
|
+
"Record",
|
|
72
|
+
"Exclude",
|
|
73
|
+
"Extract",
|
|
74
|
+
"ReturnType",
|
|
75
|
+
"InstanceType"
|
|
76
|
+
]);
|
|
77
|
+
var BUILTIN_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "any", "void", "unknown"]);
|
|
78
|
+
var GENERIC_TYPES = /* @__PURE__ */ new Set(["Array", "Promise", "Partial"]);
|
|
79
|
+
|
|
80
|
+
// src/services/client-generator.service.ts
|
|
81
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
82
|
+
var import_path = __toESM(require("path"));
|
|
83
|
+
|
|
84
|
+
// src/utils/path-utils.ts
|
|
85
|
+
function buildFullPath(basePath, parameters) {
|
|
86
|
+
if (!basePath || typeof basePath !== "string") return "/";
|
|
87
|
+
let path3 = basePath;
|
|
88
|
+
if (parameters && Array.isArray(parameters)) {
|
|
89
|
+
for (const param of parameters) {
|
|
90
|
+
if (param.data && typeof param.data === "string" && param.data.startsWith(":")) {
|
|
91
|
+
const paramName = param.data.slice(1);
|
|
92
|
+
path3 = path3.replace(`:${paramName}`, `\${${paramName}}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return path3;
|
|
97
|
+
}
|
|
98
|
+
function buildFullApiPath(route) {
|
|
99
|
+
const prefix = route.prefix || "";
|
|
100
|
+
const version = route.version || "";
|
|
101
|
+
const routePath = route.route || "";
|
|
102
|
+
const path3 = route.path || "";
|
|
103
|
+
let fullPath = "";
|
|
104
|
+
if (prefix && prefix !== "/") {
|
|
105
|
+
fullPath += prefix.replace(/^\/+|\/+$/g, "");
|
|
106
|
+
}
|
|
107
|
+
if (version && version !== "/") {
|
|
108
|
+
fullPath += `/${version.replace(/^\/+|\/+$/g, "")}`;
|
|
109
|
+
}
|
|
110
|
+
if (routePath && routePath !== "/") {
|
|
111
|
+
fullPath += `/${routePath.replace(/^\/+|\/+$/g, "")}`;
|
|
112
|
+
}
|
|
113
|
+
if (path3 && path3 !== "/") {
|
|
114
|
+
fullPath += `/${path3.replace(/^\/+|\/+$/g, "")}`;
|
|
115
|
+
} else if (path3 === "/") {
|
|
116
|
+
fullPath += "/";
|
|
117
|
+
}
|
|
118
|
+
return fullPath || "/";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/utils/string-utils.ts
|
|
122
|
+
function safeToString(value) {
|
|
123
|
+
if (typeof value === "string") return value;
|
|
124
|
+
if (typeof value === "symbol") return value.description || "Symbol";
|
|
125
|
+
return String(value);
|
|
126
|
+
}
|
|
127
|
+
function camelCase(str) {
|
|
128
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/services/client-generator.service.ts
|
|
132
|
+
var ClientGeneratorService = class {
|
|
133
|
+
constructor(outputDir) {
|
|
134
|
+
this.outputDir = outputDir;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Generates the TypeScript RPC client
|
|
138
|
+
*/
|
|
139
|
+
async generateClient(routes, schemas) {
|
|
140
|
+
await import_promises.default.mkdir(this.outputDir, { recursive: true });
|
|
141
|
+
await this.generateClientFile(routes, schemas);
|
|
142
|
+
const generatedInfo = {
|
|
143
|
+
clientFile: import_path.default.join(this.outputDir, "client.ts"),
|
|
144
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
145
|
+
};
|
|
146
|
+
return generatedInfo;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Generates the main client file with types included
|
|
150
|
+
*/
|
|
151
|
+
async generateClientFile(routes, schemas) {
|
|
152
|
+
const clientContent = this.generateClientContent(routes, schemas);
|
|
153
|
+
const clientPath = import_path.default.join(this.outputDir, "client.ts");
|
|
154
|
+
await import_promises.default.writeFile(clientPath, clientContent, "utf-8");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Generates the client TypeScript content with types included
|
|
158
|
+
*/
|
|
159
|
+
generateClientContent(routes, schemas) {
|
|
160
|
+
const controllerGroups = this.groupRoutesByController(routes);
|
|
161
|
+
return `// ============================================================================
|
|
162
|
+
// TYPES SECTION
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* API Response wrapper
|
|
167
|
+
*/
|
|
168
|
+
export interface ApiResponse<T = any> {
|
|
169
|
+
data: T
|
|
170
|
+
message?: string
|
|
171
|
+
success: boolean
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* API Error class
|
|
176
|
+
*/
|
|
177
|
+
export class ApiError extends Error {
|
|
178
|
+
constructor(
|
|
179
|
+
public statusCode: number,
|
|
180
|
+
message: string
|
|
181
|
+
) {
|
|
182
|
+
super(message)
|
|
183
|
+
this.name = 'ApiError'
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Clean separation of concerns for request options
|
|
189
|
+
*/
|
|
190
|
+
export type RequestOptions<
|
|
191
|
+
TParams = undefined,
|
|
192
|
+
TQuery = undefined,
|
|
193
|
+
TBody = undefined,
|
|
194
|
+
THeaders = undefined
|
|
195
|
+
> = (TParams extends undefined ? object : { params: TParams }) &
|
|
196
|
+
(TQuery extends undefined ? object : { query: TQuery }) &
|
|
197
|
+
(TBody extends undefined ? object : { body: TBody }) &
|
|
198
|
+
(THeaders extends undefined ? object : { headers: THeaders })
|
|
199
|
+
|
|
200
|
+
// Generated DTOs and types from integrated Schema Generation
|
|
201
|
+
${this.generateSchemaTypes(schemas)}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// CLIENT SECTION
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Generated RPC Client
|
|
209
|
+
*
|
|
210
|
+
* This class provides a type-safe HTTP client for interacting with your API endpoints.
|
|
211
|
+
* It's automatically generated by the RPCPlugin based on your controller definitions.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* \`\`\`typescript
|
|
215
|
+
* const apiClient = new ApiClient('http://localhost:3000')
|
|
216
|
+
*
|
|
217
|
+
* // Make a request to get users
|
|
218
|
+
* const response = await apiClient.users.getUsers()
|
|
219
|
+
*
|
|
220
|
+
* // Make a request with parameters
|
|
221
|
+
* const user = await apiClient.users.getUser({ params: { id: '123' } })
|
|
222
|
+
*
|
|
223
|
+
* // Make a request with body data
|
|
224
|
+
* const newUser = await apiClient.users.createUser({
|
|
225
|
+
* body: { name: 'John', email: 'john@example.com' }
|
|
226
|
+
* })
|
|
227
|
+
* \`\`\`
|
|
228
|
+
*
|
|
229
|
+
* @generated This class is auto-generated by RPCPlugin
|
|
230
|
+
*/
|
|
231
|
+
export class ApiClient {
|
|
232
|
+
private baseUrl: string
|
|
233
|
+
private defaultHeaders: Record<string, string>
|
|
234
|
+
|
|
235
|
+
constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) {
|
|
236
|
+
this.baseUrl = baseUrl.replace(/\\/$/, '')
|
|
237
|
+
this.defaultHeaders = {
|
|
238
|
+
'Content-Type': 'application/json',
|
|
239
|
+
...defaultHeaders
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Set default headers for all requests
|
|
245
|
+
*/
|
|
246
|
+
setDefaultHeaders(headers: Record<string, string>): this {
|
|
247
|
+
this.defaultHeaders = { ...this.defaultHeaders, ...headers }
|
|
248
|
+
return this
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Set default authorization header
|
|
253
|
+
*/
|
|
254
|
+
setDefaultAuth(token: string): this {
|
|
255
|
+
this.defaultHeaders.Authorization = \`Bearer \${token}\`
|
|
256
|
+
return this
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Make an HTTP request with flexible options
|
|
261
|
+
*/
|
|
262
|
+
private async request<T>(
|
|
263
|
+
method: string,
|
|
264
|
+
path: string,
|
|
265
|
+
options: RequestOptions<any, any, any, any> = {}
|
|
266
|
+
): Promise<ApiResponse<T>> {
|
|
267
|
+
const { params, query, body, headers = {} } = options as any
|
|
268
|
+
|
|
269
|
+
// Build the final URL with path parameters
|
|
270
|
+
let finalPath = path
|
|
271
|
+
if (params) {
|
|
272
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
273
|
+
finalPath = finalPath.replace(\`:\${key}\`, String(value))
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const url = new URL(finalPath, this.baseUrl)
|
|
278
|
+
|
|
279
|
+
// Add query parameters
|
|
280
|
+
if (query) {
|
|
281
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
282
|
+
if (value !== undefined && value !== null) {
|
|
283
|
+
url.searchParams.append(key, String(value))
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Merge default headers with request-specific headers
|
|
289
|
+
const finalHeaders = { ...this.defaultHeaders, ...headers }
|
|
290
|
+
|
|
291
|
+
const requestOptions: RequestInit = {
|
|
292
|
+
method,
|
|
293
|
+
headers: finalHeaders,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (body && method !== 'GET') {
|
|
297
|
+
requestOptions.body = JSON.stringify(body)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const response = await fetch(url.toString(), requestOptions)
|
|
302
|
+
const responseData = await response.json()
|
|
303
|
+
|
|
304
|
+
if (!response.ok) {
|
|
305
|
+
throw new ApiError(response.status, responseData.message || 'Request failed')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return responseData
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (error instanceof ApiError) {
|
|
311
|
+
throw error
|
|
312
|
+
}
|
|
313
|
+
throw new ApiError(0, error instanceof Error ? error.message : 'Network error')
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
${this.generateControllerMethods(controllerGroups)}
|
|
318
|
+
}
|
|
319
|
+
`;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Generates controller methods for the client
|
|
323
|
+
*/
|
|
324
|
+
generateControllerMethods(controllerGroups) {
|
|
325
|
+
let methods = "";
|
|
326
|
+
for (const [controllerName, routes] of controllerGroups) {
|
|
327
|
+
const className = controllerName.replace(/Controller$/, "");
|
|
328
|
+
methods += `
|
|
329
|
+
// ${className} Controller
|
|
330
|
+
`;
|
|
331
|
+
methods += ` get ${camelCase(className)}() {
|
|
332
|
+
`;
|
|
333
|
+
methods += ` return {
|
|
334
|
+
`;
|
|
335
|
+
for (const route of routes) {
|
|
336
|
+
const methodName = camelCase(safeToString(route.handler));
|
|
337
|
+
const httpMethod = safeToString(route.method).toLowerCase();
|
|
338
|
+
const { pathParams, queryParams, bodyParams } = this.analyzeRouteParameters(route);
|
|
339
|
+
const hasRequiredParams = pathParams.length > 0 || queryParams.some((p) => p.required) || bodyParams.length > 0 && httpMethod !== "get";
|
|
340
|
+
methods += ` ${methodName}: async (options${hasRequiredParams ? "" : "?"}: RequestOptions<`;
|
|
341
|
+
if (pathParams.length > 0) {
|
|
342
|
+
const pathParamTypes = pathParams.map((p) => {
|
|
343
|
+
const paramName = p.name;
|
|
344
|
+
const paramType = p.type || "any";
|
|
345
|
+
return `${paramName}: ${paramType}`;
|
|
346
|
+
});
|
|
347
|
+
methods += `{ ${pathParamTypes.join(", ")} }`;
|
|
348
|
+
} else {
|
|
349
|
+
methods += "undefined";
|
|
350
|
+
}
|
|
351
|
+
methods += ", ";
|
|
352
|
+
if (queryParams.length > 0) {
|
|
353
|
+
const queryParamTypes = queryParams.map((p) => {
|
|
354
|
+
const paramName = p.name;
|
|
355
|
+
const paramType = p.type || "any";
|
|
356
|
+
return `${paramName}: ${paramType}`;
|
|
357
|
+
});
|
|
358
|
+
methods += `{ ${queryParamTypes.join(", ")} }`;
|
|
359
|
+
} else {
|
|
360
|
+
methods += "undefined";
|
|
361
|
+
}
|
|
362
|
+
methods += ", ";
|
|
363
|
+
if (bodyParams.length > 0) {
|
|
364
|
+
const bodyParamTypes = bodyParams.map((p) => {
|
|
365
|
+
const paramType = p.type || "any";
|
|
366
|
+
return paramType;
|
|
367
|
+
});
|
|
368
|
+
methods += bodyParamTypes[0] || "any";
|
|
369
|
+
} else {
|
|
370
|
+
methods += "undefined";
|
|
371
|
+
}
|
|
372
|
+
methods += ", ";
|
|
373
|
+
methods += "undefined";
|
|
374
|
+
const returnType = this.extractReturnType(route.returns);
|
|
375
|
+
methods += `>): Promise<ApiResponse<${returnType}>> => {
|
|
376
|
+
`;
|
|
377
|
+
let requestPath = buildFullApiPath(route);
|
|
378
|
+
if (pathParams.length > 0) {
|
|
379
|
+
for (const pathParam of pathParams) {
|
|
380
|
+
const paramName = pathParam.name;
|
|
381
|
+
const placeholder = `:${String(pathParam.data)}`;
|
|
382
|
+
requestPath = requestPath.replace(placeholder, `:${paramName}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
methods += ` return this.request<${returnType}>('${httpMethod.toUpperCase()}', \`${requestPath}\`, options)
|
|
386
|
+
`;
|
|
387
|
+
methods += ` },
|
|
388
|
+
`;
|
|
389
|
+
}
|
|
390
|
+
methods += ` }
|
|
391
|
+
`;
|
|
392
|
+
methods += ` }
|
|
393
|
+
`;
|
|
394
|
+
}
|
|
395
|
+
return methods;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Extracts the proper return type from route analysis
|
|
399
|
+
*/
|
|
400
|
+
extractReturnType(returns) {
|
|
401
|
+
if (!returns) return "any";
|
|
402
|
+
const promiseMatch = returns.match(/Promise<(.+)>/);
|
|
403
|
+
if (promiseMatch) {
|
|
404
|
+
return promiseMatch[1];
|
|
405
|
+
}
|
|
406
|
+
return returns;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Generates schema types from integrated schema generation
|
|
410
|
+
*/
|
|
411
|
+
generateSchemaTypes(schemas) {
|
|
412
|
+
if (schemas.length === 0) {
|
|
413
|
+
return "// No schemas available from integrated Schema Generation\n";
|
|
414
|
+
}
|
|
415
|
+
let content = "// Schema types from integrated Schema Generation\n";
|
|
416
|
+
for (const schemaInfo of schemas) {
|
|
417
|
+
if (schemaInfo.typescriptType) {
|
|
418
|
+
content += `${schemaInfo.typescriptType}
|
|
419
|
+
|
|
420
|
+
`;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return content;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Groups routes by controller for better organization
|
|
427
|
+
*/
|
|
428
|
+
groupRoutesByController(routes) {
|
|
429
|
+
const groups = /* @__PURE__ */ new Map();
|
|
430
|
+
for (const route of routes) {
|
|
431
|
+
const controller = safeToString(route.controller);
|
|
432
|
+
if (!groups.has(controller)) {
|
|
433
|
+
groups.set(controller, []);
|
|
434
|
+
}
|
|
435
|
+
groups.get(controller).push(route);
|
|
436
|
+
}
|
|
437
|
+
return groups;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Analyzes route parameters to determine their types and usage
|
|
441
|
+
*/
|
|
442
|
+
analyzeRouteParameters(route) {
|
|
443
|
+
const parameters = route.parameters || [];
|
|
444
|
+
const method = String(route.method || "").toLowerCase();
|
|
445
|
+
const isInPath = (p) => {
|
|
446
|
+
const pathSegment = p.data;
|
|
447
|
+
return !!pathSegment && typeof pathSegment === "string" && route.path.includes(`:${pathSegment}`);
|
|
448
|
+
};
|
|
449
|
+
const pathParams = parameters.filter((p) => isInPath(p)).map((p) => ({ ...p, required: true }));
|
|
450
|
+
const rawBody = parameters.filter((p) => !isInPath(p) && method !== "get");
|
|
451
|
+
const bodyParams = rawBody.map((p) => ({
|
|
452
|
+
...p,
|
|
453
|
+
required: true
|
|
454
|
+
}));
|
|
455
|
+
const queryParams = parameters.filter((p) => !isInPath(p) && method === "get").map((p) => ({
|
|
456
|
+
...p,
|
|
457
|
+
required: p.required === true
|
|
458
|
+
// default false if not provided
|
|
459
|
+
}));
|
|
460
|
+
return { pathParams, queryParams, bodyParams };
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// src/services/route-analyzer.service.ts
|
|
465
|
+
var import_honestjs = require("honestjs");
|
|
466
|
+
var import_ts_morph = require("ts-morph");
|
|
467
|
+
var RouteAnalyzerService = class {
|
|
468
|
+
constructor(controllerPattern, tsConfigPath) {
|
|
469
|
+
this.controllerPattern = controllerPattern;
|
|
470
|
+
this.tsConfigPath = tsConfigPath;
|
|
471
|
+
}
|
|
472
|
+
// Track projects for cleanup
|
|
473
|
+
projects = [];
|
|
474
|
+
/**
|
|
475
|
+
* Analyzes controller methods to extract type information
|
|
476
|
+
*/
|
|
477
|
+
async analyzeControllerMethods() {
|
|
478
|
+
const routes = import_honestjs.RouteRegistry.getRoutes();
|
|
479
|
+
if (!routes?.length) {
|
|
480
|
+
return [];
|
|
481
|
+
}
|
|
482
|
+
const project = this.createProject();
|
|
483
|
+
const controllers = this.findControllerClasses(project);
|
|
484
|
+
if (controllers.size === 0) {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
return this.processRoutes(routes, controllers);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Creates a new ts-morph project
|
|
491
|
+
*/
|
|
492
|
+
createProject() {
|
|
493
|
+
const project = new import_ts_morph.Project({
|
|
494
|
+
tsConfigFilePath: this.tsConfigPath
|
|
495
|
+
});
|
|
496
|
+
project.addSourceFilesAtPaths([this.controllerPattern]);
|
|
497
|
+
this.projects.push(project);
|
|
498
|
+
return project;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Cleanup resources to prevent memory leaks
|
|
502
|
+
*/
|
|
503
|
+
dispose() {
|
|
504
|
+
this.projects.forEach((project) => {
|
|
505
|
+
project.getSourceFiles().forEach((file) => project.removeSourceFile(file));
|
|
506
|
+
});
|
|
507
|
+
this.projects = [];
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Finds controller classes in the project
|
|
511
|
+
*/
|
|
512
|
+
findControllerClasses(project) {
|
|
513
|
+
const controllers = /* @__PURE__ */ new Map();
|
|
514
|
+
const files = project.getSourceFiles();
|
|
515
|
+
for (const sourceFile of files) {
|
|
516
|
+
const classes = sourceFile.getClasses();
|
|
517
|
+
for (const classDeclaration of classes) {
|
|
518
|
+
const className = classDeclaration.getName();
|
|
519
|
+
if (className?.endsWith("Controller")) {
|
|
520
|
+
controllers.set(className, classDeclaration);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return controllers;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Processes all routes and extracts type information
|
|
528
|
+
*/
|
|
529
|
+
processRoutes(routes, controllers) {
|
|
530
|
+
const analyzedRoutes = [];
|
|
531
|
+
const errors = [];
|
|
532
|
+
for (const route of routes) {
|
|
533
|
+
try {
|
|
534
|
+
const extendedRoute = this.createExtendedRoute(route, controllers);
|
|
535
|
+
analyzedRoutes.push(extendedRoute);
|
|
536
|
+
} catch (routeError) {
|
|
537
|
+
const error = routeError instanceof Error ? routeError : new Error(String(routeError));
|
|
538
|
+
errors.push(error);
|
|
539
|
+
console.error(
|
|
540
|
+
`Error processing route ${safeToString(route.controller)}.${safeToString(route.handler)}:`,
|
|
541
|
+
routeError
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (errors.length > 0) {
|
|
546
|
+
throw new Error(`Failed to process ${errors.length} routes: ${errors.map((e) => e.message).join(", ")}`);
|
|
547
|
+
}
|
|
548
|
+
return analyzedRoutes;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Creates an extended route with type information
|
|
552
|
+
*/
|
|
553
|
+
createExtendedRoute(route, controllers) {
|
|
554
|
+
const controllerName = safeToString(route.controller);
|
|
555
|
+
const handlerName = safeToString(route.handler);
|
|
556
|
+
const controllerClass = controllers.get(controllerName);
|
|
557
|
+
let returns;
|
|
558
|
+
let parameters;
|
|
559
|
+
if (controllerClass) {
|
|
560
|
+
const handlerMethod = controllerClass.getMethods().find((method) => method.getName() === handlerName);
|
|
561
|
+
if (handlerMethod) {
|
|
562
|
+
returns = this.getReturnType(handlerMethod);
|
|
563
|
+
parameters = this.getParametersWithTypes(handlerMethod, route.parameters || []);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
controller: controllerName,
|
|
568
|
+
handler: handlerName,
|
|
569
|
+
method: safeToString(route.method).toUpperCase(),
|
|
570
|
+
prefix: route.prefix,
|
|
571
|
+
version: route.version,
|
|
572
|
+
route: route.route,
|
|
573
|
+
path: route.path,
|
|
574
|
+
fullPath: buildFullPath(route.path, route.parameters),
|
|
575
|
+
parameters,
|
|
576
|
+
returns
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Gets the return type of a method
|
|
581
|
+
*/
|
|
582
|
+
getReturnType(method) {
|
|
583
|
+
const type = method.getReturnType();
|
|
584
|
+
const typeText = type.getText(method);
|
|
585
|
+
const aliasSymbol = type.getAliasSymbol();
|
|
586
|
+
if (aliasSymbol) {
|
|
587
|
+
return aliasSymbol.getName();
|
|
588
|
+
}
|
|
589
|
+
return typeText.replace(/import\(".*?"\)\./g, "");
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Gets parameters with their types
|
|
593
|
+
*/
|
|
594
|
+
getParametersWithTypes(method, parameters) {
|
|
595
|
+
const result = [];
|
|
596
|
+
const declaredParams = method.getParameters();
|
|
597
|
+
const sortedParams = [...parameters].sort((a, b) => a.index - b.index);
|
|
598
|
+
for (const param of sortedParams) {
|
|
599
|
+
const index = param.index;
|
|
600
|
+
if (index < declaredParams.length) {
|
|
601
|
+
const declaredParam = declaredParams[index];
|
|
602
|
+
const paramName = declaredParam.getName();
|
|
603
|
+
const paramType = declaredParam.getType().getText().replace(/import\(".*?"\)\./g, "");
|
|
604
|
+
result.push({
|
|
605
|
+
index,
|
|
606
|
+
name: paramName,
|
|
607
|
+
type: paramType,
|
|
608
|
+
required: true,
|
|
609
|
+
data: param.data,
|
|
610
|
+
factory: param.factory,
|
|
611
|
+
metatype: param.metatype
|
|
612
|
+
});
|
|
613
|
+
} else {
|
|
614
|
+
result.push({
|
|
615
|
+
index,
|
|
616
|
+
name: `param${index}`,
|
|
617
|
+
type: param.metatype?.name || "unknown",
|
|
618
|
+
required: true,
|
|
619
|
+
data: param.data,
|
|
620
|
+
factory: param.factory,
|
|
621
|
+
metatype: param.metatype
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/services/schema-generator.service.ts
|
|
630
|
+
var import_ts_json_schema_generator = require("ts-json-schema-generator");
|
|
631
|
+
var import_ts_morph2 = require("ts-morph");
|
|
632
|
+
|
|
633
|
+
// src/utils/schema-utils.ts
|
|
634
|
+
function mapJsonSchemaTypeToTypeScript(schema) {
|
|
635
|
+
const type = schema.type;
|
|
636
|
+
switch (type) {
|
|
637
|
+
case "string":
|
|
638
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
639
|
+
return `'${schema.enum.join("' | '")}'`;
|
|
640
|
+
}
|
|
641
|
+
return "string";
|
|
642
|
+
case "number":
|
|
643
|
+
case "integer":
|
|
644
|
+
return "number";
|
|
645
|
+
case "boolean":
|
|
646
|
+
return "boolean";
|
|
647
|
+
case "array": {
|
|
648
|
+
const itemType = mapJsonSchemaTypeToTypeScript(schema.items || {});
|
|
649
|
+
return `${itemType}[]`;
|
|
650
|
+
}
|
|
651
|
+
case "object":
|
|
652
|
+
return "Record<string, any>";
|
|
653
|
+
default:
|
|
654
|
+
return "any";
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function generateTypeScriptInterface(typeName, schema) {
|
|
658
|
+
try {
|
|
659
|
+
const typeDefinition = schema.definitions?.[typeName];
|
|
660
|
+
if (!typeDefinition) {
|
|
661
|
+
return `export interface ${typeName} {
|
|
662
|
+
// No schema definition found
|
|
663
|
+
}`;
|
|
664
|
+
}
|
|
665
|
+
const properties = typeDefinition.properties || {};
|
|
666
|
+
const required = typeDefinition.required || [];
|
|
667
|
+
let interfaceCode = `export interface ${typeName} {
|
|
668
|
+
`;
|
|
669
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
670
|
+
const isRequired = required.includes(propName);
|
|
671
|
+
const type = mapJsonSchemaTypeToTypeScript(propSchema);
|
|
672
|
+
const optional = isRequired ? "" : "?";
|
|
673
|
+
interfaceCode += ` ${propName}${optional}: ${type}
|
|
674
|
+
`;
|
|
675
|
+
}
|
|
676
|
+
interfaceCode += "}";
|
|
677
|
+
return interfaceCode;
|
|
678
|
+
} catch (error) {
|
|
679
|
+
console.error(`Failed to generate TypeScript interface for ${typeName}:`, error);
|
|
680
|
+
return `export interface ${typeName} {
|
|
681
|
+
// Failed to generate interface
|
|
682
|
+
}`;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/utils/type-utils.ts
|
|
687
|
+
function extractNamedType(type) {
|
|
688
|
+
const symbol = type.getAliasSymbol() || type.getSymbol();
|
|
689
|
+
if (!symbol) return null;
|
|
690
|
+
const name = symbol.getName();
|
|
691
|
+
if (GENERIC_TYPES.has(name)) {
|
|
692
|
+
const inner = type.getAliasTypeArguments()?.[0] || type.getTypeArguments()?.[0];
|
|
693
|
+
return inner ? extractNamedType(inner) : null;
|
|
694
|
+
}
|
|
695
|
+
if (BUILTIN_TYPES.has(name)) return null;
|
|
696
|
+
return name;
|
|
697
|
+
}
|
|
698
|
+
function generateTypeImports(routes) {
|
|
699
|
+
const types = /* @__PURE__ */ new Set();
|
|
700
|
+
for (const route of routes) {
|
|
701
|
+
if (route.parameters) {
|
|
702
|
+
for (const param of route.parameters) {
|
|
703
|
+
if (param.type && !["string", "number", "boolean"].includes(param.type)) {
|
|
704
|
+
const typeMatch = param.type.match(/(\w+)(?:<.*>)?/);
|
|
705
|
+
if (typeMatch) {
|
|
706
|
+
const typeName = typeMatch[1];
|
|
707
|
+
if (!BUILTIN_UTILITY_TYPES.has(typeName)) {
|
|
708
|
+
types.add(typeName);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (route.returns) {
|
|
715
|
+
const returnType = route.returns.replace(/Promise<(.+)>/, "$1");
|
|
716
|
+
const baseType = returnType.replace(/\[\]$/, "");
|
|
717
|
+
if (!["string", "number", "boolean", "any", "void", "unknown"].includes(baseType)) {
|
|
718
|
+
types.add(baseType);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return Array.from(types).join(", ");
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// src/services/schema-generator.service.ts
|
|
726
|
+
var SchemaGeneratorService = class {
|
|
727
|
+
constructor(controllerPattern, tsConfigPath) {
|
|
728
|
+
this.controllerPattern = controllerPattern;
|
|
729
|
+
this.tsConfigPath = tsConfigPath;
|
|
730
|
+
}
|
|
731
|
+
// Track projects for cleanup
|
|
732
|
+
projects = [];
|
|
733
|
+
/**
|
|
734
|
+
* Generates JSON schemas from types used in controllers
|
|
735
|
+
*/
|
|
736
|
+
async generateSchemas() {
|
|
737
|
+
const project = this.createProject();
|
|
738
|
+
const sourceFiles = project.getSourceFiles(this.controllerPattern);
|
|
739
|
+
const collectedTypes = this.collectTypesFromControllers(sourceFiles);
|
|
740
|
+
return this.processTypes(collectedTypes);
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Creates a new ts-morph project
|
|
744
|
+
*/
|
|
745
|
+
createProject() {
|
|
746
|
+
const project = new import_ts_morph2.Project({
|
|
747
|
+
tsConfigFilePath: this.tsConfigPath
|
|
748
|
+
});
|
|
749
|
+
this.projects.push(project);
|
|
750
|
+
return project;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Cleanup resources to prevent memory leaks
|
|
754
|
+
*/
|
|
755
|
+
dispose() {
|
|
756
|
+
this.projects.forEach((project) => {
|
|
757
|
+
project.getSourceFiles().forEach((file) => project.removeSourceFile(file));
|
|
758
|
+
});
|
|
759
|
+
this.projects = [];
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Collects types from controller files
|
|
763
|
+
*/
|
|
764
|
+
collectTypesFromControllers(sourceFiles) {
|
|
765
|
+
const collectedTypes = /* @__PURE__ */ new Set();
|
|
766
|
+
for (const file of sourceFiles) {
|
|
767
|
+
for (const cls of file.getClasses()) {
|
|
768
|
+
for (const method of cls.getMethods()) {
|
|
769
|
+
this.collectTypesFromMethod(method, collectedTypes);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return collectedTypes;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Collects types from a single method
|
|
777
|
+
*/
|
|
778
|
+
collectTypesFromMethod(method, collectedTypes) {
|
|
779
|
+
for (const param of method.getParameters()) {
|
|
780
|
+
const type2 = extractNamedType(param.getType());
|
|
781
|
+
if (type2) collectedTypes.add(type2);
|
|
782
|
+
}
|
|
783
|
+
const returnType = method.getReturnType();
|
|
784
|
+
const innerType = returnType.getTypeArguments()[0] ?? returnType;
|
|
785
|
+
const type = extractNamedType(innerType);
|
|
786
|
+
if (type) collectedTypes.add(type);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Processes collected types to generate schemas
|
|
790
|
+
*/
|
|
791
|
+
async processTypes(collectedTypes) {
|
|
792
|
+
const schemas = [];
|
|
793
|
+
for (const typeName of collectedTypes) {
|
|
794
|
+
try {
|
|
795
|
+
const schema = await this.generateSchemaForType(typeName);
|
|
796
|
+
const typescriptType = generateTypeScriptInterface(typeName, schema);
|
|
797
|
+
schemas.push({
|
|
798
|
+
type: typeName,
|
|
799
|
+
schema,
|
|
800
|
+
typescriptType
|
|
801
|
+
});
|
|
802
|
+
} catch (err) {
|
|
803
|
+
console.error(`Failed to generate schema for ${typeName}:`, err);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return schemas;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Generates schema for a specific type
|
|
810
|
+
*/
|
|
811
|
+
async generateSchemaForType(typeName) {
|
|
812
|
+
try {
|
|
813
|
+
const generator = (0, import_ts_json_schema_generator.createGenerator)({
|
|
814
|
+
path: this.controllerPattern,
|
|
815
|
+
tsconfig: this.tsConfigPath,
|
|
816
|
+
type: typeName,
|
|
817
|
+
skipTypeCheck: false
|
|
818
|
+
// Enable type checking for better error detection
|
|
819
|
+
});
|
|
820
|
+
return generator.createSchema(typeName);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
console.error(`Failed to generate schema for type ${typeName}:`, error);
|
|
823
|
+
return {
|
|
824
|
+
type: "object",
|
|
825
|
+
properties: {},
|
|
826
|
+
required: []
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
// src/rpc.plugin.ts
|
|
833
|
+
var RPCPlugin = class {
|
|
834
|
+
controllerPattern;
|
|
835
|
+
tsConfigPath;
|
|
836
|
+
outputDir;
|
|
837
|
+
generateOnInit;
|
|
838
|
+
// Services
|
|
839
|
+
routeAnalyzer;
|
|
840
|
+
schemaGenerator;
|
|
841
|
+
clientGenerator;
|
|
842
|
+
// Internal state
|
|
843
|
+
analyzedRoutes = [];
|
|
844
|
+
analyzedSchemas = [];
|
|
845
|
+
generatedInfo = null;
|
|
846
|
+
constructor(options = {}) {
|
|
847
|
+
this.controllerPattern = options.controllerPattern ?? DEFAULT_OPTIONS.controllerPattern;
|
|
848
|
+
this.tsConfigPath = options.tsConfigPath ?? import_path2.default.resolve(process.cwd(), DEFAULT_OPTIONS.tsConfigPath);
|
|
849
|
+
this.outputDir = options.outputDir ?? import_path2.default.resolve(process.cwd(), DEFAULT_OPTIONS.outputDir);
|
|
850
|
+
this.generateOnInit = options.generateOnInit ?? DEFAULT_OPTIONS.generateOnInit;
|
|
851
|
+
this.routeAnalyzer = new RouteAnalyzerService(this.controllerPattern, this.tsConfigPath);
|
|
852
|
+
this.schemaGenerator = new SchemaGeneratorService(this.controllerPattern, this.tsConfigPath);
|
|
853
|
+
this.clientGenerator = new ClientGeneratorService(this.outputDir);
|
|
854
|
+
this.validateConfiguration();
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Validates the plugin configuration
|
|
858
|
+
*/
|
|
859
|
+
validateConfiguration() {
|
|
860
|
+
const errors = [];
|
|
861
|
+
if (!this.controllerPattern?.trim()) {
|
|
862
|
+
errors.push("Controller pattern cannot be empty");
|
|
863
|
+
}
|
|
864
|
+
if (!this.tsConfigPath?.trim()) {
|
|
865
|
+
errors.push("TypeScript config path cannot be empty");
|
|
866
|
+
} else {
|
|
867
|
+
if (!import_fs.default.existsSync(this.tsConfigPath)) {
|
|
868
|
+
errors.push(`TypeScript config file not found at: ${this.tsConfigPath}`);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (!this.outputDir?.trim()) {
|
|
872
|
+
errors.push("Output directory cannot be empty");
|
|
873
|
+
}
|
|
874
|
+
if (errors.length > 0) {
|
|
875
|
+
throw new Error(`Configuration validation failed: ${errors.join(", ")}`);
|
|
876
|
+
}
|
|
877
|
+
this.log(
|
|
878
|
+
`Configuration validated: controllerPattern=${this.controllerPattern}, tsConfigPath=${this.tsConfigPath}, outputDir=${this.outputDir}`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Called after all modules are registered
|
|
883
|
+
*/
|
|
884
|
+
afterModulesRegistered = async (app, hono) => {
|
|
885
|
+
if (this.generateOnInit) {
|
|
886
|
+
await this.analyzeEverything();
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
/**
|
|
890
|
+
* Main analysis method that coordinates all three components
|
|
891
|
+
*/
|
|
892
|
+
async analyzeEverything() {
|
|
893
|
+
try {
|
|
894
|
+
this.log("Starting comprehensive RPC analysis...");
|
|
895
|
+
this.analyzedRoutes.push(...await this.routeAnalyzer.analyzeControllerMethods());
|
|
896
|
+
this.analyzedSchemas.push(...await this.schemaGenerator.generateSchemas());
|
|
897
|
+
this.generatedInfo = await this.clientGenerator.generateClient(this.analyzedRoutes, this.analyzedSchemas);
|
|
898
|
+
this.log(
|
|
899
|
+
`\u2705 RPC analysis complete: ${this.analyzedRoutes.length} routes, ${this.analyzedSchemas.length} schemas`
|
|
900
|
+
);
|
|
901
|
+
} catch (error) {
|
|
902
|
+
this.logError("Error during RPC analysis:", error);
|
|
903
|
+
this.dispose();
|
|
904
|
+
throw error;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Manually trigger analysis (useful for testing or re-generation)
|
|
909
|
+
*/
|
|
910
|
+
async analyze() {
|
|
911
|
+
await this.analyzeEverything();
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Get the analyzed routes
|
|
915
|
+
*/
|
|
916
|
+
getRoutes() {
|
|
917
|
+
return this.analyzedRoutes;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Get the analyzed schemas
|
|
921
|
+
*/
|
|
922
|
+
getSchemas() {
|
|
923
|
+
return this.analyzedSchemas;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Get the generation info
|
|
927
|
+
*/
|
|
928
|
+
getGenerationInfo() {
|
|
929
|
+
return this.generatedInfo;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Cleanup resources to prevent memory leaks
|
|
933
|
+
*/
|
|
934
|
+
dispose() {
|
|
935
|
+
this.routeAnalyzer.dispose();
|
|
936
|
+
this.schemaGenerator.dispose();
|
|
937
|
+
this.log("Resources cleaned up");
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Plugin lifecycle cleanup
|
|
941
|
+
*/
|
|
942
|
+
onDestroy() {
|
|
943
|
+
this.dispose();
|
|
944
|
+
}
|
|
945
|
+
// ============================================================================
|
|
946
|
+
// LOGGING UTILITIES
|
|
947
|
+
// ============================================================================
|
|
948
|
+
/**
|
|
949
|
+
* Logs a message with the plugin prefix
|
|
950
|
+
*/
|
|
951
|
+
log(message) {
|
|
952
|
+
console.log(`${LOG_PREFIX} ${message}`);
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Logs an error with the plugin prefix
|
|
956
|
+
*/
|
|
957
|
+
logError(message, error) {
|
|
958
|
+
console.error(`${LOG_PREFIX} ${message}`, error || "");
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
962
|
+
0 && (module.exports = {
|
|
963
|
+
BUILTIN_TYPES,
|
|
964
|
+
BUILTIN_UTILITY_TYPES,
|
|
965
|
+
ClientGeneratorService,
|
|
966
|
+
DEFAULT_OPTIONS,
|
|
967
|
+
GENERIC_TYPES,
|
|
968
|
+
LOG_PREFIX,
|
|
969
|
+
RPCPlugin,
|
|
970
|
+
RouteAnalyzerService,
|
|
971
|
+
SchemaGeneratorService,
|
|
972
|
+
buildFullApiPath,
|
|
973
|
+
buildFullPath,
|
|
974
|
+
camelCase,
|
|
975
|
+
extractNamedType,
|
|
976
|
+
generateTypeImports,
|
|
977
|
+
generateTypeScriptInterface,
|
|
978
|
+
mapJsonSchemaTypeToTypeScript,
|
|
979
|
+
safeToString
|
|
980
|
+
});
|
|
981
|
+
//# sourceMappingURL=index.js.map
|