@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 +65 -44
- package/dist/index.cjs +3 -1
- package/dist/index.mjs +3 -1
- package/dist/index.mjs.map +1 -1
- package/dist/lib/Router.ts +35 -5
- package/dist/lib/ServerContext.ts +4 -0
- package/dist/lib/TypeweaverApp.ts +142 -39
- package/dist/lib/TypeweaverRouter.ts +47 -12
- package/dist/lib/index.ts +3 -1
- package/dist/templates/Router.ejs +3 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -113,8 +113,8 @@ export const userHandlers: ServerUserApiHandler = {
|
|
|
113
113
|
};
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
> Generated response
|
|
117
|
-
>
|
|
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
|
|
314
|
-
|
|
|
315
|
-
| `requestHandlers`
|
|
316
|
-
| `validateRequests`
|
|
317
|
-
| `
|
|
318
|
-
| `
|
|
319
|
-
| `
|
|
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
|
|
323
|
-
|
|
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
|
-
|
|
330
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
354
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
|
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 | `
|
|
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` |
|
|
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
|
|
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
|
|
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,
|
package/dist/index.mjs.map
CHANGED
|
@@ -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(
|
|
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"}
|
package/dist/lib/Router.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
61
|
+
* The error parameter is a typed HTTP response object (thrown via `throw { type, statusCode, ... }`).
|
|
42
62
|
*/
|
|
43
63
|
export type HttpResponseErrorHandler = (
|
|
44
|
-
error:
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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(
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
):
|
|
353
|
+
): Promise<IHttpResponse> {
|
|
271
354
|
const config = route.routerConfig;
|
|
272
355
|
|
|
273
356
|
if (error instanceof RequestValidationError) {
|
|
274
|
-
const handler = this.resolveErrorHandler<
|
|
275
|
-
config.
|
|
276
|
-
TypeweaverApp.
|
|
357
|
+
const handler = this.resolveErrorHandler<RequestValidationErrorHandler>(
|
|
358
|
+
config.handleRequestValidationErrors,
|
|
359
|
+
TypeweaverApp.defaultRequestValidationHandler
|
|
277
360
|
);
|
|
278
|
-
if (handler)
|
|
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
|
|
369
|
+
if (isTypedHttpResponse(error)) {
|
|
282
370
|
const handler = this.resolveErrorHandler<HttpResponseErrorHandler>(
|
|
283
371
|
config.handleHttpResponseErrors,
|
|
284
372
|
TypeweaverApp.defaultHttpResponseHandler
|
|
285
373
|
);
|
|
286
|
-
if (handler)
|
|
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)
|
|
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
|
|
335
|
-
err
|
|
336
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
*
|
|
43
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
51
|
-
"@rexeus/typeweaver-
|
|
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.
|
|
58
|
-
"@rexeus/typeweaver-gen": "^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",
|