@rexeus/typeweaver-hono 0.7.0 → 0.8.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 CHANGED
@@ -51,28 +51,24 @@ Implement your handlers and mount the generated router in a Hono app.
51
51
  // api/user-handlers.ts
52
52
  import type { Context } from "hono";
53
53
  import { HttpStatusCode } from "@rexeus/typeweaver-core";
54
- import type { IGetUserRequest, GetUserResponse, UserNotFoundErrorResponse } from "./generated";
55
- import { GetUserSuccessResponse } from "./generated";
54
+ import type { HonoUserApiHandler, IGetUserRequest, GetUserResponse } from "./generated";
55
+ import { createUserNotFoundErrorResponse, createGetUserSuccessResponse } from "./generated";
56
56
 
57
57
  export class UserHandlers implements HonoUserApiHandler {
58
- async handleGetUserRequest(request: IGetUserRequest, context: Context): Promise<GetUserResponse> {
59
- // Symbolic database fetch
60
- const databaseResult = {} as any;
61
- if (!databaseResult) {
62
- // Will be properly handled by the generated router and returned as a 404 response
63
- return new UserNotFoundErrorResponse({
64
- statusCode: HttpStatusCode.NotFound,
65
- header: { "Content-Type": "application/json" },
66
- body: { message: "User not found" },
67
- });
68
- }
69
-
70
- return new GetUserSuccessResponse({
71
- statusCode: HttpStatusCode.OK,
58
+ async handleGetUserRequest(request: IGetUserRequest, context: Context): Promise<GetUserResponse> {
59
+ const user = await db.findUser(request.param.userId);
60
+ if (!user) {
61
+ return createUserNotFoundErrorResponse({
72
62
  header: { "Content-Type": "application/json" },
73
- body: { id: request.param.userId, name: "Jane", email: "jane@example.com" },
63
+ body: { message: "User not found" },
74
64
  });
75
- },
65
+ }
66
+
67
+ return createGetUserSuccessResponse({
68
+ header: { "Content-Type": "application/json" },
69
+ body: { id: request.param.userId, name: "Jane", email: "jane@example.com" },
70
+ });
71
+ }
76
72
  // Implement other operation handlers: handleCreateUserRequest, ...
77
73
  }
78
74
  ```
@@ -91,8 +87,10 @@ const userHandlers = new UserHandlers();
91
87
  const userRouter = new UserHono({
92
88
  requestHandlers: userHandlers,
93
89
  validateRequests: true, // default, validates requests
94
- handleValidationErrors: true, // default: returns 400 with issues
95
- handleHttpResponseErrors: true, // default: returns thrown HttpResponse as-is
90
+ validateResponses: true, // default, validates responses and strips extra fields
91
+ handleRequestValidationErrors: true, // default: returns 400 with issues
92
+ handleResponseValidationErrors: true, // default: returns 500
93
+ handleHttpResponseErrors: true, // default: returns thrown typed HTTP responses as-is
96
94
  handleUnknownErrors: true, // default: returns 500
97
95
  });
98
96
 
@@ -110,14 +108,27 @@ serve({ fetch: app.fetch, port: 3000 }, () => {
110
108
 
111
109
  - `requestHandlers`: object implementing the generated `Hono<ResourceName>ApiHandler` type
112
110
  - `validateRequests` (default: `true`): enable/disable request validation
113
- - `handleValidationErrors`: `true` | `false` | `(err, c) => IHttpResponse | Promise<IHttpResponse>`,
111
+ - `validateResponses` (default: `true`): enable/disable response validation. When enabled, responses
112
+ are validated against the operation's schema and extra body fields are stripped before sending.
113
+ - `handleRequestValidationErrors`: `true` | `false` |
114
+ `(err, c) => IHttpResponse | Promise<IHttpResponse>`
114
115
  - If `true` (default), returns `400 Bad Request` with validation issues in the body
115
116
  - If `false`, disables this handler (errors fall through to the unknown error handler)
116
117
  - If function, calls the function with the error and context, expects an `IHttpResponse` to
117
118
  return, so you can customize the response in the way you want
119
+ - `handleResponseValidationErrors`: `true` | `false` |
120
+ `(err, response, c) => IHttpResponse | Promise<IHttpResponse>`
121
+ - If `true` (default), returns `500 Internal Server Error`
122
+ - If `false`, disables response validation error handling — the invalid response is returned
123
+ as-is. Validation still runs (and strips extra fields on valid responses), but invalid responses
124
+ pass through unchanged. Useful when you want field stripping without blocking invalid responses.
125
+ - If function, calls the function with the `ResponseValidationError`, the original (invalid)
126
+ response, and the Hono context. The function should return an `IHttpResponse`. If the custom
127
+ handler throws, the original response is returned as a fallback.
118
128
  - `handleHttpResponseErrors`: `true` | `false` |
119
129
  `(err, c) => IHttpResponse | Promise<IHttpResponse>`
120
- - If `true` (default), returns thrown `HttpResponse` as-is, they will be sent as the response
130
+ - If `true` (default), returns thrown typed HTTP responses (`ITypedHttpResponse`) as-is, they will
131
+ be sent as the response
121
132
  - If `false`, disables this handler (errors fall through to the unknown error handler)
122
133
  - If function, calls the function with the error and context, expects an `IHttpResponse` to
123
134
  return, so you can customize the response in the way you want
@@ -5,11 +5,17 @@
5
5
  * @generated by @rexeus/typeweaver
6
6
  */
7
7
 
8
- import { HttpResponse, RequestValidationError } from "@rexeus/typeweaver-core";
8
+ import {
9
+ isTypedHttpResponse,
10
+ RequestValidationError,
11
+ } from "@rexeus/typeweaver-core";
9
12
  import type {
10
13
  IHttpRequest,
11
14
  IHttpResponse,
12
15
  IRequestValidator,
16
+ IResponseValidator,
17
+ ITypedHttpResponse,
18
+ ResponseValidationError,
13
19
  } from "@rexeus/typeweaver-core";
14
20
  import { Hono } from "hono";
15
21
  import { HonoAdapter } from "./HonoAdapter";
@@ -25,7 +31,7 @@ import type { BlankEnv, BlankSchema, Env, Schema } from "hono/types";
25
31
  * @returns The HTTP response to send to the client
26
32
  */
27
33
  export type HonoHttpResponseErrorHandler = (
28
- error: HttpResponse,
34
+ error: ITypedHttpResponse,
29
35
  context: Context
30
36
  ) => Promise<IHttpResponse> | IHttpResponse;
31
37
 
@@ -35,7 +41,7 @@ export type HonoHttpResponseErrorHandler = (
35
41
  * @param context - The Hono context for the current request
36
42
  * @returns The HTTP response to send to the client
37
43
  */
38
- export type HonoValidationErrorHandler = (
44
+ export type HonoRequestValidationErrorHandler = (
39
45
  error: RequestValidationError,
40
46
  context: Context
41
47
  ) => Promise<IHttpResponse> | IHttpResponse;
@@ -51,6 +57,20 @@ export type HonoUnknownErrorHandler = (
51
57
  context: Context
52
58
  ) => Promise<IHttpResponse> | IHttpResponse;
53
59
 
60
+ /**
61
+ * Handles response validation errors.
62
+ * Called when a handler returns a response that does not match the expected schema.
63
+ * @param error - The response validation error with schema mismatch details
64
+ * @param response - The original (invalid) response from the handler
65
+ * @param context - The Hono context for the current request
66
+ * @returns The HTTP response to send to the client (typically a 500)
67
+ */
68
+ export type HonoResponseValidationErrorHandler = (
69
+ error: ResponseValidationError,
70
+ response: IHttpResponse,
71
+ context: Context
72
+ ) => Promise<IHttpResponse> | IHttpResponse;
73
+
54
74
  /**
55
75
  * Configuration options for TypeweaverHono routers.
56
76
  * @template RequestHandlers - Type containing all request handler methods
@@ -64,32 +84,52 @@ export type TypeweaverHonoOptions<
64
84
  * Request handler methods for each operation.
65
85
  * Each handler receives a request (validated if `validateRequests` is true) and Hono context.
66
86
  */
67
- requestHandlers: RequestHandlers;
87
+ readonly requestHandlers: RequestHandlers;
68
88
 
69
89
  /**
70
90
  * Enable request validation using generated validators.
71
91
  * When false, requests are passed through without validation.
72
92
  * @default true
73
93
  */
74
- validateRequests?: boolean;
94
+ readonly validateRequests?: boolean;
75
95
 
76
96
  /**
77
- * Configure handling of HttpResponse errors thrown by handlers.
78
- * - `true`: Use default handler (returns the error as-is)
79
- * - `false`: Let errors bubble up to Hono
80
- * - `function`: Use custom error handler
97
+ * Enable response validation using generated validators.
98
+ * When true, responses are validated and stripped of extra fields before sending.
81
99
  * @default true
82
100
  */
83
- handleHttpResponseErrors?: HonoHttpResponseErrorHandler | boolean;
101
+ readonly validateResponses?: boolean;
84
102
 
85
103
  /**
86
104
  * Configure handling of request validation errors.
87
105
  * - `true`: Use default handler (400 with error details)
88
106
  * - `false`: Let errors bubble up to Hono
107
+ * - `function`: Use custom request validation error handler
108
+ * @default true
109
+ */
110
+ readonly handleRequestValidationErrors?:
111
+ | HonoRequestValidationErrorHandler
112
+ | boolean;
113
+
114
+ /**
115
+ * Configure handling of response validation errors.
116
+ * - `true`: Use default handler (500 Internal Server Error)
117
+ * - `false`: Disable response validation error handling (return response as-is)
118
+ * - `function`: Use custom response validation error handler
119
+ * @default true
120
+ */
121
+ readonly handleResponseValidationErrors?:
122
+ | HonoResponseValidationErrorHandler
123
+ | boolean;
124
+
125
+ /**
126
+ * Configure handling of HttpResponse errors thrown by handlers.
127
+ * - `true`: Use default handler (returns the error as-is)
128
+ * - `false`: Let errors bubble up to Hono
89
129
  * - `function`: Use custom error handler
90
130
  * @default true
91
131
  */
92
- handleValidationErrors?: HonoValidationErrorHandler | boolean;
132
+ readonly handleHttpResponseErrors?: HonoHttpResponseErrorHandler | boolean;
93
133
 
94
134
  /**
95
135
  * Configure handling of unknown errors.
@@ -98,7 +138,7 @@ export type TypeweaverHonoOptions<
98
138
  * - `function`: Use custom error handler
99
139
  * @default true
100
140
  */
101
- handleUnknownErrors?: HonoUnknownErrorHandler | boolean;
141
+ readonly handleUnknownErrors?: HonoUnknownErrorHandler | boolean;
102
142
  };
103
143
 
104
144
  /**
@@ -134,11 +174,15 @@ export abstract class TypeweaverHono<
134
174
  * Resolved configuration for validation and error handling.
135
175
  */
136
176
  private readonly config: {
137
- validateRequests: boolean;
138
- errorHandlers: {
139
- validation: HonoValidationErrorHandler | undefined;
140
- httpResponse: HonoHttpResponseErrorHandler | undefined;
141
- unknown: HonoUnknownErrorHandler | undefined;
177
+ readonly validateRequests: boolean;
178
+ readonly validateResponses: boolean;
179
+ readonly errorHandlers: {
180
+ readonly requestValidation: HonoRequestValidationErrorHandler | undefined;
181
+ readonly responseValidation:
182
+ | HonoResponseValidationErrorHandler
183
+ | undefined;
184
+ readonly httpResponse: HonoHttpResponseErrorHandler | undefined;
185
+ readonly unknown: HonoUnknownErrorHandler | undefined;
142
186
  };
143
187
  };
144
188
 
@@ -146,7 +190,7 @@ export abstract class TypeweaverHono<
146
190
  * Default error handlers used when custom handlers are not provided.
147
191
  */
148
192
  private readonly defaultHandlers = {
149
- validation: (error: RequestValidationError): IHttpResponse => ({
193
+ requestValidation: (error: RequestValidationError): IHttpResponse => ({
150
194
  statusCode: 400,
151
195
  body: {
152
196
  code: "VALIDATION_ERROR",
@@ -160,13 +204,21 @@ export abstract class TypeweaverHono<
160
204
  },
161
205
  }),
162
206
 
163
- httpResponse: (error: HttpResponse): IHttpResponse => error,
207
+ responseValidation: (): IHttpResponse => ({
208
+ statusCode: 500,
209
+ body: {
210
+ code: "INTERNAL_SERVER_ERROR",
211
+ message: "An unexpected error occurred",
212
+ },
213
+ }),
214
+
215
+ httpResponse: (error: ITypedHttpResponse): IHttpResponse => error,
164
216
 
165
217
  unknown: (): IHttpResponse => ({
166
218
  statusCode: 500,
167
219
  body: {
168
220
  code: "INTERNAL_SERVER_ERROR",
169
- message: "An unexpected error occurred.",
221
+ message: "An unexpected error occurred",
170
222
  },
171
223
  }),
172
224
  };
@@ -178,15 +230,17 @@ export abstract class TypeweaverHono<
178
230
  * @param options.requestHandlers - Object containing all request handler methods
179
231
  * @param options.validateRequests - Whether to validate requests (default: true)
180
232
  * @param options.handleHttpResponseErrors - Handler or boolean for HTTP errors (default: true)
181
- * @param options.handleValidationErrors - Handler or boolean for validation errors (default: true)
233
+ * @param options.handleRequestValidationErrors - Handler or boolean for request validation errors (default: true)
182
234
  * @param options.handleUnknownErrors - Handler or boolean for unknown errors (default: true)
183
235
  */
184
236
  public constructor(options: TypeweaverHonoOptions<RequestHandlers, HonoEnv>) {
185
237
  const {
186
238
  requestHandlers,
187
239
  validateRequests = true,
240
+ validateResponses = true,
188
241
  handleHttpResponseErrors,
189
- handleValidationErrors,
242
+ handleRequestValidationErrors,
243
+ handleResponseValidationErrors,
190
244
  handleUnknownErrors,
191
245
  ...honoOptions
192
246
  } = options;
@@ -198,9 +252,15 @@ export abstract class TypeweaverHono<
198
252
  // Resolve configuration
199
253
  this.config = {
200
254
  validateRequests,
255
+ validateResponses,
201
256
  errorHandlers: {
202
- validation: this.resolveErrorHandler(handleValidationErrors, error =>
203
- this.defaultHandlers.validation(error)
257
+ requestValidation: this.resolveErrorHandler(
258
+ handleRequestValidationErrors,
259
+ error => this.defaultHandlers.requestValidation(error)
260
+ ),
261
+ responseValidation: this.resolveErrorHandler(
262
+ handleResponseValidationErrors,
263
+ (_error, _response) => this.defaultHandlers.responseValidation()
204
264
  ),
205
265
  httpResponse: this.resolveErrorHandler(
206
266
  handleHttpResponseErrors,
@@ -239,24 +299,29 @@ export abstract class TypeweaverHono<
239
299
  * Processes errors in order: validation, HTTP response, unknown.
240
300
  */
241
301
  protected registerErrorHandler(): void {
242
- this.onError(this.handleError.bind(this));
302
+ this.onError(async (error, context) =>
303
+ this.adapter.toResponse(await this.handleError(error, context))
304
+ );
243
305
  }
244
306
 
245
307
  /**
246
308
  * Safely executes an error handler and returns null if it fails.
247
- * This allows for graceful fallback to the next handler in the chain.
309
+ * This allows for graceful fallback to the next handler in the chain
310
+ * without crashing the request pipeline.
248
311
  *
249
312
  * @param handlerFn - Function that executes the error handler
250
- * @returns Response if successful, null if handler throws
313
+ * @returns The handler's response if successful, null if the handler throws
251
314
  */
252
- private async safelyExecuteHandler(
315
+ private async safelyExecuteErrorHandler(
253
316
  handlerFn: () => Promise<IHttpResponse> | IHttpResponse
254
- ): Promise<Response | null> {
317
+ ): Promise<IHttpResponse | null> {
255
318
  try {
256
- const response = await handlerFn();
257
- return this.adapter.toResponse(response);
258
- } catch {
259
- // Handler execution failed, return null to continue to next handler
319
+ return await handlerFn();
320
+ } catch (error) {
321
+ console.error(
322
+ "TypeweaverHono: error handler threw while handling error",
323
+ error
324
+ );
260
325
  return null;
261
326
  }
262
327
  }
@@ -264,24 +329,21 @@ export abstract class TypeweaverHono<
264
329
  protected async handleError(
265
330
  error: unknown,
266
331
  context: Context
267
- ): Promise<Response> {
332
+ ): Promise<IHttpResponse> {
268
333
  // Handle validation errors
269
334
  if (
270
335
  error instanceof RequestValidationError &&
271
- this.config.errorHandlers.validation
336
+ this.config.errorHandlers.requestValidation
272
337
  ) {
273
- const response = await this.safelyExecuteHandler(() =>
274
- this.config.errorHandlers.validation!(error, context)
338
+ const response = await this.safelyExecuteErrorHandler(() =>
339
+ this.config.errorHandlers.requestValidation!(error, context)
275
340
  );
276
341
  if (response) return response;
277
342
  }
278
343
 
279
344
  // Handle HTTP response errors
280
- if (
281
- error instanceof HttpResponse &&
282
- this.config.errorHandlers.httpResponse
283
- ) {
284
- const response = await this.safelyExecuteHandler(() =>
345
+ if (isTypedHttpResponse(error) && this.config.errorHandlers.httpResponse) {
346
+ const response = await this.safelyExecuteErrorHandler(() =>
285
347
  this.config.errorHandlers.httpResponse!(error, context)
286
348
  );
287
349
  if (response) return response;
@@ -289,7 +351,7 @@ export abstract class TypeweaverHono<
289
351
 
290
352
  // Handle unknown errors
291
353
  if (this.config.errorHandlers.unknown) {
292
- const response = await this.safelyExecuteHandler(() =>
354
+ const response = await this.safelyExecuteErrorHandler(() =>
293
355
  this.config.errorHandlers.unknown!(error, context)
294
356
  );
295
357
  if (response) return response;
@@ -304,7 +366,8 @@ export abstract class TypeweaverHono<
304
366
  *
305
367
  * @param context - Hono context for the current request
306
368
  * @param operationId - Unique operation identifier from the API definition
307
- * @param validator - Request validator for the specific operation
369
+ * @param requestValidator - Request validator for the specific operation
370
+ * @param responseValidator - Response validator for the specific operation
308
371
  * @param handler - Type-safe request handler function
309
372
  * @returns Hono-compatible Response object
310
373
  */
@@ -314,7 +377,8 @@ export abstract class TypeweaverHono<
314
377
  >(
315
378
  context: Context,
316
379
  operationId: string,
317
- validator: IRequestValidator,
380
+ requestValidator: IRequestValidator,
381
+ responseValidator: IResponseValidator,
318
382
  handler: HonoRequestHandler<TRequest, TResponse>
319
383
  ): Promise<Response> {
320
384
  try {
@@ -322,15 +386,67 @@ export abstract class TypeweaverHono<
322
386
 
323
387
  const httpRequest = await this.adapter.toRequest(context);
324
388
 
325
- // Conditionally validate
326
389
  const validatedRequest = this.config.validateRequests
327
- ? (validator.validate(httpRequest) as TRequest)
390
+ ? (requestValidator.validate(httpRequest) as TRequest)
328
391
  : (httpRequest as TRequest);
329
392
 
330
393
  const httpResponse = await handler(validatedRequest, context);
331
- return this.adapter.toResponse(httpResponse);
394
+ return this.adapter.toResponse(
395
+ await this.validateResponse(responseValidator, httpResponse, context)
396
+ );
332
397
  } catch (error) {
333
- return this.handleError(error, context);
398
+ if (isTypedHttpResponse(error) && this.config.validateResponses) {
399
+ const validated = await this.validateResponse(
400
+ responseValidator,
401
+ error,
402
+ context
403
+ );
404
+ return this.adapter.toResponse(validated);
405
+ }
406
+ return this.adapter.toResponse(await this.handleError(error, context));
334
407
  }
335
408
  }
409
+
410
+ /**
411
+ * Validates a response against the operation's response validator.
412
+ *
413
+ * Behavior depends on configuration:
414
+ * - `validateResponses: false` → returns the original response unchanged.
415
+ * - `validateResponses: true` (default) → runs validation:
416
+ * - Valid response → returns the stripped response (extra fields removed).
417
+ * - Invalid response + handler configured → calls the handler safely.
418
+ * If the handler throws, falls back to the original response.
419
+ * - Invalid response + `handleResponseValidationErrors: false` → returns
420
+ * the original (invalid) response as-is.
421
+ *
422
+ * @param responseValidator - The response validator for the operation
423
+ * @param response - The response to validate
424
+ * @param context - The Hono context for the current request
425
+ * @returns The validated (and stripped) response, the handler's response, or the original
426
+ */
427
+ private async validateResponse(
428
+ responseValidator: IResponseValidator,
429
+ response: IHttpResponse,
430
+ context: Context
431
+ ): Promise<IHttpResponse> {
432
+ if (!this.config.validateResponses) return response;
433
+
434
+ const result = responseValidator.safeValidate(response);
435
+
436
+ if (result.isValid) return result.data;
437
+
438
+ if (this.config.errorHandlers.responseValidation) {
439
+ const handlerResponse = await this.safelyExecuteErrorHandler(() =>
440
+ this.config.errorHandlers.responseValidation!(
441
+ result.error,
442
+ response,
443
+ context
444
+ )
445
+ );
446
+
447
+ if (handlerResponse) return handlerResponse;
448
+ }
449
+
450
+ return response;
451
+ }
336
452
  }
@@ -12,6 +12,7 @@ import { TypeweaverHono, type HonoRequestHandler, type TypeweaverHonoOptions } f
12
12
  import type { I<%- operation.className %>Request } from "./<%- operation.className %>Request";
13
13
  import { <%- operation.className %>RequestValidator } from "./<%- operation.className %>RequestValidator";
14
14
  import type { <%- operation.className %>Response } from "./<%- operation.className %>Response";
15
+ import { <%- operation.className %>ResponseValidator } from "./<%- operation.className %>ResponseValidator";
15
16
  <% } %>
16
17
 
17
18
  export type Hono<%- pascalCaseEntityName %>ApiHandler = {
@@ -33,6 +34,7 @@ export class <%- pascalCaseEntityName %>Hono extends TypeweaverHono<Hono<%- pasc
33
34
  context,
34
35
  '<%- operation.operationId %>',
35
36
  new <%- operation.className %>RequestValidator(),
37
+ new <%- operation.className %>ResponseValidator(),
36
38
  this.requestHandlers.<%- operation.handlerName %>.bind(this.requestHandlers)
37
39
  ));
38
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-hono",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Generates Hono routers and handlers straight from your API definitions. Powered by Typeweaver 🧵✨",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -47,20 +47,20 @@
47
47
  "homepage": "https://github.com/rexeus/typeweaver#readme",
48
48
  "peerDependencies": {
49
49
  "hono": "^4.11.0",
50
- "@rexeus/typeweaver-core": "^0.7.0",
51
- "@rexeus/typeweaver-gen": "^0.7.0"
50
+ "@rexeus/typeweaver-core": "^0.8.0",
51
+ "@rexeus/typeweaver-gen": "^0.8.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "hono": "^4.11.3",
55
55
  "test-utils": "file:../test-utils",
56
- "@rexeus/typeweaver-core": "^0.7.0",
57
- "@rexeus/typeweaver-gen": "^0.7.0"
56
+ "@rexeus/typeweaver-core": "^0.8.0",
57
+ "@rexeus/typeweaver-gen": "^0.8.0"
58
58
  },
59
59
  "dependencies": {
60
60
  "case": "^1.6.3"
61
61
  },
62
62
  "scripts": {
63
- "typecheck": "tsc --noEmit",
63
+ "typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
64
64
  "format": "oxfmt",
65
65
  "build": "tsdown && mkdir -p ./dist/templates ./dist/lib && cp -r ./src/templates/* ./dist/templates/ && cp -r ./src/lib/* ./dist/lib/ && cp ../../LICENSE ../../NOTICE ./dist/",
66
66
  "test": "vitest --run",