@rexeus/typeweaver-server 0.6.5 → 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
 
package/dist/index.cjs CHANGED
@@ -66,8 +66,10 @@ var RouterGenerator = class {
66
66
  context.writeFile(relativePath, content);
67
67
  }
68
68
  static createOperationData(resource) {
69
- const className = case$1.default.pascal(resource.definition.operationId);
69
+ const operationId = resource.definition.operationId;
70
+ const className = case$1.default.pascal(operationId);
70
71
  return {
72
+ operationId,
71
73
  className,
72
74
  handlerName: `handle${className}Request`,
73
75
  method: resource.definition.method,
package/dist/index.mjs CHANGED
@@ -37,8 +37,10 @@ var RouterGenerator = class {
37
37
  context.writeFile(relativePath, content);
38
38
  }
39
39
  static createOperationData(resource) {
40
- const className = Case.pascal(resource.definition.operationId);
40
+ const operationId = resource.definition.operationId;
41
+ const className = Case.pascal(operationId);
41
42
  return {
43
+ operationId,
42
44
  className,
43
45
  handlerName: `handle${className}Request`,
44
46
  method: resource.definition.method,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/RouterGenerator.ts","../src/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { HttpMethod } from \"@rexeus/typeweaver-core\";\nimport { Path } from \"@rexeus/typeweaver-gen\";\nimport type {\n GeneratorContext,\n OperationResource,\n} from \"@rexeus/typeweaver-gen\";\nimport Case from \"case\";\n\ntype OperationData = {\n readonly className: string;\n readonly handlerName: string;\n readonly method: string;\n readonly path: string;\n};\n\n/**\n * Generates TypeweaverRouter subclasses from API definitions.\n *\n * For each resource (e.g., `Todo`, `Account`), produces a `<ResourceName>Router.ts`\n * file that extends `TypeweaverRouter` and registers all operations as routes.\n */\nexport class RouterGenerator {\n /**\n * Generates router files for all resources in the given context.\n *\n * @param context - The generator context containing resources, templates, and output configuration\n */\n public static generate(context: GeneratorContext): void {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n const templateFile = path.join(moduleDir, \"templates\", \"Router.ejs\");\n\n for (const [entityName, entityResource] of Object.entries(\n context.resources.entityResources\n )) {\n this.writeRouter(\n entityName,\n templateFile,\n entityResource.operations,\n context\n );\n }\n }\n\n private static writeRouter(\n entityName: string,\n templateFile: string,\n operationResources: OperationResource[],\n context: GeneratorContext\n ): void {\n const pascalCaseEntityName = Case.pascal(entityName);\n const outputDir = path.join(context.outputDir, entityName);\n const outputPath = path.join(outputDir, `${pascalCaseEntityName}Router.ts`);\n\n const operations = operationResources\n .filter(resource => resource.definition.method !== HttpMethod.HEAD)\n .map(resource => this.createOperationData(resource))\n .sort((a, b) => this.compareRoutes(a, b));\n\n const content = context.renderTemplate(templateFile, {\n coreDir: Path.relative(outputDir, context.outputDir),\n entityName,\n pascalCaseEntityName,\n operations,\n });\n\n const relativePath = path.relative(context.outputDir, outputPath);\n context.writeFile(relativePath, content);\n }\n\n private static createOperationData(\n resource: OperationResource\n ): OperationData {\n const className = Case.pascal(resource.definition.operationId);\n\n return {\n className,\n handlerName: `handle${className}Request`,\n method: resource.definition.method,\n path: resource.definition.path,\n };\n }\n\n private static compareRoutes(a: OperationData, b: OperationData): number {\n const aSegments = a.path.split(\"/\").filter(s => s);\n const bSegments = b.path.split(\"/\").filter(s => s);\n\n // 1. Compare by depth first (shallow to deep)\n if (aSegments.length !== bSegments.length) {\n return aSegments.length - bSegments.length;\n }\n\n // 2. Compare segment by segment\n for (let i = 0; i < aSegments.length; i++) {\n const aSegment = aSegments[i]!;\n const bSegment = bSegments[i]!;\n\n const aIsParam = aSegment.startsWith(\":\");\n const bIsParam = bSegment.startsWith(\":\");\n\n // Static segments before parameters\n if (aIsParam !== bIsParam) {\n return aIsParam ? 1 : -1;\n }\n\n // Within same type, alphabetical order\n if (aSegment !== bSegment) {\n return aSegment.localeCompare(bSegment);\n }\n }\n\n // 3. Same path = sort by HTTP method priority\n return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);\n }\n\n private static readonly METHOD_PRIORITY: Record<string, number> = {\n GET: 1,\n POST: 2,\n PUT: 3,\n PATCH: 4,\n DELETE: 5,\n OPTIONS: 6,\n HEAD: 7,\n };\n\n private static getMethodPriority(method: string): number {\n return this.METHOD_PRIORITY[method] ?? 999;\n }\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { BasePlugin } from \"@rexeus/typeweaver-gen\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\nimport { RouterGenerator } from \"./RouterGenerator\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * Typeweaver plugin that generates a lightweight, dependency-free server\n * with built-in routing and middleware support.\n *\n * Copies the runtime library files (`TypeweaverApp`, `TypeweaverRouter`, `Router`,\n * `Middleware`, etc.) and generates typed router classes for each resource.\n */\nexport default class ServerPlugin extends BasePlugin {\n public name = \"server\";\n\n /**\n * Generates the server runtime and typed routers for all resources.\n *\n * @param context - The generator context\n */\n public override generate(context: GeneratorContext): void {\n const libSourceDir = path.join(moduleDir, \"lib\");\n this.copyLibFiles(context, libSourceDir, this.name);\n\n RouterGenerator.generate(context);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,IAAa,kBAAb,MAA6B;;;;;;CAM3B,OAAc,SAAS,SAAiC;EACtD,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;EAC9D,MAAM,eAAe,KAAK,KAAK,WAAW,aAAa,aAAa;AAEpE,OAAK,MAAM,CAAC,YAAY,mBAAmB,OAAO,QAChD,QAAQ,UAAU,gBACnB,CACC,MAAK,YACH,YACA,cACA,eAAe,YACf,QACD;;CAIL,OAAe,YACb,YACA,cACA,oBACA,SACM;EACN,MAAM,uBAAuB,KAAK,OAAO,WAAW;EACpD,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW,WAAW;EAC1D,MAAM,aAAa,KAAK,KAAK,WAAW,GAAG,qBAAqB,WAAW;EAE3E,MAAM,aAAa,mBAChB,QAAO,aAAY,SAAS,WAAW,WAAW,WAAW,KAAK,CAClE,KAAI,aAAY,KAAK,oBAAoB,SAAS,CAAC,CACnD,MAAM,GAAG,MAAM,KAAK,cAAc,GAAG,EAAE,CAAC;EAE3C,MAAM,UAAU,QAAQ,eAAe,cAAc;GACnD,SAAS,KAAK,SAAS,WAAW,QAAQ,UAAU;GACpD;GACA;GACA;GACD,CAAC;EAEF,MAAM,eAAe,KAAK,SAAS,QAAQ,WAAW,WAAW;AACjE,UAAQ,UAAU,cAAc,QAAQ;;CAG1C,OAAe,oBACb,UACe;EACf,MAAM,YAAY,KAAK,OAAO,SAAS,WAAW,YAAY;AAE9D,SAAO;GACL;GACA,aAAa,SAAS,UAAU;GAChC,QAAQ,SAAS,WAAW;GAC5B,MAAM,SAAS,WAAW;GAC3B;;CAGH,OAAe,cAAc,GAAkB,GAA0B;EACvE,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;EAClD,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;AAGlD,MAAI,UAAU,WAAW,UAAU,OACjC,QAAO,UAAU,SAAS,UAAU;AAItC,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;GACzC,MAAM,WAAW,UAAU;GAC3B,MAAM,WAAW,UAAU;GAE3B,MAAM,WAAW,SAAS,WAAW,IAAI;AAIzC,OAAI,aAHa,SAAS,WAAW,IAAI,CAIvC,QAAO,WAAW,IAAI;AAIxB,OAAI,aAAa,SACf,QAAO,SAAS,cAAc,SAAS;;AAK3C,SAAO,KAAK,kBAAkB,EAAE,OAAO,GAAG,KAAK,kBAAkB,EAAE,OAAO;;CAG5E,OAAwB,kBAA0C;EAChE,KAAK;EACL,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;EACR,SAAS;EACT,MAAM;EACP;CAED,OAAe,kBAAkB,QAAwB;AACvD,SAAO,KAAK,gBAAgB,WAAW;;;;;;ACzH3C,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;;;;;;;;AAS9D,IAAqB,eAArB,cAA0C,WAAW;CACnD,AAAO,OAAO;;;;;;CAOd,AAAgB,SAAS,SAAiC;EACxD,MAAM,eAAe,KAAK,KAAK,WAAW,MAAM;AAChD,OAAK,aAAa,SAAS,cAAc,KAAK,KAAK;AAEnD,kBAAgB,SAAS,QAAQ"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/RouterGenerator.ts","../src/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { HttpMethod } from \"@rexeus/typeweaver-core\";\nimport { Path } from \"@rexeus/typeweaver-gen\";\nimport type {\n GeneratorContext,\n OperationResource,\n} from \"@rexeus/typeweaver-gen\";\nimport Case from \"case\";\n\ntype OperationData = {\n readonly operationId: string;\n readonly className: string;\n readonly handlerName: string;\n readonly method: string;\n readonly path: string;\n};\n\n/**\n * Generates TypeweaverRouter subclasses from API definitions.\n *\n * For each resource (e.g., `Todo`, `Account`), produces a `<ResourceName>Router.ts`\n * file that extends `TypeweaverRouter` and registers all operations as routes.\n */\nexport class RouterGenerator {\n /**\n * Generates router files for all resources in the given context.\n *\n * @param context - The generator context containing resources, templates, and output configuration\n */\n public static generate(context: GeneratorContext): void {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n const templateFile = path.join(moduleDir, \"templates\", \"Router.ejs\");\n\n for (const [entityName, entityResource] of Object.entries(\n context.resources.entityResources\n )) {\n this.writeRouter(\n entityName,\n templateFile,\n entityResource.operations,\n context\n );\n }\n }\n\n private static writeRouter(\n entityName: string,\n templateFile: string,\n operationResources: OperationResource[],\n context: GeneratorContext\n ): void {\n const pascalCaseEntityName = Case.pascal(entityName);\n const outputDir = path.join(context.outputDir, entityName);\n const outputPath = path.join(outputDir, `${pascalCaseEntityName}Router.ts`);\n\n const operations = operationResources\n .filter(resource => resource.definition.method !== HttpMethod.HEAD)\n .map(resource => this.createOperationData(resource))\n .sort((a, b) => this.compareRoutes(a, b));\n\n const content = context.renderTemplate(templateFile, {\n coreDir: Path.relative(outputDir, context.outputDir),\n entityName,\n pascalCaseEntityName,\n operations,\n });\n\n const relativePath = path.relative(context.outputDir, outputPath);\n context.writeFile(relativePath, content);\n }\n\n private static createOperationData(\n resource: OperationResource\n ): OperationData {\n const operationId = resource.definition.operationId;\n const className = Case.pascal(operationId);\n\n return {\n operationId,\n className,\n handlerName: `handle${className}Request`,\n method: resource.definition.method,\n path: resource.definition.path,\n };\n }\n\n private static compareRoutes(a: OperationData, b: OperationData): number {\n const aSegments = a.path.split(\"/\").filter(s => s);\n const bSegments = b.path.split(\"/\").filter(s => s);\n\n // 1. Compare by depth first (shallow to deep)\n if (aSegments.length !== bSegments.length) {\n return aSegments.length - bSegments.length;\n }\n\n // 2. Compare segment by segment\n for (let i = 0; i < aSegments.length; i++) {\n const aSegment = aSegments[i]!;\n const bSegment = bSegments[i]!;\n\n const aIsParam = aSegment.startsWith(\":\");\n const bIsParam = bSegment.startsWith(\":\");\n\n // Static segments before parameters\n if (aIsParam !== bIsParam) {\n return aIsParam ? 1 : -1;\n }\n\n // Within same type, alphabetical order\n if (aSegment !== bSegment) {\n return aSegment.localeCompare(bSegment);\n }\n }\n\n // 3. Same path = sort by HTTP method priority\n return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);\n }\n\n private static readonly METHOD_PRIORITY: Record<string, number> = {\n GET: 1,\n POST: 2,\n PUT: 3,\n PATCH: 4,\n DELETE: 5,\n OPTIONS: 6,\n HEAD: 7,\n };\n\n private static getMethodPriority(method: string): number {\n return this.METHOD_PRIORITY[method] ?? 999;\n }\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { BasePlugin } from \"@rexeus/typeweaver-gen\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\nimport { RouterGenerator } from \"./RouterGenerator\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\n/**\n * Typeweaver plugin that generates a lightweight, dependency-free server\n * with built-in routing and middleware support.\n *\n * Copies the runtime library files (`TypeweaverApp`, `TypeweaverRouter`, `Router`,\n * `Middleware`, etc.) and generates typed router classes for each resource.\n */\nexport default class ServerPlugin extends BasePlugin {\n public name = \"server\";\n\n /**\n * Generates the server runtime and typed routers for all resources.\n *\n * @param context - The generator context\n */\n public override generate(context: GeneratorContext): void {\n const libSourceDir = path.join(moduleDir, \"lib\");\n this.copyLibFiles(context, libSourceDir, this.name);\n\n RouterGenerator.generate(context);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAwBA,IAAa,kBAAb,MAA6B;;;;;;CAM3B,OAAc,SAAS,SAAiC;EACtD,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;EAC9D,MAAM,eAAe,KAAK,KAAK,WAAW,aAAa,aAAa;AAEpE,OAAK,MAAM,CAAC,YAAY,mBAAmB,OAAO,QAChD,QAAQ,UAAU,gBACnB,CACC,MAAK,YACH,YACA,cACA,eAAe,YACf,QACD;;CAIL,OAAe,YACb,YACA,cACA,oBACA,SACM;EACN,MAAM,uBAAuB,KAAK,OAAO,WAAW;EACpD,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW,WAAW;EAC1D,MAAM,aAAa,KAAK,KAAK,WAAW,GAAG,qBAAqB,WAAW;EAE3E,MAAM,aAAa,mBAChB,QAAO,aAAY,SAAS,WAAW,WAAW,WAAW,KAAK,CAClE,KAAI,aAAY,KAAK,oBAAoB,SAAS,CAAC,CACnD,MAAM,GAAG,MAAM,KAAK,cAAc,GAAG,EAAE,CAAC;EAE3C,MAAM,UAAU,QAAQ,eAAe,cAAc;GACnD,SAAS,KAAK,SAAS,WAAW,QAAQ,UAAU;GACpD;GACA;GACA;GACD,CAAC;EAEF,MAAM,eAAe,KAAK,SAAS,QAAQ,WAAW,WAAW;AACjE,UAAQ,UAAU,cAAc,QAAQ;;CAG1C,OAAe,oBACb,UACe;EACf,MAAM,cAAc,SAAS,WAAW;EACxC,MAAM,YAAY,KAAK,OAAO,YAAY;AAE1C,SAAO;GACL;GACA;GACA,aAAa,SAAS,UAAU;GAChC,QAAQ,SAAS,WAAW;GAC5B,MAAM,SAAS,WAAW;GAC3B;;CAGH,OAAe,cAAc,GAAkB,GAA0B;EACvE,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;EAClD,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;AAGlD,MAAI,UAAU,WAAW,UAAU,OACjC,QAAO,UAAU,SAAS,UAAU;AAItC,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;GACzC,MAAM,WAAW,UAAU;GAC3B,MAAM,WAAW,UAAU;GAE3B,MAAM,WAAW,SAAS,WAAW,IAAI;AAIzC,OAAI,aAHa,SAAS,WAAW,IAAI,CAIvC,QAAO,WAAW,IAAI;AAIxB,OAAI,aAAa,SACf,QAAO,SAAS,cAAc,SAAS;;AAK3C,SAAO,KAAK,kBAAkB,EAAE,OAAO,GAAG,KAAK,kBAAkB,EAAE,OAAO;;CAG5E,OAAwB,kBAA0C;EAChE,KAAK;EACL,MAAM;EACN,KAAK;EACL,OAAO;EACP,QAAQ;EACR,SAAS;EACT,MAAM;EACP;CAED,OAAe,kBAAkB,QAAwB;AACvD,SAAO,KAAK,gBAAgB,WAAW;;;;;;AC5H3C,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;;;;;;;;AAS9D,IAAqB,eAArB,cAA0C,WAAW;CACnD,AAAO,OAAO;;;;;;CAOd,AAAgB,SAAS,SAAiC;EACxD,MAAM,eAAe,KAAK,KAAK,WAAW,MAAM;AAChD,OAAK,aAAa,SAAS,cAAc,KAAK,KAAK;AAEnD,kBAAgB,SAAS,QAAQ"}
@@ -9,18 +9,32 @@ 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";
16
19
 
20
+ /**
21
+ * Metadata about a matched route, available in middleware and handlers via `ctx.route`.
22
+ */
23
+ export type RouteMetadata = {
24
+ readonly operationId: string;
25
+ readonly method: HttpMethod;
26
+ readonly path: string;
27
+ };
28
+
17
29
  /**
18
30
  * A registered route with its method, path pattern, validator, and handler.
19
31
  */
20
32
  export type RouteDefinition = {
33
+ readonly operationId: string;
21
34
  readonly method: HttpMethod;
22
35
  readonly path: string;
23
- readonly validator: IRequestValidator;
36
+ readonly requestValidator: IRequestValidator;
37
+ readonly responseValidator: IResponseValidator;
24
38
  readonly handler: RequestHandler<any, any, any>;
25
39
  /** Reference to the router config for error handling. */
26
40
  readonly routerConfig: RouterErrorConfig;
@@ -31,28 +45,44 @@ export type RouteDefinition = {
31
45
  */
32
46
  export type RouterErrorConfig = {
33
47
  readonly validateRequests: boolean;
48
+ readonly validateResponses: boolean;
34
49
  readonly handleHttpResponseErrors: HttpResponseErrorHandler | boolean;
35
- readonly handleValidationErrors: ValidationErrorHandler | boolean;
50
+ readonly handleRequestValidationErrors:
51
+ | RequestValidationErrorHandler
52
+ | boolean;
53
+ readonly handleResponseValidationErrors:
54
+ | ResponseValidationErrorHandler
55
+ | boolean;
36
56
  readonly handleUnknownErrors: UnknownErrorHandler | boolean;
37
57
  };
38
58
 
39
59
  /**
40
60
  * Handles HTTP response errors thrown by request handlers.
41
- * 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, ... }`).
42
62
  */
43
63
  export type HttpResponseErrorHandler = (
44
- error: IHttpResponse,
64
+ error: ITypedHttpResponse,
45
65
  ctx: ServerContext
46
66
  ) => Promise<IHttpResponse> | IHttpResponse;
47
67
 
48
68
  /**
49
69
  * Handles request validation errors.
50
70
  */
51
- export type ValidationErrorHandler = (
71
+ export type RequestValidationErrorHandler = (
52
72
  error: RequestValidationError,
53
73
  ctx: ServerContext
54
74
  ) => Promise<IHttpResponse> | IHttpResponse;
55
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
+
56
86
  /**
57
87
  * Handles any unknown errors not caught by other handlers.
58
88
  */
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { IHttpRequest } from "@rexeus/typeweaver-core";
9
+ import type { RouteMetadata } from "./Router";
9
10
  import type { StateMap } from "./StateMap";
10
11
 
11
12
  /**
@@ -51,4 +52,7 @@ export type ServerContext<
51
52
 
52
53
  /** Type-safe key-value store for sharing data between middleware and handlers. */
53
54
  readonly state: StateMap<TState>;
55
+
56
+ /** Metadata about the matched route, or `undefined` for 404/405 requests. */
57
+ readonly route: RouteMetadata | undefined;
54
58
  };
@@ -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,9 +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,
24
+ RouteMatch,
20
25
  UnknownErrorHandler,
21
- ValidationErrorHandler,
26
+ RequestValidationErrorHandler,
22
27
  } from "./Router";
23
28
  import type { ServerContext } from "./ServerContext";
24
29
  import type { StateRequirementError, TypedMiddleware } from "./TypedMiddleware";
@@ -186,15 +191,24 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
186
191
  const url = new URL(request.url);
187
192
  const httpRequest = await this.adapter.toRequest(request, url);
188
193
 
194
+ const match = this.router.match(request.method, url.pathname);
195
+
189
196
  const ctx: ServerContext = {
190
197
  request: httpRequest,
191
198
  state: new StateMap(),
199
+ route: match
200
+ ? {
201
+ operationId: match.route.operationId,
202
+ method: match.route.method,
203
+ path: match.route.path,
204
+ }
205
+ : undefined,
192
206
  };
193
207
 
194
208
  const response = await executeMiddlewarePipeline(
195
209
  this.middlewares,
196
210
  ctx,
197
- () => this.resolveAndExecute(request.method, url.pathname, ctx)
211
+ () => this.resolveAndExecute(match, url.pathname, ctx)
198
212
  );
199
213
 
200
214
  return request.method.toUpperCase() === "HEAD"
@@ -203,21 +217,26 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
203
217
  }
204
218
 
205
219
  /**
206
- * Match the route and execute the handler.
220
+ * Execute the matched route handler, or produce 404/405 responses.
207
221
  * Called as the final handler in the middleware pipeline.
208
222
  */
209
223
  private async resolveAndExecute(
210
- method: string,
224
+ match: RouteMatch | undefined,
211
225
  pathname: string,
212
226
  ctx: ServerContext
213
227
  ): Promise<IHttpResponse> {
214
- const match = this.router.match(method, pathname);
215
-
216
228
  if (match) {
217
229
  const routeCtx = this.withPathParams(ctx, match.params);
218
230
  try {
219
- 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);
220
233
  } catch (error) {
234
+ if (
235
+ isTypedHttpResponse(error) &&
236
+ match.route.routerConfig.validateResponses
237
+ ) {
238
+ return await this.validateResponse(match.route, error, routeCtx);
239
+ }
221
240
  return this.handleError(error, routeCtx, match.route);
222
241
  }
223
242
  }
@@ -253,44 +272,123 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
253
272
  route: RouteDefinition
254
273
  ): Promise<IHttpResponse> {
255
274
  const validatedRequest = route.routerConfig.validateRequests
256
- ? route.validator.validate(ctx.request)
275
+ ? route.requestValidator.validate(ctx.request)
257
276
  : ctx.request;
258
277
 
259
278
  return route.handler(validatedRequest, ctx);
260
279
  }
261
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
+
262
346
  /**
263
347
  * Handle errors using the route's configured error handlers.
264
- * Handler errors bubble up to the safety net in `fetch()`.
265
348
  */
266
- private handleError(
349
+ private async handleError(
267
350
  error: unknown,
268
351
  ctx: ServerContext,
269
352
  route: RouteDefinition
270
- ): IHttpResponse | Promise<IHttpResponse> {
353
+ ): Promise<IHttpResponse> {
271
354
  const config = route.routerConfig;
272
355
 
273
356
  if (error instanceof RequestValidationError) {
274
- const handler = this.resolveErrorHandler<ValidationErrorHandler>(
275
- config.handleValidationErrors,
276
- TypeweaverApp.defaultValidationHandler
357
+ const handler = this.resolveErrorHandler<RequestValidationErrorHandler>(
358
+ config.handleRequestValidationErrors,
359
+ TypeweaverApp.defaultRequestValidationHandler
277
360
  );
278
- 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
+ }
279
367
  }
280
368
 
281
- if (error instanceof HttpResponse) {
369
+ if (isTypedHttpResponse(error)) {
282
370
  const handler = this.resolveErrorHandler<HttpResponseErrorHandler>(
283
371
  config.handleHttpResponseErrors,
284
372
  TypeweaverApp.defaultHttpResponseHandler
285
373
  );
286
- 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
+ }
287
380
  }
288
381
 
289
382
  const handler = this.resolveErrorHandler<UnknownErrorHandler>(
290
383
  config.handleUnknownErrors,
291
384
  this.defaultUnknownHandler
292
385
  );
293
- 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
+ }
294
392
 
295
393
  throw error;
296
394
  }
@@ -331,30 +429,35 @@ export class TypeweaverApp<TState extends Record<string, unknown> = {}> {
331
429
  return issues.map(({ message, path }) => ({ message, path }));
332
430
  }
333
431
 
334
- private static defaultValidationHandler: ValidationErrorHandler = (
335
- err
336
- ): IHttpResponse => {
337
- 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);
338
435
 
339
- const header = TypeweaverApp.sanitizeIssues(err.headerIssues);
340
- const body = TypeweaverApp.sanitizeIssues(err.bodyIssues);
341
- const query = TypeweaverApp.sanitizeIssues(err.queryIssues);
342
- 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);
343
440
 
344
- if (header) issues.header = header;
345
- if (body) issues.body = body;
346
- if (query) issues.query = query;
347
- 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;
348
445
 
349
- return {
350
- statusCode: 400,
351
- body: {
352
- code: "VALIDATION_ERROR",
353
- message: err.message,
354
- issues,
355
- },
446
+ return {
447
+ statusCode: 400,
448
+ body: {
449
+ code: "VALIDATION_ERROR",
450
+ message: err.message,
451
+ issues,
452
+ },
453
+ };
356
454
  };
357
- };
455
+
456
+ private static defaultResponseValidationHandler: ResponseValidationErrorHandler =
457
+ (): IHttpResponse => ({
458
+ statusCode: 500,
459
+ body: TypeweaverApp.INTERNAL_SERVER_ERROR_BODY,
460
+ });
358
461
 
