@honestjs/rpc-plugin 1.4.1 → 1.6.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 +203 -129
- package/dist/index.d.mts +112 -21
- package/dist/index.d.ts +112 -21
- package/dist/index.js +339 -123
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +333 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# RPC Plugin
|
|
2
2
|
|
|
3
|
-
The RPC Plugin automatically analyzes your HonestJS controllers and
|
|
4
|
-
proper parameter typing.
|
|
3
|
+
The RPC Plugin automatically analyzes your HonestJS controllers and, by default,
|
|
4
|
+
generates a fully-typed TypeScript RPC client with proper parameter typing. You
|
|
5
|
+
can also provide custom generators.
|
|
5
6
|
|
|
6
7
|
## Installation
|
|
7
8
|
|
|
@@ -16,102 +17,135 @@ pnpm add @honestjs/rpc-plugin
|
|
|
16
17
|
## Basic Setup
|
|
17
18
|
|
|
18
19
|
```typescript
|
|
19
|
-
import { RPCPlugin } from
|
|
20
|
-
import { Application } from
|
|
21
|
-
import AppModule from
|
|
20
|
+
import { RPCPlugin } from "@honestjs/rpc-plugin";
|
|
21
|
+
import { Application } from "honestjs";
|
|
22
|
+
import AppModule from "./app.module";
|
|
22
23
|
|
|
23
24
|
const { hono } = await Application.create(AppModule, {
|
|
24
|
-
plugins: [new RPCPlugin()]
|
|
25
|
-
})
|
|
25
|
+
plugins: [new RPCPlugin()],
|
|
26
|
+
});
|
|
26
27
|
|
|
27
|
-
export default hono
|
|
28
|
+
export default hono;
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
## Configuration Options
|
|
31
32
|
|
|
32
33
|
```typescript
|
|
33
34
|
interface RPCPluginOptions {
|
|
34
|
-
readonly controllerPattern?: string // Glob pattern for controller files (default: 'src/modules/*/*.controller.ts')
|
|
35
|
-
readonly tsConfigPath?: string // Path to tsconfig.json (default: 'tsconfig.json')
|
|
36
|
-
readonly outputDir?: string // Output directory for generated files (default: './generated/rpc')
|
|
37
|
-
readonly generateOnInit?: boolean // Generate files on initialization (default: true)
|
|
35
|
+
readonly controllerPattern?: string; // Glob pattern for controller files (default: 'src/modules/*/*.controller.ts')
|
|
36
|
+
readonly tsConfigPath?: string; // Path to tsconfig.json (default: 'tsconfig.json')
|
|
37
|
+
readonly outputDir?: string; // Output directory for generated files (default: './generated/rpc')
|
|
38
|
+
readonly generateOnInit?: boolean; // Generate files on initialization (default: true)
|
|
39
|
+
readonly generators?: readonly RPCGenerator[]; // Optional list of generators to execute
|
|
40
|
+
readonly mode?: "strict" | "best-effort"; // strict fails on warnings/fallbacks
|
|
41
|
+
readonly logLevel?: "silent" | "error" | "warn" | "info" | "debug"; // default: info
|
|
42
|
+
readonly customClassMatcher?: (
|
|
43
|
+
classDeclaration: ClassDeclaration,
|
|
44
|
+
) => boolean; // optional override; default discovery uses decorators
|
|
45
|
+
readonly failOnSchemaError?: boolean; // default true in strict mode
|
|
46
|
+
readonly failOnRouteAnalysisWarning?: boolean; // default true in strict mode
|
|
38
47
|
readonly context?: {
|
|
39
|
-
readonly namespace?: string // Default: 'rpc'
|
|
48
|
+
readonly namespace?: string; // Default: 'rpc'
|
|
40
49
|
readonly keys?: {
|
|
41
|
-
readonly artifact?: string // Default: 'artifact'
|
|
42
|
-
}
|
|
43
|
-
}
|
|
50
|
+
readonly artifact?: string; // Default: 'artifact'
|
|
51
|
+
};
|
|
52
|
+
};
|
|
44
53
|
}
|
|
45
54
|
```
|
|
46
55
|
|
|
56
|
+
### Generator Behavior
|
|
57
|
+
|
|
58
|
+
- If `generators` is omitted, the plugin uses the built-in
|
|
59
|
+
`TypeScriptClientGenerator` by default.
|
|
60
|
+
- If `generators` is provided, only those generators are executed.
|
|
61
|
+
- You can still use the built-in TypeScript client generator explicitly:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { RPCPlugin, TypeScriptClientGenerator } from "@honestjs/rpc-plugin";
|
|
65
|
+
|
|
66
|
+
new RPCPlugin({
|
|
67
|
+
generators: [new TypeScriptClientGenerator("./generated/rpc")],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
47
71
|
## Application Context Artifact
|
|
48
72
|
|
|
49
73
|
After analysis, RPC plugin publishes this artifact to the application context:
|
|
50
74
|
|
|
51
75
|
```typescript
|
|
52
76
|
type RpcArtifact = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
artifactVersion: string;
|
|
78
|
+
routes: ExtendedRouteInfo[];
|
|
79
|
+
schemas: SchemaInfo[];
|
|
80
|
+
};
|
|
56
81
|
```
|
|
57
82
|
|
|
58
|
-
Default key is `'rpc.artifact'` (from
|
|
83
|
+
Default key is `'rpc.artifact'` (from
|
|
84
|
+
`context.namespace + '.' + context.keys.artifact`). This enables direct
|
|
85
|
+
integration with API docs:
|
|
59
86
|
|
|
60
87
|
```typescript
|
|
61
|
-
import { ApiDocsPlugin } from
|
|
88
|
+
import { ApiDocsPlugin } from "@honestjs/api-docs-plugin";
|
|
62
89
|
|
|
63
90
|
const { hono } = await Application.create(AppModule, {
|
|
64
|
-
plugins: [new RPCPlugin(), new ApiDocsPlugin({ artifact:
|
|
65
|
-
})
|
|
91
|
+
plugins: [new RPCPlugin(), new ApiDocsPlugin({ artifact: "rpc.artifact" })],
|
|
92
|
+
});
|
|
66
93
|
```
|
|
67
94
|
|
|
95
|
+
`artifactVersion` is currently `"1"` and is used for compatibility checks.
|
|
96
|
+
|
|
68
97
|
## What It Generates
|
|
69
98
|
|
|
70
99
|
The plugin generates files in the output directory (default: `./generated/rpc`):
|
|
71
100
|
|
|
72
|
-
| File
|
|
73
|
-
|
|
|
74
|
-
| `client.ts`
|
|
75
|
-
| `.rpc-checksum`
|
|
76
|
-
| `rpc-artifact.json`
|
|
101
|
+
| File | Description | When generated |
|
|
102
|
+
| ---------------------- | ---------------------------------------------------------------------- | ------------------------------ |
|
|
103
|
+
| `client.ts` | Type-safe RPC client with all DTOs | When TypeScript generator runs |
|
|
104
|
+
| `.rpc-checksum` | Hash of source files for incremental caching | Always |
|
|
105
|
+
| `rpc-artifact.json` | Serialized routes/schemas artifact for cache-backed context publishing | Always |
|
|
106
|
+
| `rpc-diagnostics.json` | Diagnostics report (mode, warnings, cache status) | Always |
|
|
77
107
|
|
|
78
108
|
### TypeScript RPC Client (`client.ts`)
|
|
79
109
|
|
|
80
|
-
The plugin generates a single comprehensive file that includes both the client
|
|
110
|
+
The plugin generates a single comprehensive file that includes both the client
|
|
111
|
+
and all type definitions:
|
|
81
112
|
|
|
82
113
|
- **Controller-based organization**: Methods grouped by controller
|
|
83
114
|
- **Type-safe parameters**: Path, query, and body parameters with proper typing
|
|
84
|
-
- **Flexible request options**: Clean separation of params, query, body, and
|
|
115
|
+
- **Flexible request options**: Clean separation of params, query, body, and
|
|
116
|
+
headers
|
|
85
117
|
- **Error handling**: Built-in error handling with custom ApiError class
|
|
86
118
|
- **Header management**: Easy custom header management
|
|
87
|
-
- **Custom fetch support**: Inject custom fetch implementations for testing,
|
|
88
|
-
|
|
119
|
+
- **Custom fetch support**: Inject custom fetch implementations for testing,
|
|
120
|
+
middleware, and compatibility
|
|
121
|
+
- **Integrated types**: All DTOs, interfaces, and utility types included in the
|
|
122
|
+
same file
|
|
89
123
|
|
|
90
124
|
```typescript
|
|
91
125
|
// Generated client usage
|
|
92
|
-
import { ApiClient } from
|
|
126
|
+
import { ApiClient } from "./generated/rpc/client";
|
|
93
127
|
|
|
94
128
|
// Create client instance with base URL
|
|
95
|
-
const apiClient = new ApiClient(
|
|
129
|
+
const apiClient = new ApiClient("http://localhost:3000");
|
|
96
130
|
|
|
97
131
|
// Type-safe API calls
|
|
98
132
|
const user = await apiClient.users.create({
|
|
99
|
-
body: { name:
|
|
100
|
-
})
|
|
133
|
+
body: { name: "John", email: "john@example.com" },
|
|
134
|
+
});
|
|
101
135
|
|
|
102
136
|
const users = await apiClient.users.list({
|
|
103
|
-
query: { page: 1, limit: 10 }
|
|
104
|
-
})
|
|
137
|
+
query: { page: 1, limit: 10 },
|
|
138
|
+
});
|
|
105
139
|
|
|
106
140
|
const user = await apiClient.users.getById({
|
|
107
|
-
params: { id:
|
|
108
|
-
})
|
|
141
|
+
params: { id: "123" },
|
|
142
|
+
});
|
|
109
143
|
|
|
110
144
|
// Set custom headers
|
|
111
145
|
apiClient.setDefaultHeaders({
|
|
112
|
-
|
|
113
|
-
Authorization:
|
|
114
|
-
})
|
|
146
|
+
"X-API-Key": "your-api-key",
|
|
147
|
+
Authorization: "Bearer your-jwt-token",
|
|
148
|
+
});
|
|
115
149
|
```
|
|
116
150
|
|
|
117
151
|
The generated `client.ts` file contains everything you need:
|
|
@@ -127,7 +161,8 @@ The RPC client supports custom fetch implementations, which is useful for:
|
|
|
127
161
|
|
|
128
162
|
- **Testing**: Inject mock fetch functions for unit testing
|
|
129
163
|
- **Custom Logic**: Add logging, retries, or other middleware
|
|
130
|
-
- **Environment Compatibility**: Use different fetch implementations
|
|
164
|
+
- **Environment Compatibility**: Use different fetch implementations
|
|
165
|
+
(node-fetch, undici, etc.)
|
|
131
166
|
- **Interceptors**: Wrap requests with custom logic before/after execution
|
|
132
167
|
|
|
133
168
|
### Basic Custom Fetch Example
|
|
@@ -135,13 +170,18 @@ The RPC client supports custom fetch implementations, which is useful for:
|
|
|
135
170
|
```typescript
|
|
136
171
|
// Simple logging wrapper
|
|
137
172
|
const loggingFetch = (input: RequestInfo | URL, init?: RequestInit) => {
|
|
138
|
-
console.log(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
173
|
+
console.log(
|
|
174
|
+
`[${new Date().toISOString()}] Making ${
|
|
175
|
+
init?.method || "GET"
|
|
176
|
+
} request to:`,
|
|
177
|
+
input,
|
|
178
|
+
);
|
|
179
|
+
return fetch(input, init);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const apiClient = new ApiClient("http://localhost:3000", {
|
|
183
|
+
fetchFn: loggingFetch,
|
|
184
|
+
});
|
|
145
185
|
```
|
|
146
186
|
|
|
147
187
|
### Advanced Custom Fetch Examples
|
|
@@ -152,24 +192,26 @@ const retryFetch = (maxRetries = 3) => {
|
|
|
152
192
|
return async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
153
193
|
for (let i = 0; i <= maxRetries; i++) {
|
|
154
194
|
try {
|
|
155
|
-
const response = await fetch(input, init)
|
|
156
|
-
if (response.ok) return response
|
|
195
|
+
const response = await fetch(input, init);
|
|
196
|
+
if (response.ok) return response;
|
|
157
197
|
|
|
158
|
-
if (i === maxRetries) return response
|
|
198
|
+
if (i === maxRetries) return response;
|
|
159
199
|
|
|
160
200
|
// Wait with exponential backoff
|
|
161
|
-
await new Promise((resolve) =>
|
|
201
|
+
await new Promise((resolve) =>
|
|
202
|
+
setTimeout(resolve, Math.pow(2, i) * 1000)
|
|
203
|
+
);
|
|
162
204
|
} catch (error) {
|
|
163
|
-
if (i === maxRetries) throw error
|
|
205
|
+
if (i === maxRetries) throw error;
|
|
164
206
|
}
|
|
165
207
|
}
|
|
166
|
-
throw new Error(
|
|
167
|
-
}
|
|
168
|
-
}
|
|
208
|
+
throw new Error("Max retries exceeded");
|
|
209
|
+
};
|
|
210
|
+
};
|
|
169
211
|
|
|
170
|
-
const apiClientWithRetry = new ApiClient(
|
|
171
|
-
fetchFn: retryFetch(3)
|
|
172
|
-
})
|
|
212
|
+
const apiClientWithRetry = new ApiClient("http://localhost:3000", {
|
|
213
|
+
fetchFn: retryFetch(3),
|
|
214
|
+
});
|
|
173
215
|
|
|
174
216
|
// Request/response interceptor
|
|
175
217
|
const interceptorFetch = (input: RequestInfo | URL, init?: RequestInit) => {
|
|
@@ -178,20 +220,20 @@ const interceptorFetch = (input: RequestInfo | URL, init?: RequestInit) => {
|
|
|
178
220
|
...init,
|
|
179
221
|
headers: {
|
|
180
222
|
...init?.headers,
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
}
|
|
223
|
+
"X-Request-ID": crypto.randomUUID(),
|
|
224
|
+
},
|
|
225
|
+
};
|
|
184
226
|
|
|
185
227
|
return fetch(input, enhancedInit).then((response) => {
|
|
186
228
|
// Post-response logic
|
|
187
|
-
console.log(`Response status: ${response.status}`)
|
|
188
|
-
return response
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const apiClientWithInterceptor = new ApiClient(
|
|
193
|
-
fetchFn: interceptorFetch
|
|
194
|
-
})
|
|
229
|
+
console.log(`Response status: ${response.status}`);
|
|
230
|
+
return response;
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const apiClientWithInterceptor = new ApiClient("http://localhost:3000", {
|
|
235
|
+
fetchFn: interceptorFetch,
|
|
236
|
+
});
|
|
195
237
|
```
|
|
196
238
|
|
|
197
239
|
### Testing with Custom Fetch
|
|
@@ -200,37 +242,45 @@ const apiClientWithInterceptor = new ApiClient('http://localhost:3000', {
|
|
|
200
242
|
// Mock fetch for testing
|
|
201
243
|
const mockFetch = jest.fn().mockResolvedValue({
|
|
202
244
|
ok: true,
|
|
203
|
-
json: () => Promise.resolve({ data: { id:
|
|
204
|
-
})
|
|
245
|
+
json: () => Promise.resolve({ data: { id: "123", name: "Test User" } }),
|
|
246
|
+
});
|
|
205
247
|
|
|
206
|
-
const testApiClient = new ApiClient(
|
|
207
|
-
fetchFn: mockFetch
|
|
208
|
-
})
|
|
248
|
+
const testApiClient = new ApiClient("http://test.com", {
|
|
249
|
+
fetchFn: mockFetch,
|
|
250
|
+
});
|
|
209
251
|
|
|
210
252
|
// Your test can now verify the mock was called
|
|
211
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
253
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
254
|
+
"http://test.com/api/v1/users/123",
|
|
255
|
+
expect.objectContaining({ method: "GET" }),
|
|
256
|
+
);
|
|
212
257
|
```
|
|
213
258
|
|
|
214
259
|
## Hash-based Caching
|
|
215
260
|
|
|
216
|
-
On startup the plugin hashes all controller source files (SHA-256) and stores
|
|
261
|
+
On startup the plugin hashes all controller source files (SHA-256) and stores
|
|
262
|
+
the checksum in `.rpc-checksum` inside the output directory. On subsequent runs,
|
|
263
|
+
if the hash matches and the expected output files already exist, the expensive
|
|
264
|
+
analysis and generation pipeline is skipped entirely. This significantly reduces
|
|
265
|
+
startup time in large projects.
|
|
217
266
|
|
|
218
267
|
Caching is automatic and requires no configuration. To force regeneration:
|
|
219
268
|
|
|
220
269
|
```typescript
|
|
221
|
-
// Manual call — defaults to force=true, always regenerates
|
|
222
|
-
await rpcPlugin.analyze()
|
|
223
|
-
|
|
224
270
|
// Explicit cache bypass
|
|
225
|
-
await rpcPlugin.analyze(true)
|
|
271
|
+
await rpcPlugin.analyze({ force: true });
|
|
226
272
|
|
|
227
273
|
// Respect the cache (same behavior as automatic startup)
|
|
228
|
-
await rpcPlugin.analyze(false)
|
|
274
|
+
await rpcPlugin.analyze({ force: false });
|
|
229
275
|
```
|
|
230
276
|
|
|
231
|
-
You can also delete `.rpc-checksum` from the output directory to clear the
|
|
277
|
+
You can also delete `.rpc-checksum` from the output directory to clear the
|
|
278
|
+
cache.
|
|
232
279
|
|
|
233
|
-
> **Note:** The hash covers controller files matched by the `controllerPattern`
|
|
280
|
+
> **Note:** The hash covers controller files matched by the `controllerPattern`
|
|
281
|
+
> glob. If you only change a DTO/model file that lives outside that pattern, the
|
|
282
|
+
> cache won't invalidate automatically. Use `analyze()` or delete
|
|
283
|
+
> `.rpc-checksum` in that case.
|
|
234
284
|
|
|
235
285
|
## How It Works
|
|
236
286
|
|
|
@@ -270,21 +320,40 @@ export class ApiClient {
|
|
|
270
320
|
get users() {
|
|
271
321
|
return {
|
|
272
322
|
create: async <Result = User>(
|
|
273
|
-
options: RequestOptions<
|
|
323
|
+
options: RequestOptions<
|
|
324
|
+
{ name: string; email: string },
|
|
325
|
+
undefined,
|
|
326
|
+
undefined,
|
|
327
|
+
undefined
|
|
328
|
+
>,
|
|
274
329
|
) => {
|
|
275
|
-
return this.request<Result>(
|
|
330
|
+
return this.request<Result>("POST", `/api/v1/users/`, options);
|
|
276
331
|
},
|
|
277
332
|
list: async <Result = User[]>(
|
|
278
|
-
options?: RequestOptions<
|
|
333
|
+
options?: RequestOptions<
|
|
334
|
+
undefined,
|
|
335
|
+
{ page: number; limit: number },
|
|
336
|
+
undefined,
|
|
337
|
+
undefined
|
|
338
|
+
>,
|
|
279
339
|
) => {
|
|
280
|
-
return this.request<Result>(
|
|
340
|
+
return this.request<Result>("GET", `/api/v1/users/`, options);
|
|
281
341
|
},
|
|
282
342
|
getById: async <Result = User>(
|
|
283
|
-
options: RequestOptions<
|
|
343
|
+
options: RequestOptions<
|
|
344
|
+
undefined,
|
|
345
|
+
{ id: string },
|
|
346
|
+
undefined,
|
|
347
|
+
undefined
|
|
348
|
+
>,
|
|
284
349
|
) => {
|
|
285
|
-
return this.request<Result>(
|
|
286
|
-
|
|
287
|
-
|
|
350
|
+
return this.request<Result>(
|
|
351
|
+
"GET",
|
|
352
|
+
`/api/v1/users/:id`,
|
|
353
|
+
options,
|
|
354
|
+
);
|
|
355
|
+
},
|
|
356
|
+
};
|
|
288
357
|
}
|
|
289
358
|
}
|
|
290
359
|
|
|
@@ -293,24 +362,29 @@ export type RequestOptions<
|
|
|
293
362
|
TParams = undefined,
|
|
294
363
|
TQuery = undefined,
|
|
295
364
|
TBody = undefined,
|
|
296
|
-
THeaders = undefined
|
|
297
|
-
> =
|
|
298
|
-
(
|
|
299
|
-
(
|
|
300
|
-
(
|
|
365
|
+
THeaders = undefined,
|
|
366
|
+
> =
|
|
367
|
+
& (TParams extends undefined ? object : { params: TParams })
|
|
368
|
+
& (TQuery extends undefined ? object : { query: TQuery })
|
|
369
|
+
& (TBody extends undefined ? object : { body: TBody })
|
|
370
|
+
& (THeaders extends undefined ? object : { headers: THeaders });
|
|
301
371
|
```
|
|
302
372
|
|
|
303
373
|
## Plugin Lifecycle
|
|
304
374
|
|
|
305
|
-
The plugin automatically generates files when your HonestJS application starts
|
|
306
|
-
subsequent startups, the hash-based cache
|
|
375
|
+
The plugin automatically generates files when your HonestJS application starts
|
|
376
|
+
up (if `generateOnInit` is true). On subsequent startups, the hash-based cache
|
|
377
|
+
will skip regeneration if controller files haven't changed.
|
|
307
378
|
|
|
308
379
|
You can also manually trigger generation:
|
|
309
380
|
|
|
310
381
|
```typescript
|
|
311
|
-
const rpcPlugin = new RPCPlugin()
|
|
312
|
-
await rpcPlugin.analyze() // Force regeneration (bypasses cache)
|
|
313
|
-
await rpcPlugin.analyze(false) // Respect cache
|
|
382
|
+
const rpcPlugin = new RPCPlugin();
|
|
383
|
+
await rpcPlugin.analyze({ force: true }); // Force regeneration (bypasses cache)
|
|
384
|
+
await rpcPlugin.analyze({ force: false }); // Respect cache
|
|
385
|
+
|
|
386
|
+
// Analyze-only mode (no files generated, diagnostics still emitted)
|
|
387
|
+
await rpcPlugin.analyze({ force: true, dryRun: true });
|
|
314
388
|
```
|
|
315
389
|
|
|
316
390
|
## Advanced Usage
|
|
@@ -321,9 +395,9 @@ If your controllers follow a different file structure:
|
|
|
321
395
|
|
|
322
396
|
```typescript
|
|
323
397
|
new RPCPlugin({
|
|
324
|
-
controllerPattern:
|
|
325
|
-
outputDir:
|
|
326
|
-
})
|
|
398
|
+
controllerPattern: "src/controllers/**/*.controller.ts",
|
|
399
|
+
outputDir: "./src/generated/api",
|
|
400
|
+
});
|
|
327
401
|
```
|
|
328
402
|
|
|
329
403
|
### Manual Generation Control
|
|
@@ -332,11 +406,11 @@ Disable automatic generation and control when files are generated:
|
|
|
332
406
|
|
|
333
407
|
```typescript
|
|
334
408
|
const rpcPlugin = new RPCPlugin({
|
|
335
|
-
generateOnInit: false
|
|
336
|
-
})
|
|
409
|
+
generateOnInit: false,
|
|
410
|
+
});
|
|
337
411
|
|
|
338
412
|
// Later in your code
|
|
339
|
-
await rpcPlugin.analyze()
|
|
413
|
+
await rpcPlugin.analyze();
|
|
340
414
|
```
|
|
341
415
|
|
|
342
416
|
## Integration with HonestJS
|
|
@@ -346,32 +420,32 @@ await rpcPlugin.analyze()
|
|
|
346
420
|
Here's how your controllers should be structured for optimal RPC generation:
|
|
347
421
|
|
|
348
422
|
```typescript
|
|
349
|
-
import {
|
|
423
|
+
import { Body, Controller, Get, Param, Post, Query } from "honestjs";
|
|
350
424
|
|
|
351
425
|
interface CreateUserDto {
|
|
352
|
-
name: string
|
|
353
|
-
email: string
|
|
426
|
+
name: string;
|
|
427
|
+
email: string;
|
|
354
428
|
}
|
|
355
429
|
|
|
356
430
|
interface ListUsersQuery {
|
|
357
|
-
page?: number
|
|
358
|
-
limit?: number
|
|
431
|
+
page?: number;
|
|
432
|
+
limit?: number;
|
|
359
433
|
}
|
|
360
434
|
|
|
361
|
-
@Controller(
|
|
435
|
+
@Controller("/users")
|
|
362
436
|
export class UsersController {
|
|
363
|
-
@Post(
|
|
437
|
+
@Post("/")
|
|
364
438
|
async create(@Body() createUserDto: CreateUserDto): Promise<User> {
|
|
365
439
|
// Implementation
|
|
366
440
|
}
|
|
367
441
|
|
|
368
|
-
@Get(
|
|
442
|
+
@Get("/")
|
|
369
443
|
async list(@Query() query: ListUsersQuery): Promise<User[]> {
|
|
370
444
|
// Implementation
|
|
371
445
|
}
|
|
372
446
|
|
|
373
|
-
@Get(
|
|
374
|
-
async getById(@Param(
|
|
447
|
+
@Get("/:id")
|
|
448
|
+
async getById(@Param("id") id: string): Promise<User> {
|
|
375
449
|
// Implementation
|
|
376
450
|
}
|
|
377
451
|
}
|
|
@@ -382,13 +456,13 @@ export class UsersController {
|
|
|
382
456
|
Ensure your controllers are properly registered in modules:
|
|
383
457
|
|
|
384
458
|
```typescript
|
|
385
|
-
import { Module } from
|
|
386
|
-
import { UsersController } from
|
|
387
|
-
import { UsersService } from
|
|
459
|
+
import { Module } from "honestjs";
|
|
460
|
+
import { UsersController } from "./users.controller";
|
|
461
|
+
import { UsersService } from "./users.service";
|
|
388
462
|
|
|
389
463
|
@Module({
|
|
390
464
|
controllers: [UsersController],
|
|
391
|
-
|
|
465
|
+
services: [UsersService],
|
|
392
466
|
})
|
|
393
467
|
export class UsersModule {}
|
|
394
468
|
```
|
|
@@ -400,13 +474,13 @@ The generated client includes comprehensive error handling:
|
|
|
400
474
|
```typescript
|
|
401
475
|
try {
|
|
402
476
|
const user = await apiClient.users.create({
|
|
403
|
-
body: { name:
|
|
404
|
-
})
|
|
477
|
+
body: { name: "John", email: "john@example.com" },
|
|
478
|
+
});
|
|
405
479
|
} catch (error) {
|
|
406
480
|
if (error instanceof ApiError) {
|
|
407
|
-
console.error(`API Error ${error.statusCode}: ${error.message}`)
|
|
481
|
+
console.error(`API Error ${error.statusCode}: ${error.message}`);
|
|
408
482
|
} else {
|
|
409
|
-
console.error(
|
|
483
|
+
console.error("Unexpected error:", error);
|
|
410
484
|
}
|
|
411
485
|
}
|
|
412
486
|
```
|