@rexeus/typeweaver-server 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
@@ -113,8 +113,8 @@ export const userHandlers: ServerUserApiHandler = {
113
113
  };
114
114
  ```
115
115
 
116
- > Generated response classes (e.g. `GetUserSuccessResponse`) are also available for when you need
117
- > runtime type checks or `instanceof` discrimination in error handling.
116
+ > Generated response factory functions (e.g. `createGetUserSuccessResponse`) are also available for
117
+ > constructing typed responses with pre-set `type` and `statusCode` discriminators.
118
118
 
119
119
  ### Create the app
120
120
 
@@ -310,48 +310,67 @@ const app = new TypeweaverApp({
310
310
 
311
311
  Each router accepts `TypeweaverRouterOptions`:
312
312
 
313
- | Option | Type | Default | Description |
314
- | -------------------------- | ---------------------------- | ---------- | ---------------------------------- |
315
- | `requestHandlers` | `Server<Resource>ApiHandler` | _required_ | Handler methods for each operation |
316
- | `validateRequests` | `boolean` | `true` | Enable/disable request validation |
317
- | `handleValidationErrors` | `boolean \| function` | `true` | Handle validation errors |
318
- | `handleHttpResponseErrors` | `boolean \| function` | `true` | Handle thrown `HttpResponse` |
319
- | `handleUnknownErrors` | `boolean \| function` | `true` | Handle unexpected errors |
313
+ | Option | Type | Default | Description |
314
+ | -------------------------------- | ---------------------------- | ---------- | ---------------------------------- |
315
+ | `requestHandlers` | `Server<Resource>ApiHandler` | _required_ | Handler methods for each operation |
316
+ | `validateRequests` | `boolean` | `true` | Enable/disable request validation |
317
+ | `validateResponses` | `boolean` | `true` | Enable/disable response validation |
318
+ | `handleRequestValidationErrors` | `boolean \| function` | `true` | Handle request validation errors |
319
+ | `handleResponseValidationErrors` | `boolean \| function` | `true` | Handle response validation errors |
320
+ | `handleHttpResponseErrors` | `boolean \| function` | `true` | Handle thrown typed HTTP responses |
321
+ | `handleUnknownErrors` | `boolean \| function` | `true` | Handle unexpected errors |
320
322
 
321
323
  When set to `true`, error handlers use sensible defaults (400/500 responses). When set to `false`,
322
- errors fall through to the next handler in the chain. When set to a function, it receives the error
323
- and `ServerContext` and must return an `IHttpResponse`.
324
+ errors fall through to the next handler in the chain (except `handleResponseValidationErrors`, where
325
+ `false` means the invalid response is returned as-is — validation still runs for field stripping,
326
+ but invalid responses pass through unchanged). When set to a function, it receives the error and
327
+ `ServerContext` and must return an `IHttpResponse`. If a custom error handler throws, the framework
328
+ catches the exception and falls through gracefully to the next handler.
324
329
 
325
330
  ### 🚨 Error Handling
326
331
 
327
332
  #### Throwing errors in handlers
328
333
 
329
- All generated error response classes (e.g. `NotFoundErrorResponse`, `ValidationErrorResponse`)
330
- extend `HttpResponse`. Throw them in your handlers — the framework catches them automatically:
334
+ Throw any object matching `ITypedHttpResponse` (i.e. `{ type: string, statusCode: number, ... }`)
335
+ from your handlers — the framework catches it automatically and returns it as the response:
331
336
 
332
337
  ```ts
333
338
  import { HttpStatusCode } from "@rexeus/typeweaver-core";
334
- import { GetUserSuccessResponse, NotFoundErrorResponse } from "./generated";
335
339
 
336
340
  async handleGetUserRequest(request) {
337
341
  const user = await db.findUser(request.param.userId);
338
342
  if (!user) {
339
- throw new NotFoundErrorResponse({
343
+ // Plain objects work — anything with `type` and `statusCode` is recognized
344
+ throw {
345
+ type: "NotFoundError",
340
346
  statusCode: HttpStatusCode.NOT_FOUND,
341
347
  header: { "Content-Type": "application/json" },
342
348
  body: { message: "Resource not found", code: "NOT_FOUND_ERROR" },
343
- });
349
+ };
344
350
  }
345
- return new GetUserSuccessResponse({
351
+ return {
352
+ type: "GetUserSuccess",
346
353
  statusCode: HttpStatusCode.OK,
347
354
  header: { "Content-Type": "application/json" },
348
355
  body: user,
349
- });
356
+ };
350
357
  }