359
462
  private static defaultHttpResponseHandler: HttpResponseErrorHandler = (
360
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
  }
@@ -109,21 +138,27 @@ export abstract class TypeweaverRouter<
109
138
  /**
110
139
  * Register a route. Called by generated subclasses in their constructor.
111
140
  *
141
+ * @param operationId - Unique operation identifier from the API definition
112
142
  * @param method - HTTP method (GET, POST, PUT, DELETE, etc.)
113
143
  * @param path - Path pattern with `:param` placeholders
114
144
  * @param validator - Request validator for this operation
145
+ * @param responseValidator - Response validator for this operation
115
146
  * @param handler - Type-safe request handler
116
147
  */
117
148
  protected route(
149
+ operationId: string,
118
150
  method: HttpMethod,
119
151
  path: string,
120
- validator: IRequestValidator,
152
+ requestValidator: IRequestValidator,
153
+ responseValidator: IResponseValidator,
121
154
  handler: RequestHandler<any, any, any>
122
155
  ): void {
123
156
  this.routes.push({
157
+ operationId,
124
158
  method,
125
159
  path,
126
- validator,
160
+ requestValidator,
161
+ responseValidator,
127
162
  handler,
128
163
  routerConfig: this.errorConfig,
129
164
  });
package/dist/lib/index.ts CHANGED
@@ -15,8 +15,10 @@ export {
15
15
  export { HttpMethod } from "@rexeus/typeweaver-core";
16
16
  export type {
17
17
  HttpResponseErrorHandler,
18
+ RequestValidationErrorHandler,
19
+ ResponseValidationErrorHandler,
20
+ RouteMetadata,
18
21
  UnknownErrorHandler,
19
- ValidationErrorHandler,
20
22
  } from "./Router";
21
23
  export type { ServerContext } from "./ServerContext";
22
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<
@@ -32,9 +33,11 @@ export class <%- pascalCaseEntityName %>Router<
32
33
  protected setupRoutes(): void {
33
34
  <% for (const operation of operations) { %>
34
35
  this.route(
36
+ '<%- operation.operationId %>',
35
37
  HttpMethod.<%- operation.method %>,
36
38
  '<%- operation.path %>',
37
39
  new <%- operation.className %>RequestValidator(),
40
+ new <%- operation.className %>ResponseValidator(),
38
41
  this.requestHandlers.<%- operation.handlerName %>.bind(this.requestHandlers)
39
42
  );
40
43
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-server",
3
- "version": "0.6.5",
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.6.5",
51
- "@rexeus/typeweaver-gen": "^0.6.5"
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.6.5",
58
- "@rexeus/typeweaver-gen": "^0.6.5"
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",