@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Orkhan Karimov <karimovok1@gmail.com> (https://github.com/kerimovok)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # @honestjs/rpc-plugin
2
+
3
+ A comprehensive RPC plugin for HonestJS that combines route analysis, schema generation, and client generation into a
4
+ single, tightly-coupled solution.
5
+
6
+ ## Features
7
+
8
+ - **Route Analysis**: Automatically analyzes controller methods and extracts type information using ts-morph
9
+ - **Schema Generation**: Generates JSON schemas and TypeScript interfaces from types used in controllers
10
+ - **Client Generation**: Creates a fully-typed TypeScript RPC client with proper parameter typing
11
+ - **Tight Integration**: All components share data directly, eliminating duplication and file I/O overhead
12
+ - **Type Safety**: Full TypeScript support with generated types and interfaces
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @honestjs/rpc-plugin
18
+ # or
19
+ yarn add @honestjs/rpc-plugin
20
+ # or
21
+ pnpm add @honestjs/rpc-plugin
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Basic Setup
27
+
28
+ ```typescript
29
+ import { RPCPlugin } from '@honestjs/rpc-plugin'
30
+ import { Application } from 'honestjs'
31
+
32
+ const app = new Application({
33
+ plugins: [
34
+ new RPCPlugin({
35
+ outputDir: './generated/rpc'
36
+ })
37
+ ]
38
+ })
39
+ ```
40
+
41
+ ### Configuration Options
42
+
43
+ ```typescript
44
+ interface RPCPluginOptions {
45
+ readonly controllerPattern?: string // Glob pattern for controller files (default: 'src/modules/*/*.controller.ts')
46
+ readonly tsConfigPath?: string // Path to tsconfig.json (default: 'tsconfig.json')
47
+ readonly outputDir?: string // Output directory for generated files (default: './generated/rpc')
48
+ readonly generateOnInit?: boolean // Generate files on initialization (default: true)
49
+ }
50
+ ```
51
+
52
+ ## What It Generates
53
+
54
+ ### 1. TypeScript RPC Client (`client.ts`)
55
+
56
+ A fully-typed client that provides:
57
+
58
+ - **Controller-based organization**: Methods grouped by controller
59
+ - **Type-safe parameters**: Path, query, and body parameters with proper typing
60
+ - **Flexible request options**: Clean separation of params, query, body, and headers
61
+ - **Error handling**: Built-in error handling with custom ApiError class
62
+ - **Authentication support**: Easy header and auth token management
63
+
64
+ ```typescript
65
+ // Generated client usage
66
+ import { ApiClient } from './generated/rpc/client'
67
+
68
+ // Create client instance with base URL
69
+ const apiClient = new ApiClient('http://localhost:3000')
70
+
71
+ // Type-safe API calls
72
+ const user = await apiClient.users.create({
73
+ body: { name: 'John', email: 'john@example.com' }
74
+ })
75
+
76
+ const users = await apiClient.users.list({
77
+ query: { page: 1, limit: 10 }
78
+ })
79
+
80
+ const user = await apiClient.users.getById({
81
+ params: { id: '123' }
82
+ })
83
+
84
+ // Set default authentication
85
+ apiClient.setDefaultAuth('your-jwt-token')
86
+
87
+ // Set custom headers
88
+ apiClient.setDefaultHeaders({
89
+ 'X-API-Key': 'your-api-key'
90
+ })
91
+ ```
92
+
93
+ ### 2. Type Definitions (`types.ts`)
94
+
95
+ Comprehensive type definitions including:
96
+
97
+ - **Generated DTOs**: TypeScript interfaces from your controller types
98
+ - **Route information**: Complete route metadata with parameter types
99
+ - **API response types**: Standardized response and error types
100
+ - **Utility types**: Request options and parameter types
101
+
102
+ ## How It Works
103
+
104
+ ### 1. Route Analysis
105
+
106
+ - Scans your HonestJS route registry
107
+ - Uses ts-morph to analyze controller source code
108
+ - Extracts method signatures, parameter types, and return types
109
+ - Builds comprehensive route metadata
110
+
111
+ ### 2. Schema Generation
112
+
113
+ - Analyzes types used in controller methods
114
+ - Generates JSON schemas using ts-json-schema-generator
115
+ - Creates TypeScript interfaces from schemas
116
+ - Integrates with route analysis for complete type coverage
117
+
118
+ ### 3. Client Generation
119
+
120
+ - Groups routes by controller for organization
121
+ - Generates type-safe method signatures
122
+ - Creates parameter validation and typing
123
+ - Builds the complete RPC client with proper error handling
124
+
125
+ ## Benefits of the Unified Approach
126
+
127
+ - **No Duplication**: Single source of truth for all type information
128
+ - **Tight Coupling**: Components share data directly without file I/O
129
+ - **Better Performance**: Eliminates redundant analysis and file generation
130
+ - **Consistent Types**: All generated code uses the same type definitions
131
+ - **Easier Maintenance**: Single plugin to configure and maintain
132
+
133
+ ## Example Generated Output
134
+
135
+ ### Route Analysis
136
+
137
+ ```typescript
138
+ export interface ExtendedRouteInfo {
139
+ controller: string
140
+ handler: string
141
+ method: string
142
+ path: string
143
+ fullPath: string
144
+ returns?: string
145
+ parameters?: ParameterMetadataWithType[]
146
+ }
147
+
148
+ export const ANALYZED_ROUTES = [
149
+ {
150
+ controller: 'UsersController',
151
+ handler: 'create',
152
+ method: 'POST',
153
+ path: '/',
154
+ fullPath: '/api/v1/users/',
155
+ returns: 'Promise<User>',
156
+ parameters: [
157
+ {
158
+ index: 0,
159
+ name: 'createUserDto',
160
+ type: 'CreateUserDto',
161
+ required: true,
162
+ data: undefined
163
+ }
164
+ ]
165
+ }
166
+ ] as const
167
+ ```
168
+
169
+ ### Generated Client
170
+
171
+ ```typescript
172
+ export class ApiClient {
173
+ get users() {
174
+ return {
175
+ create: async (
176
+ options: RequestOptions<{ name: string; email: string }, undefined, undefined, undefined>
177
+ ): Promise<ApiResponse<any>> => {
178
+ return this.request('POST', `/api/v1/users/`, options)
179
+ },
180
+ list: async (
181
+ options?: RequestOptions<undefined, { page: number; limit: number }, undefined, undefined>
182
+ ): Promise<ApiResponse<any>> => {
183
+ return this.request('GET', `/api/v1/users/`, options)
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // RequestOptions type definition
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
+
201
+ ## Plugin Lifecycle
202
+
203
+ The plugin automatically generates files when your HonestJS application starts up (if `generateOnInit` is true). You can
204
+ also manually trigger generation:
205
+
206
+ ```typescript
207
+ const rpcPlugin = new RPCPlugin()
208
+ await rpcPlugin.analyze() // Manually trigger analysis and generation
209
+ ```
210
+
211
+ ## Dependencies
212
+
213
+ - **ts-morph**: TypeScript source code analysis
214
+ - **ts-json-schema-generator**: JSON schema generation from TypeScript types
215
+ - **honestjs**: Core framework integration
216
+
217
+ ## License
218
+
219
+ MIT
@@ -0,0 +1,337 @@
1
+ import { RouteInfo, ParameterMetadata, IPlugin, Application } from 'honestjs';
2
+ import { Hono } from 'hono';
3
+ import { Type } from 'ts-morph';
4
+
5
+ /**
6
+ * Parameter metadata with enhanced type information
7
+ */
8
+ interface ParameterMetadataWithType extends ParameterMetadata {
9
+ readonly type: string;
10
+ readonly required: boolean;
11
+ readonly name: string;
12
+ }
13
+ /**
14
+ * Extended route information with comprehensive type data
15
+ */
16
+ interface ExtendedRouteInfo extends Omit<RouteInfo, 'parameters' | 'method'> {
17
+ readonly returns?: string;
18
+ readonly parameters?: readonly ParameterMetadataWithType[];
19
+ readonly method: string;
20
+ readonly fullPath: string;
21
+ }
22
+ /**
23
+ * Controller groups for organization
24
+ */
25
+ type ControllerGroups = Map<string, readonly ExtendedRouteInfo[]>;
26
+ /**
27
+ * Route parameter information for client generation
28
+ * Extends ParameterMetadataWithType for consistency
29
+ */
30
+ interface RouteParameter extends ParameterMetadataWithType {
31
+ }
32
+
33
+ /**
34
+ * Schema with comprehensive type information
35
+ */
36
+ interface SchemaInfo {
37
+ readonly type: string;
38
+ readonly schema: Record<string, any>;
39
+ readonly typescriptType?: string;
40
+ }
41
+ /**
42
+ * Generated client file information
43
+ */
44
+ interface GeneratedClientInfo {
45
+ readonly clientFile: string;
46
+ readonly generatedAt: string;
47
+ }
48
+
49
+ /**
50
+ * Clean separation of concerns for request options
51
+ */
52
+ type RequestOptions<TParams = never, TQuery = never, TBody = never, THeaders = never> = (TParams extends never ? {
53
+ params?: never;
54
+ } : {
55
+ params: TParams;
56
+ }) & (TQuery extends never ? {
57
+ query?: never;
58
+ } : {
59
+ query?: TQuery;
60
+ }) & (TBody extends never ? {
61
+ body?: never;
62
+ } : {
63
+ body: TBody;
64
+ }) & (THeaders extends never ? {
65
+ headers?: never;
66
+ } : {
67
+ headers: THeaders;
68
+ });
69
+ /**
70
+ * API Response wrapper
71
+ */
72
+ interface ApiResponse<T = any> {
73
+ data: T;
74
+ message?: string;
75
+ success: boolean;
76
+ }
77
+ /**
78
+ * API Error class
79
+ */
80
+ declare class ApiError extends Error {
81
+ statusCode: number;
82
+ constructor(statusCode: number, message: string);
83
+ }
84
+
85
+ /**
86
+ * Configuration options for the RPCPlugin
87
+ */
88
+ interface RPCPluginOptions {
89
+ readonly controllerPattern?: string;
90
+ readonly tsConfigPath?: string;
91
+ readonly outputDir?: string;
92
+ readonly generateOnInit?: boolean;
93
+ }
94
+ /**
95
+ * Comprehensive RPC plugin that combines route analysis, schema generation, and client generation
96
+ */
97
+ declare class RPCPlugin implements IPlugin {
98
+ private readonly controllerPattern;
99
+ private readonly tsConfigPath;
100
+ private readonly outputDir;
101
+ private readonly generateOnInit;
102
+ private readonly routeAnalyzer;
103
+ private readonly schemaGenerator;
104
+ private readonly clientGenerator;
105
+ private readonly analyzedRoutes;
106
+ private readonly analyzedSchemas;
107
+ private generatedInfo;
108
+ constructor(options?: RPCPluginOptions);
109
+ /**
110
+ * Validates the plugin configuration
111
+ */
112
+ private validateConfiguration;
113
+ /**
114
+ * Called after all modules are registered
115
+ */
116
+ afterModulesRegistered: (app: Application, hono: Hono) => Promise<void>;
117
+ /**
118
+ * Main analysis method that coordinates all three components
119
+ */
120
+ private analyzeEverything;
121
+ /**
122
+ * Manually trigger analysis (useful for testing or re-generation)
123
+ */
124
+ analyze(): Promise<void>;
125
+ /**
126
+ * Get the analyzed routes
127
+ */
128
+ getRoutes(): readonly ExtendedRouteInfo[];
129
+ /**
130
+ * Get the analyzed schemas
131
+ */
132
+ getSchemas(): readonly SchemaInfo[];
133
+ /**
134
+ * Get the generation info
135
+ */
136
+ getGenerationInfo(): GeneratedClientInfo | null;
137
+ /**
138
+ * Cleanup resources to prevent memory leaks
139
+ */
140
+ dispose(): void;
141
+ /**
142
+ * Plugin lifecycle cleanup
143
+ */
144
+ onDestroy(): void;
145
+ /**
146
+ * Logs a message with the plugin prefix
147
+ */
148
+ private log;
149
+ /**
150
+ * Logs an error with the plugin prefix
151
+ */
152
+ private logError;
153
+ }
154
+
155
+ /**
156
+ * Service for generating TypeScript RPC clients
157
+ */
158
+ declare class ClientGeneratorService {
159
+ private readonly outputDir;
160
+ constructor(outputDir: string);
161
+ /**
162
+ * Generates the TypeScript RPC client
163
+ */
164
+ generateClient(routes: readonly ExtendedRouteInfo[], schemas: readonly SchemaInfo[]): Promise<GeneratedClientInfo>;
165
+ /**
166
+ * Generates the main client file with types included
167
+ */
168
+ private generateClientFile;
169
+ /**
170
+ * Generates the client TypeScript content with types included
171
+ */
172
+ private generateClientContent;
173
+ /**
174
+ * Generates controller methods for the client
175
+ */
176
+ private generateControllerMethods;
177
+ /**
178
+ * Extracts the proper return type from route analysis
179
+ */
180
+ private extractReturnType;
181
+ /**
182
+ * Generates schema types from integrated schema generation
183
+ */
184
+ private generateSchemaTypes;
185
+ /**
186
+ * Groups routes by controller for better organization
187
+ */
188
+ private groupRoutesByController;
189
+ /**
190
+ * Analyzes route parameters to determine their types and usage
191
+ */
192
+ private analyzeRouteParameters;
193
+ }
194
+
195
+ /**
196
+ * Service for analyzing controller methods and extracting type information
197
+ */
198
+ declare class RouteAnalyzerService {
199
+ private readonly controllerPattern;
200
+ private readonly tsConfigPath;
201
+ constructor(controllerPattern: string, tsConfigPath: string);
202
+ private projects;
203
+ /**
204
+ * Analyzes controller methods to extract type information
205
+ */
206
+ analyzeControllerMethods(): Promise<ExtendedRouteInfo[]>;
207
+ /**
208
+ * Creates a new ts-morph project
209
+ */
210
+ private createProject;
211
+ /**
212
+ * Cleanup resources to prevent memory leaks
213
+ */
214
+ dispose(): void;
215
+ /**
216
+ * Finds controller classes in the project
217
+ */
218
+ private findControllerClasses;
219
+ /**
220
+ * Processes all routes and extracts type information
221
+ */
222
+ private processRoutes;
223
+ /**
224
+ * Creates an extended route with type information
225
+ */
226
+ private createExtendedRoute;
227
+ /**
228
+ * Gets the return type of a method
229
+ */
230
+ private getReturnType;
231
+ /**
232
+ * Gets parameters with their types
233
+ */
234
+ private getParametersWithTypes;
235
+ }
236
+
237
+ /**
238
+ * Service for generating JSON schemas from TypeScript types used in controllers
239
+ */
240
+ declare class SchemaGeneratorService {
241
+ private readonly controllerPattern;
242
+ private readonly tsConfigPath;
243
+ constructor(controllerPattern: string, tsConfigPath: string);
244
+ private projects;
245
+ /**
246
+ * Generates JSON schemas from types used in controllers
247
+ */
248
+ generateSchemas(): Promise<SchemaInfo[]>;
249
+ /**
250
+ * Creates a new ts-morph project
251
+ */
252
+ private createProject;
253
+ /**
254
+ * Cleanup resources to prevent memory leaks
255
+ */
256
+ dispose(): void;
257
+ /**
258
+ * Collects types from controller files
259
+ */
260
+ private collectTypesFromControllers;
261
+ /**
262
+ * Collects types from a single method
263
+ */
264
+ private collectTypesFromMethod;
265
+ /**
266
+ * Processes collected types to generate schemas
267
+ */
268
+ private processTypes;
269
+ /**
270
+ * Generates schema for a specific type
271
+ */
272
+ private generateSchemaForType;
273
+ }
274
+
275
+ /**
276
+ * Builds the full path with parameter placeholders
277
+ */
278
+ declare function buildFullPath(basePath: string, parameters: readonly ParameterMetadata[]): string;
279
+ /**
280
+ * Builds the full API path using route information
281
+ */
282
+ declare function buildFullApiPath(route: ExtendedRouteInfo): string;
283
+
284
+ /**
285
+ * Maps JSON schema types to TypeScript types
286
+ */
287
+ declare function mapJsonSchemaTypeToTypeScript(schema: Record<string, any>): string;
288
+ /**
289
+ * Generates TypeScript interface from JSON schema
290
+ */
291
+ declare function generateTypeScriptInterface(typeName: string, schema: Record<string, any>): string;
292
+
293
+ /**
294
+ * Safely converts a value to string, handling symbols and other types
295
+ */
296
+ declare function safeToString(value: unknown): string;
297
+ /**
298
+ * Converts a string to camelCase
299
+ */
300
+ declare function camelCase(str: string): string;
301
+
302
+ /**
303
+ * Extracts a named type from a TypeScript type
304
+ */
305
+ declare function extractNamedType(type: Type): string | null;
306
+ /**
307
+ * Generates type imports for the client
308
+ */
309
+ declare function generateTypeImports(routes: readonly any[]): string;
310
+
311
+ /**
312
+ * Default configuration options for the RPCPlugin
313
+ */
314
+ declare const DEFAULT_OPTIONS: {
315
+ readonly controllerPattern: "src/modules/*/*.controller.ts";
316
+ readonly tsConfigPath: "tsconfig.json";
317
+ readonly outputDir: "./generated/rpc";
318
+ readonly generateOnInit: true;
319
+ };
320
+ /**
321
+ * Log prefix for the RPC plugin
322
+ */
323
+ declare const LOG_PREFIX = "[ RPCPlugin ]";
324
+ /**
325
+ * Built-in TypeScript types that should not be imported
326
+ */
327
+ declare const BUILTIN_UTILITY_TYPES: Set<string>;
328
+ /**
329
+ * Built-in TypeScript types that should be skipped
330
+ */
331
+ declare const BUILTIN_TYPES: Set<string>;
332
+ /**
333
+ * Generic type names that should be unwrapped
334
+ */
335
+ declare const GENERIC_TYPES: Set<string>;
336
+
337
+ export { ApiError, type ApiResponse, BUILTIN_TYPES, BUILTIN_UTILITY_TYPES, ClientGeneratorService, type ControllerGroups, DEFAULT_OPTIONS, type ExtendedRouteInfo, GENERIC_TYPES, type GeneratedClientInfo, LOG_PREFIX, type ParameterMetadataWithType, RPCPlugin, type RPCPluginOptions, type RequestOptions, RouteAnalyzerService, type RouteParameter, SchemaGeneratorService, type SchemaInfo, buildFullApiPath, buildFullPath, camelCase, extractNamedType, generateTypeImports, generateTypeScriptInterface, mapJsonSchemaTypeToTypeScript, safeToString };