351
358
  ```
352
359
 
353
- When `handleHttpResponseErrors` is `true` (the default), thrown `HttpResponse` instances are
354
- returned as-is. No extra configuration needed.
360
+ Generated factory functions (e.g. `createNotFoundErrorResponse`) are a convenient shorthand they
361
+ set `type` and `statusCode` for you so you only pass `header` and `body`:
362
+
363
+ ```ts
364
+ import { createNotFoundErrorResponse } from "./generated";
365
+
366
+ throw createNotFoundErrorResponse({
367
+ header: { "Content-Type": "application/json" },
368
+ body: { message: "Resource not found", code: "NOT_FOUND_ERROR" },
369
+ });
370
+ ```
371
+
372
+ When `handleHttpResponseErrors` is `true` (the default), thrown typed HTTP responses
373
+ (`ITypedHttpResponse`) are returned as-is. No extra configuration needed.
355
374
 
356
375
  #### Custom error mapping
357
376
 
@@ -362,21 +381,21 @@ Use custom handler functions to transform errors into your own response shape.
362
381
  ```ts
363
382
  new UserRouter({
364
383
  requestHandlers: userHandlers,
365
- handleValidationErrors: (error, ctx) =>
366
- new ValidationErrorResponse({
367
- statusCode: HttpStatusCode.BAD_REQUEST,
368
- header: { "Content-Type": "application/json" },
369
- body: {
370
- code: "VALIDATION_ERROR",
371
- message: "Request is invalid",
372
- issues: {
373
- body: error.bodyIssues,
374
- query: error.queryIssues,
375
- param: error.pathParamIssues,
376
- header: error.headerIssues,
377
- },
384
+ handleRequestValidationErrors: (error, ctx) => ({
385
+ type: "ValidationError",
386
+ statusCode: HttpStatusCode.BAD_REQUEST,
387
+ header: { "Content-Type": "application/json" },
388
+ body: {
389
+ code: "VALIDATION_ERROR",
390
+ message: "Request is invalid",
391
+ issues: {
392
+ body: error.bodyIssues,
393
+ query: error.queryIssues,
394
+ param: error.pathParamIssues,
395
+ header: error.headerIssues,
378
396
  },
379
- }),
397
+ },
398
+ }),
380
399
  });
381
400
  ```
382
401
 
@@ -402,25 +421,27 @@ new UserRouter({
402
421
  requestHandlers: userHandlers,
403
422
  handleUnknownErrors: (error, ctx) => {
404
423
  logger.error("Unhandled error", { error, path: ctx.request.path });
405
- return new InternalServerErrorResponse({
424
+ return {
425
+ type: "InternalServerError",
406
426
  statusCode: HttpStatusCode.INTERNAL_SERVER_ERROR,
407
427
  header: { "Content-Type": "application/json" },
408
428
  body: { code: "INTERNAL_SERVER_ERROR", message: "Something went wrong" },
409
- });
429
+ };
410
430
  },
411
431
  });
412
432
  ```
413
433
 
414
434
  ### 📋 Error Responses
415
435
 
416
- | Status | Code | When |
417
- | ------ | ----------------------- | ------------------------------------------------------------- |
418
- | `400` | `BAD_REQUEST` | Malformed request body |
419
- | `400` | Validation issues | `handleValidationErrors: true` and request fails validation |
420
- | `404` | `NOT_FOUND` | No matching route |
421
- | `405` | `METHOD_NOT_ALLOWED` | Route exists but method not allowed (includes `Allow` header) |
422
- | `413` | `PAYLOAD_TOO_LARGE` | Request body exceeds `maxBodySize` |
423
- | `500` | `INTERNAL_SERVER_ERROR` | Unhandled error in handler |
436
+ | Status | Code | When |
437
+ | ------ | ----------------------- | -------------------------------------------------------------------- |
438
+ | `400` | `BAD_REQUEST` | Malformed request body |
439
+ | `400` | Validation issues | `handleRequestValidationErrors: true` and request fails validation |
440
+ | `404` | `NOT_FOUND` | No matching route |
441
+ | `405` | `METHOD_NOT_ALLOWED` | Route exists but method not allowed (includes `Allow` header) |
442
+ | `413` | `PAYLOAD_TOO_LARGE` | Request body exceeds `maxBodySize` |
443
+ | `500` | `INTERNAL_SERVER_ERROR` | `handleResponseValidationErrors: true` and response fails validation |
444
+ | `500` | `INTERNAL_SERVER_ERROR` | Unhandled error in handler |
424
445
 
425
446
  All error responses follow the shape: `{ code: string, message: string }`.
426
447
 
@@ -9,7 +9,10 @@ import type {
9
9
  HttpMethod,
10
10
  IHttpResponse,
11
11
  IRequestValidator,
12
+ IResponseValidator,
13
+ ITypedHttpResponse,
12
14
  RequestValidationError,
15
+ ResponseValidationError,
13
16
  } from "@rexeus/typeweaver-core";
14
17
  import type { RequestHandler } from "./RequestHandler";
15
18
  import type { ServerContext } from "./ServerContext";
@@ -30,7 +33,8 @@ export type RouteDefinition = {
30
33
  readonly operationId: string;
31
34
  readonly method: HttpMethod;
32
35
  readonly path: string;
33
- readonly validator: IRequestValidator;
36
+ readonly requestValidator: IRequestValidator;
37
+ readonly responseValidator: IResponseValidator;
34
38
  readonly handler: RequestHandler<any, any, any>;
35
39
  /** Reference to the router config for error handling. */
36
40
  readonly routerConfig: RouterErrorConfig;
@@ -41,28 +45,44 @@ export type RouteDefinition = {
41
45
  */
42
46
  export type RouterErrorConfig = {
43
47
  readonly validateRequests: boolean;
48
+ readonly validateResponses: boolean;
44
49
  readonly handleHttpResponseErrors: HttpResponseErrorHandler | boolean;
45
- readonly handleValidationErrors: ValidationErrorHandler | boolean;
50
+ readonly handleRequestValidationErrors:
51
+ | RequestValidationErrorHandler
52
+ | boolean;
53
+ readonly handleResponseValidationErrors:
54
+ | ResponseValidationErrorHandler
55
+ | boolean;
46
56
  readonly handleUnknownErrors: UnknownErrorHandler | boolean;
47
57
  };
48
58
 
49
59
  /**
50
60
  * Handles HTTP response errors thrown by request handlers.
51
- * The error parameter is an `HttpResponse` instance (thrown via `throw new HttpResponse(...)`).
61
+ * The error parameter is a typed HTTP response object (thrown via `throw { type, statusCode, ... }`).
52
62
  */
53
63
  export type HttpResponseErrorHandler = (
54
- error: IHttpResponse,
64
+ error: ITypedHttpResponse,
55
65
  ctx: ServerContext
56
66
  ) => Promise<IHttpResponse> | IHttpResponse;
57
67
 
58
68
  /**
59
69
  * Handles request validation errors.
60
70
  */
61
- export type ValidationErrorHandler = (
71
+ export type RequestValidationErrorHandler = (
62
72
  error: RequestValidationError,
63
73
  ctx: ServerContext
64
74
  ) => Promise<IHttpResponse> | IHttpResponse;
65
75
 
76
+ /**
77
+ * Handles response validation errors.
78
+ * Called when a handler returns a response that does not match the expected schema.
79
+ */
80
+ export type ResponseValidationErrorHandler = (
81
+ error: ResponseValidationError,
82
+ response: IHttpResponse,
83
+ ctx: ServerContext
84
+ ) => Promise<IHttpResponse> | IHttpResponse;
85
+
66
86
  /**
67
87
  * Handles any unknown errors not caught by other handlers.
68
88
  */
@@ -5,7 +5,10 @@
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 { IHttpResponse } from "@rexeus/typeweaver-core";
10
13
  import { BodyParseError, PayloadTooLargeError } from "./Errors";
11
14
  import { FetchApiAdapter } from "./FetchApiAdapter";
@@ -16,10 +19,11 @@ import type { Middleware } from "./Middleware";
16
19
  import type { RequestHandler } from "./RequestHandler";
17
20
  import type {
18
21
  HttpResponseErrorHandler,
22
+ ResponseValidationErrorHandler,
19
23
  RouteDefinition,
20
24
  RouteMatch,
21
25
  UnknownErrorHandler,
22
- ValidationErrorHandler,
26
+ RequestValidationErrorHandler,
23
27
  } from "./Router";
24
28
  import type { ServerContext } from "./ServerContext";
25
29
  import type { StateRequirementError, TypedMiddleware } from "./TypedMiddleware";
@@ -224,8 +228,15 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
224
228
  if (match) {
225
229
  const routeCtx = this.withPathParams(ctx, match.params);
226
230
  try {
227
- return await this.executeHandler(routeCtx, match.route);
231
+ const response = await this.executeHandler(routeCtx, match.route);
232
+ return await this.validateResponse(match.route, response, routeCtx);
228
233
  } catch (error) {
234
+ if (
235
+ isTypedHttpResponse(error) &&
236
+ match.route.routerConfig.validateResponses
237
+ ) {
238
+ return await this.validateResponse(match.route, error, routeCtx);
239
+ }
229
240
  return this.handleError(error, routeCtx, match.route);
230
241
  }
231
242
  }
@@ -261,44 +272,123 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
261
272
  route: RouteDefinition
262
273
  ): Promise<IHttpResponse> {
263
274
  const validatedRequest = route.routerConfig.validateRequests
264
- ? route.validator.validate(ctx.request)
275
+ ? route.requestValidator.validate(ctx.request)
265
276
  : ctx.request;
266
277
 
267
278
  return route.handler(validatedRequest, ctx);
268
279
  }
269
280
 
281
+ /**
282
+ * Validates a response against the operation's response validator.
283
+ *
284
+ * Behavior depends on configuration:
285
+ * - `validateResponses: false` → returns the original response unchanged.
286
+ * - `validateResponses: true` (default) → runs validation:
287
+ * - Valid response → returns the stripped response (extra fields removed).
288
+ * - Invalid response + handler configured → calls the handler safely.
289
+ * If the handler throws, falls back to the original response.
290
+ * - Invalid response + `handleResponseValidationErrors: false` → returns
291
+ * the original (invalid) response as-is.
292
+ *
293
+ * @param route - The route definition containing the response validator and config
294
+ * @param response - The response to validate
295
+ * @param ctx - The server context for the current request
296
+ * @returns The validated (and stripped) response, the handler's response, or the original
297
+ */
298
+ private async validateResponse(
299
+ route: RouteDefinition,
300
+ response: IHttpResponse,
301
+ ctx: ServerContext
302
+ ): Promise<IHttpResponse> {
303
+ if (!route.routerConfig.validateResponses) return response;
304
+
305
+ const result = route.responseValidator.safeValidate(response);
306
+
307
+ if (result.isValid) return result.data;
308
+
309
+ const handler = this.resolveErrorHandler<ResponseValidationErrorHandler>(
310
+ route.routerConfig.handleResponseValidationErrors,
311
+ TypeweaverApp.defaultResponseValidationHandler
312
+ );
313
+
314
+ if (handler) {
315
+ const handlerResponse = await this.safelyExecuteErrorHandler(() =>
316
+ handler(result.error, response, ctx)
317
+ );
318
+ if (handlerResponse) return handlerResponse;
319
+ }
320
+
321
+ return response;
322
+ }
323
+
324
+ /**
325
+ * Safely executes an error handler and returns null if it fails.
326
+ * This allows for graceful fallback to the next handler in the chain
327
+ * without crashing the request pipeline.
328
+ *
329
+ * If the handler throws, the error is reported via `safeOnError`
330
+ * and null is returned so the caller can fall through to the next handler.
331
+ *
332
+ * @param handlerFn - Function that executes the error handler
333
+ * @returns The handler's response if successful, null if the handler throws
334
+ */
335
+ private async safelyExecuteErrorHandler(
336
+ handlerFn: () => Promise<IHttpResponse> | IHttpResponse
337
+ ): Promise<IHttpResponse | null> {
338
+ try {
339
+ return await handlerFn();
340
+ } catch (error) {
341
+ this.safeOnError(error);
342
+ return null;
343
+ }
344
+ }
345
+
270
346
  /**
271
347
  * Handle errors using the route's configured error handlers.
272
- * Handler errors bubble up to the safety net in `fetch()`.
273
348
  */
274
- private handleError(
349
+ private async handleError(
275
350
  error: unknown,
276
351
  ctx: ServerContext,
277
352
  route: RouteDefinition
278
- ): IHttpResponse | Promise<IHttpResponse> {
353
+ ): Promise<IHttpResponse> {
279
354
  const config = route.routerConfig;
280
355
 
281
356
  if (error instanceof RequestValidationError) {
282
- const handler = this.resolveErrorHandler<ValidationErrorHandler>(
283
- config.handleValidationErrors,
284
- TypeweaverApp.defaultValidationHandler
357
+ const handler = this.resolveErrorHandler<RequestValidationErrorHandler>(
358
+ config.handleRequestValidationErrors,
359
+ TypeweaverApp.defaultRequestValidationHandler
285
360
  );
286
- if (handler) return handler(error, ctx);
361
+ if (handler) {
362
+ const response = await this.safelyExecuteErrorHandler(() =>
363
+ handler(error, ctx)
364
+ );
365
+ if (response) return response;
366
+ }
287
367
  }
288
368
 
289
- if (error instanceof HttpResponse) {
369
+ if (isTypedHttpResponse(error)) {
290
370
  const handler = this.resolveErrorHandler<HttpResponseErrorHandler>(
291
371
  config.handleHttpResponseErrors,
292
372
  TypeweaverApp.defaultHttpResponseHandler
293
373
  );
294
- if (handler) return handler(error, ctx);
374
+ if (handler) {
375
+ const response = await this.safelyExecuteErrorHandler(() =>
376
+ handler(error, ctx)
377
+ );
378
+ if (response) return response;
379
+ }
295
380
  }
296
381
 
297
382
  const handler = this.resolveErrorHandler<UnknownErrorHandler>(
298
383
  config.handleUnknownErrors,
299
384
  this.defaultUnknownHandler
300
385
  );
301
- if (handler) return handler(error, ctx);
386
+ if (handler) {
387
+ const response = await this.safelyExecuteErrorHandler(() =>
388
+ handler(error, ctx)
389
+ );
390
+ if (response) return response;
391
+ }
302
392
 
303
393
  throw error;
304
394
  }
@@ -339,30 +429,35 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
339
429
  return issues.map(({ message, path }) => ({ message, path }));
340
430
  }
341
431
 
342
- private static defaultValidationHandler: ValidationErrorHandler = (
343
- err
344
- ): IHttpResponse => {
345
- const issues: Record<string, unknown> = Object.create(null);
432
+ private static defaultRequestValidationHandler: RequestValidationErrorHandler =
433
+ (err): IHttpResponse => {
434
+ const issues: Record<string, unknown> = Object.create(null);
346
435
 
347
- const header = TypeweaverApp.sanitizeIssues(err.headerIssues);
348
- const body = TypeweaverApp.sanitizeIssues(err.bodyIssues);
349
- const query = TypeweaverApp.sanitizeIssues(err.queryIssues);
350
- const param = TypeweaverApp.sanitizeIssues(err.pathParamIssues);
436
+ const header = TypeweaverApp.sanitizeIssues(err.headerIssues);
437
+ const body = TypeweaverApp.sanitizeIssues(err.bodyIssues);
438
+ const query = TypeweaverApp.sanitizeIssues(err.queryIssues);
439
+ const param = TypeweaverApp.sanitizeIssues(err.pathParamIssues);
351
440
 
352
- if (header) issues.header = header;
353
- if (body) issues.body = body;
354
- if (query) issues.query = query;
355
- if (param) issues.param = param;
441
+ if (header) issues.header = header;
442
+ if (body) issues.body = body;
443
+ if (query) issues.query = query;
444
+ if (param) issues.param = param;
356
445
 
357
- return {
358
- statusCode: 400,
359
- body: {
360
- code: "VALIDATION_ERROR",
361
- message: err.message,
362
- issues,
363
- },
446
+ return {
447
+ statusCode: 400,
448
+ body: {
449
+ code: "VALIDATION_ERROR",
450
+ message: err.message,
451
+ issues,
452
+ },
453
+ };
364
454
  };
365
- };
455
+
456
+ private static defaultResponseValidationHandler: ResponseValidationErrorHandler =
457
+ (): IHttpResponse => ({
458
+ statusCode: 500,
459
+ body: TypeweaverApp.INTERNAL_SERVER_ERROR_BODY,
460
+ });
366
461
 
367
462
  private static defaultHttpResponseHandler: HttpResponseErrorHandler = (
368
463
  err
@@ -7,14 +7,19 @@
7
7
  * @generated by @rexeus/typeweaver
8
8
  */
9
9
 
10
- import type { HttpMethod, IRequestValidator } from "@rexeus/typeweaver-core";
10
+ import type {
11
+ HttpMethod,
12
+ IRequestValidator,
13
+ IResponseValidator,
14
+ } from "@rexeus/typeweaver-core";
11
15
  import type { RequestHandler } from "./RequestHandler";
12
16
  import type {
13
17
  HttpResponseErrorHandler,
18
+ ResponseValidationErrorHandler,
14
19
  RouteDefinition,
15
20
  RouterErrorConfig,
16
21
  UnknownErrorHandler,
17
- ValidationErrorHandler,
22
+ RequestValidationErrorHandler,
18
23
  } from "./Router";
19
24
 
20
25
  /**
@@ -39,22 +44,42 @@ export type TypeweaverRouterOptions<
39
44
  readonly validateRequests?: boolean;
40
45
 
41
46
  /**
42
- * Configure handling of HttpResponse errors thrown by handlers.
43
- * - `true`: Use default handler (returns the error as response)
44
- * - `false`: Disable this handler (errors fall through to the unknown error handler)
45
- * - `function`: Use custom error handler
47
+ * Enable response validation using generated validators.
48
+ * When true, responses are validated and stripped of extra fields before sending.
46
49
  * @default true
47
50
  */
48
- readonly handleHttpResponseErrors?: HttpResponseErrorHandler | boolean;
51
+ readonly validateResponses?: boolean;
49
52
 
50
53
  /**
51
54
  * Configure handling of request validation errors.
52
55
  * - `true`: Use default handler (400 with error details)
53
56
  * - `false`: Disable this handler (errors fall through to the unknown error handler)
57
+ * - `function`: Use custom request validation error handler
58
+ * @default true
59
+ */
60
+ readonly handleRequestValidationErrors?:
61
+ | RequestValidationErrorHandler
62
+ | boolean;
63
+
64
+ /**
65
+ * Configure handling of response validation errors.
66
+ * - `true`: Use default handler (500 Internal Server Error)
67
+ * - `false`: Disable response validation error handling (return response as-is)
68
+ * - `function`: Use custom response validation error handler
69
+ * @default true
70
+ */
71
+ readonly handleResponseValidationErrors?:
72
+ | ResponseValidationErrorHandler
73
+ | boolean;
74
+
75
+ /**
76
+ * Configure handling of HttpResponse errors thrown by handlers.
77
+ * - `true`: Use default handler (returns the error as response)
78
+ * - `false`: Disable this handler (errors fall through to the unknown error handler)
54
79
  * - `function`: Use custom error handler
55
80
  * @default true
56
81
  */
57
- readonly handleValidationErrors?: ValidationErrorHandler | boolean;
82
+ readonly handleHttpResponseErrors?: HttpResponseErrorHandler | boolean;
58
83
 
59
84
  /**
60
85
  * Configure handling of unknown errors.
@@ -91,8 +116,10 @@ export abstract class TypeweaverRouter<
91
116
  const {
92
117
  requestHandlers,
93
118
  validateRequests = true,
119
+ validateResponses = true,
94
120
  handleHttpResponseErrors = true,
95
- handleValidationErrors = true,
121
+ handleRequestValidationErrors = true,
122
+ handleResponseValidationErrors = true,
96
123
  handleUnknownErrors = true,
97
124
  } = options;
98
125
 
@@ -100,8 +127,10 @@ export abstract class TypeweaverRouter<
100
127
 
101
128
  this.errorConfig = {
102
129
  validateRequests,
130
+ validateResponses,
103
131
  handleHttpResponseErrors,
104
- handleValidationErrors,
132
+ handleRequestValidationErrors,
133
+ handleResponseValidationErrors,
105
134
  handleUnknownErrors,
106
135
  };
107
136
  }
@@ -113,20 +142,23 @@ export abstract class TypeweaverRouter<
113
142
  * @param method - HTTP method (GET, POST, PUT, DELETE, etc.)
114
143
  * @param path - Path pattern with `:param` placeholders
115
144
  * @param validator - Request validator for this operation
145
+ * @param responseValidator - Response validator for this operation
116
146
  * @param handler - Type-safe request handler
117
147
  */
118
148
  protected route(
119
149
  operationId: string,
120
150
  method: HttpMethod,
121
151
  path: string,
122
- validator: IRequestValidator,
152
+ requestValidator: IRequestValidator,
153
+ responseValidator: IResponseValidator,
123
154
  handler: RequestHandler<any, any, any>
124
155
  ): void {
125
156
  this.routes.push({
126
157
  operationId,
127
158
  method,
128
159
  path,
129
- validator,
160
+ requestValidator,
161
+ responseValidator,
130
162
  handler,
131
163
  routerConfig: this.errorConfig,
132
164
  });
package/dist/lib/index.ts CHANGED
@@ -15,9 +15,10 @@ export {
15
15
  export { HttpMethod } from "@rexeus/typeweaver-core";
16
16
  export type {
17
17
  HttpResponseErrorHandler,
18
+ RequestValidationErrorHandler,
19
+ ResponseValidationErrorHandler,
18
20
  RouteMetadata,
19
21
  UnknownErrorHandler,
20
- ValidationErrorHandler,
21
22
  } from "./Router";
22
23
  export type { ServerContext } from "./ServerContext";
23
24
  export type { RequestHandler } from "./RequestHandler";
@@ -11,6 +11,7 @@ import { HttpMethod, TypeweaverRouter, type RequestHandler, type TypeweaverRoute
11
11
  import type { I<%- operation.className %>Request } from "./<%- operation.className %>Request";
12
12
  import { <%- operation.className %>RequestValidator } from "./<%- operation.className %>RequestValidator";
13
13
  import type { <%- operation.className %>Response } from "./<%- operation.className %>Response";
14
+ import { <%- operation.className %>ResponseValidator } from "./<%- operation.className %>ResponseValidator";
14
15
  <% } %>
15
16
 
16
17
  export type Server<%- pascalCaseEntityName %>ApiHandler<
@@ -36,6 +37,7 @@ export class <%- pascalCaseEntityName %>Router<
36
37
  HttpMethod.<%- operation.method %>,
37
38
  '<%- operation.path %>',
38
39
  new <%- operation.className %>RequestValidator(),
40
+ new <%- operation.className %>ResponseValidator(),
39
41
  this.requestHandlers.<%- operation.handlerName %>.bind(this.requestHandlers)
40
42
  );
41
43
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-server",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Generates a lightweight, dependency-free server with built-in routing and middleware from your API definitions. Powered by Typeweaver.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -47,21 +47,21 @@
47
47
  },
48
48
  "homepage": "https://github.com/rexeus/typeweaver#readme",
49
49
  "peerDependencies": {
50
- "@rexeus/typeweaver-core": "^0.7.0",
51
- "@rexeus/typeweaver-gen": "^0.7.0"
50
+ "@rexeus/typeweaver-gen": "^0.8.0",
51
+ "@rexeus/typeweaver-core": "^0.8.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "get-port": "^7.1.0",
55
55
  "test-utils": "file:../test-utils",
56
56
  "tsx": "^4.21.0",
57
- "@rexeus/typeweaver-core": "^0.7.0",
58
- "@rexeus/typeweaver-gen": "^0.7.0"
57
+ "@rexeus/typeweaver-core": "^0.8.0",
58
+ "@rexeus/typeweaver-gen": "^0.8.0"
59
59
  },
60
60
  "dependencies": {
61
61
  "case": "^1.6.3"
62
62
  },
63
63
  "scripts": {
64
- "typecheck": "tsc --noEmit",
64
+ "typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
65
65
  "format": "oxfmt",
66
66
  "build": "tsdown && mkdir -p ./dist/templates ./dist/lib && cp -r ./src/templates/* ./dist/templates/ && cp -r ./src/lib/* ./dist/lib/ && cp ../../LICENSE ../../NOTICE ./dist/",
67
67
  "test": "vitest --run",