@cosmneo/onion-lasagna 0.4.1 → 1.0.0-beta.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/{chunk-XIRJ73IO.js → chunk-264NA7XM.js} +11 -6
  3. package/dist/chunk-264NA7XM.js.map +1 -0
  4. package/dist/{chunk-AUMHMWDD.js → chunk-3BBDU53E.js} +58 -23
  5. package/dist/chunk-3BBDU53E.js.map +1 -0
  6. package/dist/{chunk-4HMXTGHK.js → chunk-5MULXOAU.js} +2 -2
  7. package/dist/{chunk-KJ4JGZOE.js → chunk-AJWFTEC5.js} +19 -5
  8. package/dist/chunk-AJWFTEC5.js.map +1 -0
  9. package/dist/{chunk-4YBAV6LZ.js → chunk-BHSAOT5B.js} +6 -5
  10. package/dist/chunk-BHSAOT5B.js.map +1 -0
  11. package/dist/chunk-BMOAHH3T.js +55 -0
  12. package/dist/chunk-BMOAHH3T.js.map +1 -0
  13. package/dist/{chunk-A4JUAZK4.js → chunk-BPG5YNVO.js} +33 -1
  14. package/dist/chunk-BPG5YNVO.js.map +1 -0
  15. package/dist/{chunk-EJNADL7J.js → chunk-FUDTACMG.js} +164 -53
  16. package/dist/chunk-FUDTACMG.js.map +1 -0
  17. package/dist/{chunk-3BY5RBF2.js → chunk-ICGALFMB.js} +5 -2
  18. package/dist/chunk-ICGALFMB.js.map +1 -0
  19. package/dist/{chunk-VBG3UYQR.js → chunk-IDSI64MZ.js} +24 -15
  20. package/dist/chunk-IDSI64MZ.js.map +1 -0
  21. package/dist/{chunk-UNVB4INM.js → chunk-LAAR2TVP.js} +18 -3
  22. package/dist/chunk-LAAR2TVP.js.map +1 -0
  23. package/dist/{chunk-T7S574XQ.js → chunk-O67HG4GE.js} +12 -47
  24. package/dist/chunk-O67HG4GE.js.map +1 -0
  25. package/dist/{chunk-ANLXZHUS.js → chunk-QX525IMM.js} +66 -10
  26. package/dist/chunk-QX525IMM.js.map +1 -0
  27. package/dist/{chunk-HNEAH6OZ.js → chunk-RUDRJY45.js} +44 -12
  28. package/dist/chunk-RUDRJY45.js.map +1 -0
  29. package/dist/{chunk-XP6PLTV2.js → chunk-YKWUH4P3.js} +6 -3
  30. package/dist/{chunk-XP6PLTV2.js.map → chunk-YKWUH4P3.js.map} +1 -1
  31. package/dist/events/asyncapi/index.cjs +18 -4
  32. package/dist/events/asyncapi/index.cjs.map +1 -1
  33. package/dist/events/asyncapi/index.d.cts +11 -0
  34. package/dist/events/asyncapi/index.d.ts +11 -0
  35. package/dist/events/asyncapi/index.js +1 -1
  36. package/dist/events/index.cjs +33 -7
  37. package/dist/events/index.cjs.map +1 -1
  38. package/dist/events/index.d.cts +2 -2
  39. package/dist/events/index.d.ts +2 -2
  40. package/dist/events/index.js +8 -7
  41. package/dist/events/server/index.cjs +15 -3
  42. package/dist/events/server/index.cjs.map +1 -1
  43. package/dist/events/server/index.d.cts +2 -2
  44. package/dist/events/server/index.d.ts +2 -2
  45. package/dist/events/server/index.js +7 -6
  46. package/dist/events/shared/index.cjs +15 -3
  47. package/dist/events/shared/index.cjs.map +1 -1
  48. package/dist/events/shared/index.js +6 -5
  49. package/dist/global.cjs +37 -0
  50. package/dist/global.cjs.map +1 -1
  51. package/dist/global.d.cts +39 -1
  52. package/dist/global.d.ts +39 -1
  53. package/dist/global.js +7 -3
  54. package/dist/graphql/index.cjs +635 -365
  55. package/dist/graphql/index.cjs.map +1 -1
  56. package/dist/graphql/index.d.cts +3 -3
  57. package/dist/graphql/index.d.ts +3 -3
  58. package/dist/graphql/index.js +11 -9
  59. package/dist/graphql/sdl/index.cjs +165 -53
  60. package/dist/graphql/sdl/index.cjs.map +1 -1
  61. package/dist/graphql/sdl/index.d.cts +56 -1
  62. package/dist/graphql/sdl/index.d.ts +56 -1
  63. package/dist/graphql/sdl/index.js +5 -3
  64. package/dist/graphql/server/index.cjs +132 -11
  65. package/dist/graphql/server/index.cjs.map +1 -1
  66. package/dist/graphql/server/index.d.cts +2 -2
  67. package/dist/graphql/server/index.d.ts +2 -2
  68. package/dist/graphql/server/index.js +4 -6
  69. package/dist/graphql/shared/index.cjs +117 -10
  70. package/dist/graphql/shared/index.cjs.map +1 -1
  71. package/dist/graphql/shared/index.d.cts +14 -8
  72. package/dist/graphql/shared/index.d.ts +14 -8
  73. package/dist/graphql/shared/index.js +6 -6
  74. package/dist/http/index.cjs +162 -32
  75. package/dist/http/index.cjs.map +1 -1
  76. package/dist/http/index.d.cts +2 -2
  77. package/dist/http/index.d.ts +2 -2
  78. package/dist/http/index.js +9 -8
  79. package/dist/http/openapi/index.cjs +9 -4
  80. package/dist/http/openapi/index.cjs.map +1 -1
  81. package/dist/http/openapi/index.d.cts +1 -1
  82. package/dist/http/openapi/index.d.ts +1 -1
  83. package/dist/http/openapi/index.js +2 -2
  84. package/dist/http/route/index.cjs +21 -3
  85. package/dist/http/route/index.cjs.map +1 -1
  86. package/dist/http/route/index.d.cts +2 -2
  87. package/dist/http/route/index.d.ts +2 -2
  88. package/dist/http/route/index.js +2 -2
  89. package/dist/http/server/index.cjs +91 -19
  90. package/dist/http/server/index.cjs.map +1 -1
  91. package/dist/http/server/index.d.cts +3 -3
  92. package/dist/http/server/index.d.ts +3 -3
  93. package/dist/http/server/index.js +5 -4
  94. package/dist/http/shared/index.cjs +80 -6
  95. package/dist/http/shared/index.cjs.map +1 -1
  96. package/dist/http/shared/index.d.cts +8 -3
  97. package/dist/http/shared/index.d.ts +8 -3
  98. package/dist/http/shared/index.js +5 -4
  99. package/dist/index.cjs +752 -251
  100. package/dist/index.cjs.map +1 -1
  101. package/dist/index.d.cts +194 -23
  102. package/dist/index.d.ts +194 -23
  103. package/dist/index.js +363 -101
  104. package/dist/index.js.map +1 -1
  105. package/dist/{router-definition.type-BElX-Pl4.d.cts → router-definition.type-BpMusb4W.d.cts} +22 -1
  106. package/dist/{router-definition.type-DxG8ncJZ.d.ts → router-definition.type-Jnds2Dbe.d.ts} +22 -1
  107. package/dist/{types-afYpL7Ap.d.cts → types-D6Cujlt2.d.ts} +3 -2
  108. package/dist/{types-B6Q1iCgf.d.ts → types-Dl24BMWh.d.cts} +3 -2
  109. package/package.json +17 -6
  110. package/dist/chunk-3BY5RBF2.js.map +0 -1
  111. package/dist/chunk-4YBAV6LZ.js.map +0 -1
  112. package/dist/chunk-A4JUAZK4.js.map +0 -1
  113. package/dist/chunk-ANLXZHUS.js.map +0 -1
  114. package/dist/chunk-AUMHMWDD.js.map +0 -1
  115. package/dist/chunk-EJNADL7J.js.map +0 -1
  116. package/dist/chunk-FEY2GSVT.js +0 -1
  117. package/dist/chunk-FEY2GSVT.js.map +0 -1
  118. package/dist/chunk-HNEAH6OZ.js.map +0 -1
  119. package/dist/chunk-KJ4JGZOE.js.map +0 -1
  120. package/dist/chunk-T7S574XQ.js.map +0 -1
  121. package/dist/chunk-UNVB4INM.js.map +0 -1
  122. package/dist/chunk-VBG3UYQR.js.map +0 -1
  123. package/dist/chunk-XIRJ73IO.js.map +0 -1
  124. /package/dist/{chunk-4HMXTGHK.js.map → chunk-5MULXOAU.js.map} +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # @cosmneo/onion-lasagna
2
+
3
+ ## 1.0.0-beta.0
4
+
5
+ ### Major Changes
6
+
7
+ - 01b4e2e: **1.0.0 — audited & stabilized.** This release remediates a full code-quality audit (120 findings:
8
+ 1 blocker, 3 critical, 42 major, plus minors/nits) across every package, each fix covered by tests.
9
+
10
+ Notable behavior changes (the reason this is a major):
11
+ - **HTTP client** — non-idempotent methods (POST/PATCH) are no longer auto-retried by default; the
12
+ documented 30s request timeout is now actually applied; array query params serialize as repeated
13
+ keys (`?tag=a&tag=b`); `ClientError` no longer drops non-JSON error bodies.
14
+ - **GraphQL (Yoga)** — discriminated-union outputs now resolve at runtime (were unqueryable); typed
15
+ subscriptions validate per yielded item instead of failing; output-validation failures are masked
16
+ rather than leaking internal field paths; `UnauthorizedError` maps to the `UNAUTHENTICATED`
17
+ extension code (was `FORBIDDEN`).
18
+ - **Svelte Query / Vue Query** — hooks are now reactive: `useEnabled` gating and input-derived query
19
+ keys update correctly (previously frozen at setup).
20
+ - **Errors** — `DbError` / `TimeoutError` / `PartialLoadError` are now reliably masked across
21
+ HTTP/GraphQL even under bundling; a stable error brand replaces fragile class-name matching.
22
+ - **HTTP server** — `HandlerResponse.headers` now accepts `string | string[]` (multi-value headers
23
+ like `Set-Cookie`); malformed request bodies normalize to 400; `defineRouter` `basePath` is now
24
+ applied; response validation rejects undeclared status codes.
25
+ - **Domain** — value objects and domain events no longer leak mutable internal state via `.value` /
26
+ `.occurredOn`.
27
+ - **Packaging** — `uuid` is bundled so the CommonJS build no longer breaks ESM-only consumers;
28
+ `typescript` is now an optional peer dependency.
29
+
30
+ See PRs #11–#38 for full detail.
@@ -3,7 +3,7 @@ import {
3
3
  generateOperationId,
4
4
  getPathParamNames,
5
5
  isRouterDefinition
6
- } from "./chunk-XP6PLTV2.js";
6
+ } from "./chunk-YKWUH4P3.js";
7
7
 
8
8
  // src/presentation/http/openapi/generate.ts
9
9
  function isValidJsonSchema(value) {
@@ -25,6 +25,11 @@ function generateOpenAPI(router, config) {
25
25
  }
26
26
  const operation = buildOperation(key, route);
27
27
  const method = route.method.toLowerCase();
28
+ if (paths[openAPIPath][method] !== void 0) {
29
+ throw new Error(
30
+ `Duplicate route: ${route.method.toUpperCase()} ${openAPIPath} is defined more than once. Each path+method combination must be unique in a router.`
31
+ );
32
+ }
28
33
  paths[openAPIPath][method] = operation;
29
34
  if (operation.tags) {
30
35
  for (const tag of operation.tags) {
@@ -100,6 +105,7 @@ function buildOperation(key, route) {
100
105
  }
101
106
  function buildParameters(route) {
102
107
  const parameters = [];
108
+ const paramsJsonSchema = route.request.params ? route.request.params.toJsonSchema() : null;
103
109
  const pathParamNames = getPathParamNames(route.path);
104
110
  for (const name of pathParamNames) {
105
111
  const param = {
@@ -108,10 +114,9 @@ function buildParameters(route) {
108
114
  required: true,
109
115
  schema: { type: "string" }
110
116
  };
111
- if (route.request.params) {
112
- const jsonSchema = route.request.params.toJsonSchema();
113
- if (jsonSchema.properties && typeof jsonSchema.properties === "object") {
114
- const propSchema = jsonSchema.properties[name];
117
+ if (paramsJsonSchema) {
118
+ if (paramsJsonSchema.properties && typeof paramsJsonSchema.properties === "object") {
119
+ const propSchema = paramsJsonSchema.properties[name];
115
120
  if (isValidJsonSchema(propSchema)) {
116
121
  param.schema = propSchema;
117
122
  }
@@ -213,4 +218,4 @@ function buildResponses(route) {
213
218
  export {
214
219
  generateOpenAPI
215
220
  };
216
- //# sourceMappingURL=chunk-XIRJ73IO.js.map
221
+ //# sourceMappingURL=chunk-264NA7XM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/presentation/http/openapi/generate.ts"],"sourcesContent":["/**\n * @fileoverview OpenAPI specification generation from router definitions (v2 — flat API).\n *\n * The `generateOpenAPI` function creates a complete OpenAPI 3.1 specification\n * from a router definition. All route schemas are converted to JSON Schema\n * and included in the specification.\n *\n * Schemas are read from `route.request` (body, query, params, headers) and\n * per-status response schemas from `route.responses`.\n *\n * @module unified/openapi/generate\n */\n\nimport type { JsonSchema, SchemaAdapter } from '../schema/types';\nimport type { RouterConfig, RouterDefinition, RouteDefinition } from '../route/types';\nimport { isRouterDefinition, collectRoutes, getPathParamNames } from '../route/types';\nimport { generateOperationId } from '../route/utils';\nimport type {\n OpenAPIConfig,\n OpenAPISpec,\n OpenAPIPaths,\n OpenAPIPathItem,\n OpenAPIOperation,\n OpenAPIParameter,\n OpenAPIRequestBody,\n OpenAPIResponses,\n OpenAPITag,\n} from './types';\n\n/**\n * Checks if a value is a valid JSON schema structure.\n *\n * A valid JSON schema must be an object and have at least one of:\n * - `type` property (primitive or object types)\n * - `$ref` property (reference to another schema)\n * - `oneOf`, `anyOf`, `allOf` (composition)\n * - `properties` (object schema without explicit type)\n * - `items` (array schema without explicit type)\n * - `enum` (enumeration)\n * - `const` (constant value)\n *\n * @param value - The value to check\n * @returns True if value appears to be a valid JSON schema\n */\nfunction isValidJsonSchema(value: unknown): value is JsonSchema {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n\n const schema = value as Record<string, unknown>;\n\n // Check for valid JSON schema indicators\n return (\n 'type' in schema ||\n '$ref' in schema ||\n 'oneOf' in schema ||\n 'anyOf' in schema ||\n 'allOf' in schema ||\n 'properties' in schema ||\n 'items' in schema ||\n 'enum' in schema ||\n 'const' in schema\n );\n}\n\n/**\n * Generates an OpenAPI specification from a router definition.\n *\n * This function walks the router structure, extracts JSON schemas from\n * all route definitions, and builds a complete OpenAPI 3.1 specification.\n *\n * Operation IDs are auto-generated from the router key path when not\n * explicitly specified in the route docs.\n *\n * @param router - Router definition or router config\n * @param config - OpenAPI configuration (info, servers, security, etc.)\n * @returns Complete OpenAPI specification\n *\n * @example Basic usage\n * ```typescript\n * import { generateOpenAPI } from '@cosmneo/onion-lasagna/http/openapi';\n * import { api } from './routes';\n *\n * const spec = generateOpenAPI(api, {\n * info: {\n * title: 'My API',\n * version: '1.0.0',\n * description: 'A comprehensive API for managing resources',\n * },\n * servers: [\n * { url: 'http://localhost:3000', description: 'Development' },\n * { url: 'https://api.example.com', description: 'Production' },\n * ],\n * });\n *\n * // Serve the spec\n * app.get('/openapi.json', (c) => c.json(spec));\n * ```\n */\nexport function generateOpenAPI<T extends RouterConfig>(\n router: T | RouterDefinition<T>,\n config: OpenAPIConfig,\n): OpenAPISpec {\n const routes = isRouterDefinition(router) ? router.routes : router;\n const collectedRoutes = collectRoutes(routes);\n\n // Build paths\n const paths: OpenAPIPaths = {};\n const allTags = new Set<string>();\n\n for (const { key, route } of collectedRoutes) {\n const openAPIPath = convertToOpenAPIPath(route.path);\n\n if (!paths[openAPIPath]) {\n paths[openAPIPath] = {};\n }\n\n const operation = buildOperation(key, route);\n const method = route.method.toLowerCase() as keyof OpenAPIPathItem;\n\n // Guard: detect duplicate path+method — silently overwriting would lose a route\n if ((paths[openAPIPath] as Record<string, OpenAPIOperation>)[method] !== undefined) {\n throw new Error(\n `Duplicate route: ${route.method.toUpperCase()} ${openAPIPath} is defined more than once. ` +\n `Each path+method combination must be unique in a router.`,\n );\n }\n\n (paths[openAPIPath] as Record<string, OpenAPIOperation>)[method] = operation;\n\n // Collect tags\n if (operation.tags) {\n for (const tag of operation.tags) {\n allTags.add(tag);\n }\n }\n }\n\n // Build the specification\n const spec: OpenAPISpec = {\n openapi: config.openapi ?? '3.1.0',\n info: config.info,\n paths,\n };\n\n // Add optional sections\n if (config.servers && config.servers.length > 0) {\n (spec as { servers: typeof config.servers }).servers = config.servers;\n }\n\n if (config.securitySchemes && Object.keys(config.securitySchemes).length > 0) {\n (spec as { components: { securitySchemes: typeof config.securitySchemes } }).components = {\n securitySchemes: config.securitySchemes,\n };\n }\n\n if (config.security && config.security.length > 0) {\n (spec as { security: typeof config.security }).security = config.security;\n }\n\n // Merge custom tags with collected tags\n const tags: OpenAPITag[] = config.tags ? [...config.tags] : [];\n for (const tagName of allTags) {\n if (!tags.some((t) => t.name === tagName)) {\n tags.push({ name: tagName });\n }\n }\n if (tags.length > 0) {\n (spec as unknown as { tags: OpenAPITag[] }).tags = tags;\n }\n\n if (config.externalDocs) {\n (spec as { externalDocs: typeof config.externalDocs }).externalDocs = config.externalDocs;\n }\n\n return spec;\n}\n\n/**\n * Converts a path with :param to OpenAPI {param} format.\n */\nfunction convertToOpenAPIPath(path: string): string {\n return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');\n}\n\n/**\n * Builds an OpenAPI operation from a route definition.\n * Auto-generates operationId from the key if not explicitly set.\n */\nfunction buildOperation(key: string, route: RouteDefinition): OpenAPIOperation {\n const operation: OpenAPIOperation = {\n responses: buildResponses(route),\n };\n\n // operationId: use explicit or auto-generate from router key\n const operationId = route.docs.operationId ?? generateOperationId(key);\n (operation as { operationId: string }).operationId = operationId;\n\n if (route.docs.summary) {\n (operation as { summary: string }).summary = route.docs.summary;\n }\n\n if (route.docs.description) {\n (operation as { description: string }).description = route.docs.description;\n }\n\n if (route.docs.tags && route.docs.tags.length > 0) {\n (operation as { tags: readonly string[] }).tags = route.docs.tags;\n }\n\n if (route.docs.deprecated) {\n (operation as { deprecated: boolean }).deprecated = true;\n }\n\n if (route.docs.security && route.docs.security.length > 0) {\n (operation as { security: typeof route.docs.security }).security = route.docs.security;\n }\n\n if (route.docs.externalDocs) {\n (operation as { externalDocs: typeof route.docs.externalDocs }).externalDocs =\n route.docs.externalDocs;\n }\n\n // Add parameters\n const parameters = buildParameters(route);\n if (parameters.length > 0) {\n (operation as unknown as { parameters: OpenAPIParameter[] }).parameters = parameters;\n }\n\n // Add request body\n if (route.request.body) {\n (operation as { requestBody: OpenAPIRequestBody }).requestBody = buildRequestBody(route);\n }\n\n return operation;\n}\n\n/**\n * Builds OpenAPI parameters from route request schemas.\n */\nfunction buildParameters(route: RouteDefinition): OpenAPIParameter[] {\n const parameters: OpenAPIParameter[] = [];\n\n // Path parameters\n // Hoist toJsonSchema() call once per route (C16) instead of re-computing per path param.\n const paramsJsonSchema = route.request.params\n ? (route.request.params as SchemaAdapter).toJsonSchema()\n : null;\n\n const pathParamNames = getPathParamNames(route.path);\n for (const name of pathParamNames) {\n const param: OpenAPIParameter = {\n name,\n in: 'path',\n required: true,\n schema: { type: 'string' },\n };\n\n // If we have a params schema, try to get more info\n if (paramsJsonSchema) {\n if (paramsJsonSchema.properties && typeof paramsJsonSchema.properties === 'object') {\n const propSchema = (paramsJsonSchema.properties as Record<string, unknown>)[name];\n if (isValidJsonSchema(propSchema)) {\n (param as { schema: JsonSchema }).schema = propSchema;\n }\n }\n }\n\n parameters.push(param);\n }\n\n // Query parameters\n if (route.request.query) {\n const querySchema = (route.request.query as SchemaAdapter).toJsonSchema();\n\n if (querySchema.properties && typeof querySchema.properties === 'object') {\n const requiredFields = new Set(\n Array.isArray(querySchema.required) ? querySchema.required : [],\n );\n\n for (const [name, propSchema] of Object.entries(\n querySchema.properties as Record<string, unknown>,\n )) {\n // Skip invalid schema structures\n if (!isValidJsonSchema(propSchema)) {\n continue;\n }\n\n const param: OpenAPIParameter = {\n name,\n in: 'query',\n required: requiredFields.has(name),\n schema: propSchema,\n };\n\n // Add description if present\n if ('description' in propSchema && typeof propSchema.description === 'string') {\n (param as { description: string }).description = propSchema.description;\n }\n\n parameters.push(param);\n }\n }\n }\n\n // Header parameters\n if (route.request.headers) {\n const headersSchema = (route.request.headers as SchemaAdapter).toJsonSchema();\n\n if (headersSchema.properties && typeof headersSchema.properties === 'object') {\n const requiredFields = new Set(\n Array.isArray(headersSchema.required) ? headersSchema.required : [],\n );\n\n for (const [name, propSchema] of Object.entries(\n headersSchema.properties as Record<string, unknown>,\n )) {\n // Skip invalid schema structures\n if (!isValidJsonSchema(propSchema)) {\n continue;\n }\n\n const param: OpenAPIParameter = {\n name,\n in: 'header',\n required: requiredFields.has(name),\n schema: propSchema,\n };\n\n parameters.push(param);\n }\n }\n }\n\n return parameters;\n}\n\n/**\n * Builds OpenAPI request body from route.\n * Reads metadata (contentType, required, description) from `route._meta.body`.\n */\nfunction buildRequestBody(route: RouteDefinition): OpenAPIRequestBody {\n const bodySchema = route.request.body as SchemaAdapter;\n const meta = route._meta?.body;\n\n const contentType = meta?.contentType ?? 'application/json';\n\n const requestBody: OpenAPIRequestBody = {\n required: meta?.required !== false,\n content: {\n [contentType]: {\n schema: bodySchema.toJsonSchema(),\n },\n },\n };\n\n if (meta?.description) {\n (requestBody as { description: string }).description = meta.description;\n }\n\n return requestBody;\n}\n\n/**\n * Builds OpenAPI responses from route.\n *\n * Each status code in `route.responses` gets its own entry with\n * description, schema, and content type. If no responses are defined,\n * a default `200` entry is generated.\n */\nfunction buildResponses(route: RouteDefinition): OpenAPIResponses {\n const responses: OpenAPIResponses = {};\n\n if (route.responses && Object.keys(route.responses).length > 0) {\n for (const [statusCode, config] of Object.entries(route.responses)) {\n const description = config.description ?? `Response ${statusCode}`;\n\n if (config.schema) {\n const contentType = config.contentType ?? 'application/json';\n responses[statusCode] = {\n description,\n content: {\n [contentType]: {\n schema: (config.schema as SchemaAdapter).toJsonSchema(),\n },\n },\n };\n } else {\n responses[statusCode] = { description };\n }\n }\n } else {\n responses['200'] = { description: 'Successful response' };\n }\n\n return responses;\n}\n"],"mappings":"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","names":[]}
@@ -1,8 +1,10 @@
1
+ import {
2
+ InvalidRequestError
3
+ } from "./chunk-BMOAHH3T.js";
1
4
  import {
2
5
  ControllerError,
3
- InvalidRequestError,
4
6
  UnauthorizedError
5
- } from "./chunk-T7S574XQ.js";
7
+ } from "./chunk-O67HG4GE.js";
6
8
  import {
7
9
  wrapError
8
10
  } from "./chunk-ZG26OQFN.js";
@@ -11,7 +13,10 @@ import {
11
13
  generateOperationId,
12
14
  isRouterDefinition,
13
15
  normalizePath
14
- } from "./chunk-XP6PLTV2.js";
16
+ } from "./chunk-YKWUH4P3.js";
17
+
18
+ // src/presentation/http/server/create-server-routes.ts
19
+ import { randomUUID } from "crypto";
15
20
 
16
21
  // src/presentation/http/server/types.ts
17
22
  function isSimpleHandlerConfig(config) {
@@ -21,6 +26,7 @@ function isSimpleHandlerConfig(config) {
21
26
  // src/presentation/http/server/create-server-routes.ts
22
27
  function createServerRoutesInternal(router, handlers, options) {
23
28
  const routes = isRouterDefinition(router) ? router.routes : router;
29
+ const basePath = isRouterDefinition(router) ? normalizeBasePath(router.basePath) : "";
24
30
  const collectedRoutes = collectRoutes(routes);
25
31
  const sortedRoutes = sortRoutesBySpecificity(collectedRoutes);
26
32
  const result = [];
@@ -40,10 +46,16 @@ function createServerRoutesInternal(router, handlers, options) {
40
46
  `Missing handler for route "${key}". All routes must have a handler configuration.`
41
47
  );
42
48
  }
43
- result.push(createRouteHandler(key, route, handlerConfig, resolvedOptions));
49
+ result.push(createRouteHandler(key, route, handlerConfig, resolvedOptions, basePath));
44
50
  }
45
51
  return result;
46
52
  }
53
+ function normalizeBasePath(basePath) {
54
+ if (!basePath) return "";
55
+ let normalized = basePath.startsWith("/") ? basePath : `/${basePath}`;
56
+ normalized = normalized.replace(/\/+$/, "");
57
+ return normalized;
58
+ }
47
59
  function sortRoutesBySpecificity(routes) {
48
60
  return [...routes].sort((a, b) => {
49
61
  const aSegments = a.route.path.split("/").filter(Boolean);
@@ -65,15 +77,16 @@ function sortRoutesBySpecificity(routes) {
65
77
  return 0;
66
78
  });
67
79
  }
68
- function createRouteHandler(key, route, config, options) {
80
+ function createRouteHandler(key, route, config, options, basePath = "") {
69
81
  const middleware = config.middleware ?? [];
70
82
  const globalMiddleware = options?.middleware ?? [];
71
83
  const allMiddleware = [...globalMiddleware, ...middleware];
72
84
  const shouldValidateRequest = options.validateRequest ?? true;
73
85
  const shouldValidateResponse = options.validateResponse ?? true;
86
+ const routePath = normalizePath(basePath + route.path);
74
87
  return {
75
88
  method: route.method,
76
- path: normalizePath(route.path),
89
+ path: routePath,
77
90
  metadata: {
78
91
  operationId: route.docs.operationId ?? generateOperationId(key),
79
92
  summary: route.docs.summary,
@@ -82,27 +95,36 @@ function createRouteHandler(key, route, config, options) {
82
95
  deprecated: route.docs.deprecated
83
96
  },
84
97
  handler: async (rawRequest, ctx) => {
85
- const rawContext = options?.createContext ? options.createContext(rawRequest) : ctx ?? { requestId: generateRequestId() };
98
+ let rawContext;
99
+ if (options?.createContext) {
100
+ const factoryCtx = options.createContext(rawRequest);
101
+ rawContext = ctx ? { ...ctx, ...factoryCtx } : factoryCtx;
102
+ } else {
103
+ rawContext = ctx ?? { requestId: generateRequestId() };
104
+ }
86
105
  const validatedContext = route.request.context ? wrapError(
87
106
  () => {
88
107
  const result = validateContextData(route, rawContext);
89
108
  if (!result.success) {
90
109
  const errors = result.errors ?? [];
91
- throw new InvalidRequestError({
110
+ const innerError = new InvalidRequestError({
92
111
  message: "Context validation failed",
93
112
  validationErrors: errors.map((e) => ({
94
113
  field: e.path.join("."),
95
114
  message: e.message
96
115
  }))
97
116
  });
117
+ throw innerError;
98
118
  }
99
119
  return result.data;
100
120
  },
101
- () => new UnauthorizedError({ message: "Authentication required" })
121
+ // C05-4: pass the caught error as cause so UnauthorizedError.cause is populated
122
+ (cause) => new UnauthorizedError({ message: "Authentication required", cause })
102
123
  ) : rawContext;
124
+ const normalizedHeaders = normalizeHeaders(rawRequest.headers);
103
125
  let validatedRequest;
104
126
  if (shouldValidateRequest) {
105
- const validationResult = validateRequestData(route, rawRequest);
127
+ const validationResult = validateRequestData(route, rawRequest, normalizedHeaders);
106
128
  if (!validationResult.success) {
107
129
  const errors = validationResult.errors ?? [];
108
130
  throw new InvalidRequestError({
@@ -115,14 +137,15 @@ function createRouteHandler(key, route, config, options) {
115
137
  }
116
138
  const data = validationResult.data ?? {};
117
139
  validatedRequest = {
118
- body: data.body,
119
- query: data.query,
140
+ // C05-3: fall back to raw body when no body schema (consistent with skip-validation branch)
141
+ body: route.request.body ? data.body : rawRequest.body,
142
+ query: data.query ?? normalizeQuery(rawRequest.query),
120
143
  pathParams: data.pathParams,
121
- headers: data.headers,
144
+ headers: data.headers ?? normalizedHeaders,
122
145
  raw: {
123
146
  method: rawRequest.method,
124
147
  url: rawRequest.url,
125
- headers: normalizeHeaders(rawRequest.headers)
148
+ headers: normalizedHeaders
126
149
  }
127
150
  };
128
151
  } else {
@@ -130,11 +153,12 @@ function createRouteHandler(key, route, config, options) {
130
153
  body: rawRequest.body,
131
154
  query: normalizeQuery(rawRequest.query),
132
155
  pathParams: normalizePathParams(rawRequest.params),
133
- headers: normalizeHeaders(rawRequest.headers),
156
+ // C16-2: reuse already-normalized headers (no second call)
157
+ headers: normalizedHeaders,
134
158
  raw: {
135
159
  method: rawRequest.method,
136
160
  url: rawRequest.url,
137
- headers: normalizeHeaders(rawRequest.headers)
161
+ headers: normalizedHeaders
138
162
  }
139
163
  };
140
164
  }
@@ -184,7 +208,7 @@ function createRouteHandler(key, route, config, options) {
184
208
  }
185
209
  };
186
210
  }
187
- function validateRequestData(route, rawRequest) {
211
+ function validateRequestData(route, rawRequest, normalizedHeaders) {
188
212
  const errors = [];
189
213
  const data = {};
190
214
  if (route.request.body) {
@@ -230,8 +254,7 @@ function validateRequestData(route, rawRequest) {
230
254
  data.pathParams = normalizePathParams(rawRequest.params);
231
255
  }
232
256
  if (route.request.headers) {
233
- const headersObj = normalizeHeaders(rawRequest.headers);
234
- const result = route.request.headers.validate(headersObj);
257
+ const result = route.request.headers.validate(normalizedHeaders);
235
258
  if (result.success) {
236
259
  data.headers = result.data;
237
260
  } else {
@@ -252,8 +275,20 @@ function validateResponseData(route, response) {
252
275
  if (!route.responses) {
253
276
  return { success: true };
254
277
  }
255
- const entry = route.responses[String(response.status)];
256
- const schema = entry?.schema;
278
+ const statusKey = String(response.status);
279
+ const entry = route.responses[statusKey];
280
+ if (entry === void 0) {
281
+ return {
282
+ success: false,
283
+ errors: [
284
+ {
285
+ path: ["response"],
286
+ message: `Response status ${response.status} is not declared in route.responses`
287
+ }
288
+ ]
289
+ };
290
+ }
291
+ const schema = entry.schema;
257
292
  if (!schema) {
258
293
  return { success: true };
259
294
  }
@@ -349,7 +384,7 @@ function normalizeHeaders(headers) {
349
384
  return result;
350
385
  }
351
386
  function generateRequestId() {
352
- return `req_${crypto.randomUUID()}`;
387
+ return `req_${randomUUID()}`;
353
388
  }
354
389
 
355
390
  // src/presentation/http/server/server-routes-builder.ts
@@ -396,4 +431,4 @@ function serverRoutes(router) {
396
431
  export {
397
432
  serverRoutes
398
433
  };
399
- //# sourceMappingURL=chunk-AUMHMWDD.js.map
434
+ //# sourceMappingURL=chunk-3BBDU53E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/presentation/http/server/create-server-routes.ts","../src/presentation/http/server/types.ts","../src/presentation/http/server/server-routes-builder.ts"],"sourcesContent":["/**\n * @fileoverview Internal implementation for creating server routes with auto-validation.\n *\n * Generates server-side route handlers from a router definition.\n * Each handler automatically validates incoming requests and outgoing\n * responses against the route's schemas.\n *\n * @module unified/server/create-server-routes\n * @internal\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { SchemaAdapter, ValidationIssue } from '../schema/types';\nimport type { RouterConfig, RouterDefinition, RouteDefinition } from '../route/types';\nimport { isRouterDefinition, collectRoutes, normalizePath } from '../route/types';\nimport type {\n AnyHandlerConfig,\n CreateServerRoutesOptions,\n HandlerContext,\n HandlerResponse,\n RawHttpRequest,\n UnifiedRouteInput,\n ValidatedRequest,\n} from './types';\nimport { isSimpleHandlerConfig } from './types';\nimport { InvalidRequestError } from '../../exceptions/invalid-request.error';\nimport { ControllerError } from '../../exceptions/controller.error';\nimport { UnauthorizedError } from '../../../app/exceptions/unauthorized.error';\nimport { wrapError } from '../../../global/utils/wrap-error.util';\nimport { generateOperationId } from '../route/utils';\n\n/**\n * Internal implementation for creating server routes.\n * Used by the builder pattern (serverRoutes).\n *\n * @internal\n */\nexport function createServerRoutesInternal<T extends RouterConfig>(\n router: T | RouterDefinition<T>,\n handlers: Record<string, AnyHandlerConfig<RouteDefinition, unknown, unknown>>,\n options?: CreateServerRoutesOptions,\n): UnifiedRouteInput[] {\n const routes = isRouterDefinition(router) ? router.routes : router;\n\n // C05-1: extract basePath from RouterDefinition (if any) and normalize it\n const basePath = isRouterDefinition(router) ? normalizeBasePath(router.basePath) : '';\n\n const collectedRoutes = collectRoutes(routes);\n\n // Sort routes by specificity: static segments before parameterized\n // This ensures /api/users/me is registered before /api/users/:userId\n const sortedRoutes = sortRoutesBySpecificity(collectedRoutes);\n\n const result: UnifiedRouteInput[] = [];\n\n // Default validation options to true, allowPartial to false\n const resolvedOptions: CreateServerRoutesOptions = {\n ...options,\n validateRequest: options?.validateRequest ?? true,\n validateResponse: options?.validateResponse ?? true,\n allowPartial: options?.allowPartial ?? false,\n };\n\n for (const { key, route } of sortedRoutes) {\n const handlerConfig = handlers[key] as AnyHandlerConfig<RouteDefinition, any, any> | undefined;\n\n if (!handlerConfig) {\n if (resolvedOptions.allowPartial) {\n // Skip routes without handlers when allowPartial is true\n continue;\n }\n throw new Error(\n `Missing handler for route \"${key}\". All routes must have a handler configuration.`,\n );\n }\n\n result.push(createRouteHandler(key, route, handlerConfig, resolvedOptions, basePath));\n }\n\n return result;\n}\n\n/**\n * Normalizes a basePath: ensures it starts with '/' and has no trailing slash.\n * Returns '' for undefined/empty basePath.\n *\n * @internal\n */\nfunction normalizeBasePath(basePath?: string): string {\n if (!basePath) return '';\n // Ensure leading slash\n let normalized = basePath.startsWith('/') ? basePath : `/${basePath}`;\n // Strip trailing slash\n normalized = normalized.replace(/\\/+$/, '');\n return normalized;\n}\n\n/**\n * Sorts routes by path specificity to ensure correct route matching.\n *\n * Static path segments are sorted before parameterized segments at each position.\n * This ensures that `/api/users/me` is registered before `/api/users/:userId`,\n * preventing the parameterized route from incorrectly matching the static path.\n *\n * @example\n * Given routes:\n * - /api/users/:userId (parameterized)\n * - /api/users/me (static)\n *\n * After sorting:\n * - /api/users/me (registered first - matches exactly)\n * - /api/users/:userId (registered second - catches remaining)\n */\nfunction sortRoutesBySpecificity<T extends { route: { path: string } }>(routes: T[]): T[] {\n return [...routes].sort((a, b) => {\n const aSegments = a.route.path.split('/').filter(Boolean);\n const bSegments = b.route.path.split('/').filter(Boolean);\n\n const maxLen = Math.max(aSegments.length, bSegments.length);\n\n for (let i = 0; i < maxLen; i++) {\n const aSeg = aSegments[i];\n const bSeg = bSegments[i];\n\n // Missing segment (shorter path) - shorter paths first for same prefix\n if (aSeg === undefined && bSeg !== undefined) return -1;\n if (aSeg !== undefined && bSeg === undefined) return 1;\n if (aSeg === undefined || bSeg === undefined) return 0;\n\n // Check if segment is parameterized (supports both :param and {param} formats)\n const aIsParam = aSeg.startsWith(':') || (aSeg.startsWith('{') && aSeg.endsWith('}'));\n const bIsParam = bSeg.startsWith(':') || (bSeg.startsWith('{') && bSeg.endsWith('}'));\n\n // Static segments come before parameterized segments\n if (!aIsParam && bIsParam) return -1;\n if (aIsParam && !bIsParam) return 1;\n\n // Both static or both parameterized - compare alphabetically for stable sorting\n const cmp = aSeg.localeCompare(bSeg);\n if (cmp !== 0) return cmp;\n }\n\n return 0;\n });\n}\n\n/**\n * Creates a single route handler with validation.\n *\n * Supports two handler patterns:\n * - Simple handler: handler(req, ctx) → response\n * - Use case pattern: requestMapper → useCase.execute → responseMapper\n */\nfunction createRouteHandler(\n key: string,\n route: RouteDefinition,\n // TInput/TOutput are user-defined and erased at this level - any is required for type compatibility\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config: AnyHandlerConfig<RouteDefinition, any, any>,\n options: CreateServerRoutesOptions,\n /** C05-1: basePath prefix already normalized (leading slash, no trailing slash) */\n basePath = '',\n): UnifiedRouteInput {\n const middleware = config.middleware ?? [];\n const globalMiddleware = options?.middleware ?? [];\n const allMiddleware = [...globalMiddleware, ...middleware];\n\n const shouldValidateRequest = options.validateRequest ?? true;\n const shouldValidateResponse = options.validateResponse ?? true;\n\n // C05-1: prepend basePath to route path (normalizePath converts {param} → :param)\n const routePath = normalizePath(basePath + route.path);\n\n return {\n method: route.method,\n path: routePath,\n metadata: {\n operationId: route.docs.operationId ?? generateOperationId(key),\n summary: route.docs.summary,\n description: route.docs.description,\n tags: route.docs.tags as string[],\n deprecated: route.docs.deprecated,\n },\n handler: async (rawRequest: RawHttpRequest, ctx?: HandlerContext): Promise<HandlerResponse> => {\n // C05-8: Create context — merge factory output with framework ctx rather than silently\n // overriding. createContext result takes explicit precedence over framework ctx fields,\n // but framework ctx fields not present in the factory result are preserved.\n let rawContext: HandlerContext;\n if (options?.createContext) {\n const factoryCtx = options.createContext(rawRequest);\n // Merge: framework ctx first (base), factory ctx on top (wins on overlap)\n rawContext = ctx ? { ...ctx, ...factoryCtx } : factoryCtx;\n } else {\n rawContext = ctx ?? { requestId: generateRequestId() };\n }\n\n // Validate context (if schema defined)\n // Context validation failures are treated as authentication errors (401)\n // because context typically carries auth data (user, session, token).\n // A missing or invalid context means the caller is not properly authenticated.\n const validatedContext: unknown = route.request.context\n ? wrapError(\n () => {\n const result = validateContextData(route, rawContext);\n if (!result.success) {\n const errors = result.errors ?? [];\n // Build the inner error so it can be used as cause (C05-4)\n const innerError = new InvalidRequestError({\n message: 'Context validation failed',\n validationErrors: errors.map((e) => ({\n field: e.path.join('.'),\n message: e.message,\n })),\n });\n throw innerError;\n }\n return result.data;\n },\n // C05-4: pass the caught error as cause so UnauthorizedError.cause is populated\n (cause) => new UnauthorizedError({ message: 'Authentication required', cause }),\n )\n : rawContext;\n\n // C16-2: hoist normalizeHeaders once per request — used for both req.headers and raw.headers\n const normalizedHeaders = normalizeHeaders(rawRequest.headers);\n\n // Validate request (if enabled)\n // Use internal type since specific route types are erased in this function\n let validatedRequest: ValidatedRequestInternal;\n\n if (shouldValidateRequest) {\n const validationResult = validateRequestData(route, rawRequest, normalizedHeaders);\n\n if (!validationResult.success) {\n const errors = validationResult.errors ?? [];\n throw new InvalidRequestError({\n message: 'Request validation failed',\n validationErrors: errors.map((e) => ({\n field: e.path.join('.'),\n message: e.message,\n })),\n });\n }\n\n const data = validationResult.data ?? {};\n\n validatedRequest = {\n // C05-3: fall back to raw body when no body schema (consistent with skip-validation branch)\n body: route.request.body ? data.body : rawRequest.body,\n query: data.query ?? normalizeQuery(rawRequest.query),\n pathParams: data.pathParams,\n headers: data.headers ?? normalizedHeaders,\n raw: {\n method: rawRequest.method,\n url: rawRequest.url,\n headers: normalizedHeaders,\n },\n };\n } else {\n // Skip validation - pass through normalized data\n\n validatedRequest = {\n body: rawRequest.body,\n query: normalizeQuery(rawRequest.query),\n pathParams: normalizePathParams(rawRequest.params),\n // C16-2: reuse already-normalized headers (no second call)\n headers: normalizedHeaders,\n raw: {\n method: rawRequest.method,\n url: rawRequest.url,\n headers: normalizedHeaders,\n },\n };\n }\n\n // Execute the pipeline based on handler type\n // Errors from the use case/handler propagate to the framework's error handler\n const executePipeline = async (): Promise<HandlerResponse> => {\n if (isSimpleHandlerConfig(config)) {\n // Simple handler: direct call\n return config.handler(\n validatedRequest as unknown as ValidatedRequest<RouteDefinition>,\n validatedContext as HandlerContext,\n );\n } else {\n // Use case handler: requestMapper → useCase → responseMapper\n const { requestMapper, useCase, responseMapper } = config;\n\n // Map request to use case input\n // Cast is safe: ValidatedRequestInternal has same shape as ValidatedRequest<TRoute>\n // Type erasure in this function requires the cast for TypeScript\n // validatedContext is typed correctly based on route's context schema\n const input = requestMapper(\n validatedRequest as unknown as ValidatedRequest<RouteDefinition>,\n validatedContext as HandlerContext,\n );\n\n // Execute use case\n const output = await useCase.execute(input);\n\n // Map output to HTTP response\n return responseMapper(output);\n }\n };\n\n let response: HandlerResponse;\n\n if (allMiddleware.length === 0) {\n response = await executePipeline();\n } else {\n // Build middleware chain\n // Note: Middleware receives the raw context before validation\n let index = 0;\n const next = async (): Promise<HandlerResponse> => {\n if (index >= allMiddleware.length) {\n return executePipeline();\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- index bounds checked above\n const mw = allMiddleware[index++]!;\n return mw(rawRequest, rawContext, next);\n };\n\n response = await next();\n }\n\n // Always validate status code (must be 100-599)\n validateStatusCode(response.status);\n\n // Validate response schema (if enabled)\n if (shouldValidateResponse) {\n const responseValidationResult = validateResponseData(route, response);\n\n if (!responseValidationResult.success) {\n const errors = responseValidationResult.errors ?? [];\n throw new ControllerError({\n message: 'Response validation failed',\n code: 'RESPONSE_VALIDATION_ERROR',\n cause: new Error(errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ')),\n });\n }\n }\n\n return response;\n },\n };\n}\n\n/**\n * Validates request data against route request schemas.\n *\n * @param normalizedHeaders - C16-2: pre-normalized headers (avoids double normalization)\n */\nfunction validateRequestData(\n route: RouteDefinition,\n rawRequest: RawHttpRequest,\n normalizedHeaders: Record<string, string>,\n): ValidationResultInternal {\n const errors: ValidationIssue[] = [];\n const data: {\n body?: unknown;\n query?: unknown;\n pathParams?: unknown;\n headers?: unknown;\n } = {};\n\n // Validate body\n if (route.request.body) {\n const result = (route.request.body as SchemaAdapter).validate(rawRequest.body);\n if (result.success) {\n data.body = result.data;\n } else {\n errors.push(\n ...result.issues.map((issue) => ({\n ...issue,\n path: ['body', ...issue.path],\n })),\n );\n }\n }\n\n // Validate query\n if (route.request.query) {\n const queryObj = normalizeQuery(rawRequest.query);\n const result = (route.request.query as SchemaAdapter).validate(queryObj);\n if (result.success) {\n data.query = result.data;\n } else {\n errors.push(\n ...result.issues.map((issue) => ({\n ...issue,\n path: ['query', ...issue.path],\n })),\n );\n }\n }\n\n // Validate path params\n if (route.request.params) {\n const result = (route.request.params as SchemaAdapter).validate(rawRequest.params ?? {});\n if (result.success) {\n data.pathParams = result.data;\n } else {\n errors.push(\n ...result.issues.map((issue) => ({\n ...issue,\n path: ['pathParams', ...issue.path],\n })),\n );\n }\n } else {\n // Normalize raw params if no schema (ensure all values are strings)\n data.pathParams = normalizePathParams(rawRequest.params);\n }\n\n // Validate headers using pre-normalized headers (C16-2: no double normalization)\n if (route.request.headers) {\n const result = (route.request.headers as SchemaAdapter).validate(normalizedHeaders);\n if (result.success) {\n data.headers = result.data;\n } else {\n errors.push(\n ...result.issues.map((issue) => ({\n ...issue,\n path: ['headers', ...issue.path],\n })),\n );\n }\n }\n\n if (errors.length > 0) {\n return { success: false, errors };\n }\n\n return { success: true, data };\n}\n\n/**\n * Validates response data against the route's response schema.\n *\n * Looks up the matching status code in `route.responses` and validates:\n * 1. The status is declared in `route.responses` (MISSED undeclared-status fix)\n * 2. The response body matches the declared schema (if any)\n *\n * If `route.responses` is undefined, all statuses are permissible.\n */\nfunction validateResponseData(\n route: RouteDefinition,\n response: HandlerResponse,\n): ValidationResultInternal {\n if (!route.responses) {\n // No responses declared at all — fully open contract, skip validation\n return { success: true };\n }\n\n const statusKey = String(response.status);\n const entry = route.responses[statusKey];\n\n // undeclared-status: if route.responses is defined but the returned status isn't listed,\n // reject it so handlers can't return undocumented statuses.\n if (entry === undefined) {\n return {\n success: false,\n errors: [\n {\n path: ['response'],\n message: `Response status ${response.status} is not declared in route.responses`,\n },\n ],\n };\n }\n\n const schema = entry.schema as SchemaAdapter | undefined;\n\n if (!schema) {\n // Status is declared but has no schema — body shape is not validated\n return { success: true };\n }\n\n const result = schema.validate(response.body);\n\n if (result.success) {\n return { success: true };\n }\n\n const errors = result.issues.map((issue) => ({\n ...issue,\n path: ['response', ...issue.path],\n }));\n\n return { success: false, errors };\n}\n\n/**\n * Validates context data against route context schema.\n */\nfunction validateContextData(\n route: RouteDefinition,\n context: HandlerContext,\n): ContextValidationResultInternal {\n const contextSchema = route.request.context as SchemaAdapter | undefined;\n\n // No context schema defined - skip validation\n if (!contextSchema) {\n return { success: true, data: context };\n }\n\n // Validate context against schema\n const result = contextSchema.validate(context);\n\n if (result.success) {\n return { success: true, data: result.data };\n }\n\n // Prefix errors with 'context.' for clarity\n const errors = result.issues.map((issue) => ({\n ...issue,\n path: ['context', ...issue.path],\n }));\n\n return { success: false, errors };\n}\n\ninterface ValidationResultInternal {\n success: boolean;\n errors?: ValidationIssue[];\n data?: {\n body?: unknown;\n query?: unknown;\n pathParams?: unknown;\n headers?: unknown;\n };\n}\n\ninterface ContextValidationResultInternal {\n success: boolean;\n errors?: ValidationIssue[];\n data?: unknown;\n}\n\n/**\n * Internal validated request type with unknown fields.\n * Used inside createRouteHandler where specific types are erased.\n * The requestMapper receives the properly typed ValidatedRequest<TRoute>.\n */\ninterface ValidatedRequestInternal {\n readonly body: unknown;\n readonly query: unknown;\n readonly pathParams: unknown;\n readonly headers: unknown;\n readonly raw: {\n readonly method: string;\n readonly url: string;\n readonly headers: Record<string, string>;\n };\n}\n\n/**\n * Validates that an HTTP status code is in the valid range (100-599).\n *\n * @throws {ControllerError} If the status code is invalid\n */\nfunction validateStatusCode(status: number): void {\n if (!Number.isInteger(status) || status < 100 || status > 599) {\n throw new ControllerError({\n message: `Invalid HTTP status code: ${status}. Status must be an integer between 100 and 599.`,\n code: 'INVALID_STATUS_CODE',\n });\n }\n}\n\n/**\n * Normalizes query parameters, preserving arrays for duplicate keys.\n *\n * When a query parameter appears multiple times (e.g., `?tag=a&tag=b`),\n * the framework provides an array. This function preserves that array\n * so schema validation can properly validate array vs single-value params.\n *\n * Empty strings are allowed (e.g., `?flag=` results in `{ flag: '' }`).\n * Undefined values are filtered out from arrays.\n */\nfunction normalizeQuery(\n query?: Record<string, string | string[] | undefined>,\n): Record<string, string | string[]> {\n if (!query) return {};\n\n const result: Record<string, string | string[]> = {};\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n\n if (Array.isArray(value)) {\n // Filter out undefined values but preserve the array structure\n const definedValues = value.filter((v): v is string => v !== undefined);\n if (definedValues.length === 1 && definedValues[0] !== undefined) {\n // Single value in array - unwrap for convenience\n result[key] = definedValues[0];\n } else if (definedValues.length > 1) {\n // Multiple values - preserve as array for schema validation\n result[key] = definedValues;\n }\n // Empty array (all undefined) - skip this key\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Normalizes path parameters to ensure all values are non-empty strings.\n *\n * @throws {InvalidRequestError} If any path parameter is empty\n */\nfunction normalizePathParams(params?: Record<string, string>): Record<string, string> {\n if (!params) return {};\n\n const result: Record<string, string> = {};\n const emptyParams: string[] = [];\n\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n const stringValue = String(value);\n if (stringValue === '') {\n emptyParams.push(key);\n } else {\n result[key] = stringValue;\n }\n }\n }\n\n // Throw error for empty path params instead of silently filtering\n if (emptyParams.length > 0) {\n throw new InvalidRequestError({\n message: 'Path parameters cannot be empty',\n validationErrors: emptyParams.map((param) => ({\n field: `pathParams.${param}`,\n message: 'Path parameter cannot be empty',\n })),\n });\n }\n\n return result;\n}\n\n/**\n * Normalizes headers to a flat object.\n *\n * Per RFC 7230, multiple header values are joined with \", \" (comma + space).\n * Headers are lowercased for consistency.\n */\nfunction normalizeHeaders(\n headers: Record<string, string | string[] | undefined>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n\n if (Array.isArray(value)) {\n // Filter undefined values and join per RFC 7230\n const definedValues = value.filter((v): v is string => v !== undefined);\n if (definedValues.length > 0) {\n result[key.toLowerCase()] = definedValues.join(', ');\n }\n } else {\n result[key.toLowerCase()] = value;\n }\n }\n return result;\n}\n\n/**\n * Generates a unique request ID using crypto-secure UUID.\n *\n * C05-6: uses `randomUUID` from 'node:crypto' (imported at top of file) instead of\n * the bare global `crypto.randomUUID()` which is only available on Node >=18.17.\n * The node:crypto import works from Node 14.17+ / all Node 18.x versions.\n */\nfunction generateRequestId(): string {\n return `req_${randomUUID()}`;\n}\n","/**\n * @fileoverview Server types for the unified route system.\n *\n * @module unified/server/types\n */\n\nimport type {\n HttpMethod,\n RouteDefinition,\n RouterConfig,\n RouterKeys,\n GetRoute,\n} from '../route/types';\n\n// ============================================================================\n// Validated Request\n// ============================================================================\n\n/**\n * A validated request with typed data.\n * This is what handlers receive after validation passes.\n */\nexport interface ValidatedRequest<TRoute extends RouteDefinition> {\n /**\n * Validated request body.\n */\n readonly body: TRoute['_types']['body'];\n\n /**\n * Validated query parameters.\n */\n readonly query: TRoute['_types']['query'];\n\n /**\n * Validated path parameters.\n */\n readonly pathParams: TRoute['_types']['pathParams'];\n\n /**\n * Validated headers.\n */\n readonly headers: TRoute['_types']['headers'];\n\n /**\n * Raw request object for advanced use cases.\n */\n readonly raw: {\n readonly method: string;\n readonly url: string;\n readonly headers: Record<string, string>;\n };\n}\n\n/**\n * Typed context based on route definition.\n * If the route defines a context schema, this will be the validated type.\n * Otherwise, it falls back to the generic HandlerContext.\n */\nexport type TypedContext<TRoute extends RouteDefinition> =\n TRoute['_types']['context'] extends undefined ? HandlerContext : TRoute['_types']['context'];\n\n// ============================================================================\n// Handler Types\n// ============================================================================\n\n/**\n * Context passed to handlers.\n * Can be extended with custom context via serverRoutes options.\n */\nexport interface HandlerContext {\n /**\n * Request ID for tracing.\n */\n readonly requestId?: string;\n\n /**\n * Additional context data.\n */\n readonly [key: string]: unknown;\n}\n\n/**\n * Response from a handler.\n */\nexport interface HandlerResponse<TData = unknown> {\n /**\n * HTTP status code.\n */\n readonly status: number;\n\n /**\n * Response body.\n */\n readonly body?: TData;\n\n /**\n * Response headers.\n * Supports multi-value headers (e.g. repeated Set-Cookie) via string arrays.\n */\n readonly headers?: Record<string, string | string[]>;\n}\n\n// ============================================================================\n// Use Case Port\n// ============================================================================\n\n/**\n * Use case port interface for unified routes.\n *\n * This is a simplified version that accepts any input/output types (plain objects).\n * It's structurally compatible with `BaseInboundPort`, so existing use case\n * implementations work without changes.\n *\n * @typeParam TInput - Input type (plain object or void for no input)\n * @typeParam TOutput - Output type (plain object or void for no output)\n *\n * @example\n * ```typescript\n * // Define plain types for use case contracts\n * type CreateProjectInput = {\n * name: string;\n * description?: string;\n * };\n *\n * type CreateProjectOutput = {\n * projectId: string;\n * };\n *\n * // Use case implements this interface\n * class CreateProjectUseCase implements UseCasePort<CreateProjectInput, CreateProjectOutput> {\n * async execute(input: CreateProjectInput): Promise<CreateProjectOutput> {\n * // ... implementation\n * return { projectId: '...' };\n * }\n * }\n * ```\n */\n\nexport interface UseCasePort<TInput = void, TOutput = void> {\n execute(input?: TInput): Promise<TOutput>;\n}\n\n// ============================================================================\n// Server Configuration\n// ============================================================================\n\n/**\n * Handler configuration for a single route.\n *\n * Mirrors the BaseController pattern with three components:\n * - `requestMapper`: Maps validated HTTP request to use case input\n * - `useCase`: The use case to execute\n * - `responseMapper`: Maps use case output to HTTP response\n *\n * @typeParam TRoute - The route definition type\n * @typeParam TInput - Use case input type (plain object)\n * @typeParam TOutput - Use case output type (plain object)\n *\n * @example\n * ```typescript\n * const config: RouteHandlerConfig<typeof createProjectRoute, CreateProjectInput, CreateProjectOutput> = {\n * requestMapper: (req) => ({\n * name: req.body.name,\n * description: req.body.description,\n * }),\n * useCase: createProjectUseCase,\n * responseMapper: (out) => ({\n * status: 201,\n * body: { projectId: out.projectId },\n * }),\n * };\n * ```\n */\n\nexport interface RouteHandlerConfig<TRoute extends RouteDefinition, TInput = void, TOutput = void> {\n /**\n * Maps the validated HTTP request to use case input.\n * The request has already been validated by the route's schemas.\n * Context is typed based on the route's context schema (if defined).\n */\n readonly requestMapper: (req: ValidatedRequest<TRoute>, ctx: TypedContext<TRoute>) => TInput;\n\n /**\n * The use case to execute.\n * Can be any object with an `execute` method matching `UseCasePort`.\n */\n readonly useCase: UseCasePort<TInput, TOutput>;\n\n /**\n * Maps the use case output to an HTTP response.\n * Determines the status code and response body.\n */\n readonly responseMapper: (output: TOutput) => HandlerResponse;\n\n /**\n * Middleware to run before the handler.\n */\n readonly middleware?: readonly MiddlewareFunction[];\n}\n\n/**\n * Middleware function type.\n */\nexport type MiddlewareFunction = (\n request: unknown,\n context: HandlerContext,\n next: () => Promise<HandlerResponse>,\n) => Promise<HandlerResponse>;\n\n// ============================================================================\n// Simple Handler Types\n// ============================================================================\n\n/**\n * Simple handler function that directly returns a response.\n * Use this for simple routes that don't need the use case pattern.\n */\nexport type SimpleHandlerFn<TRoute extends RouteDefinition> = (\n req: ValidatedRequest<TRoute>,\n ctx: TypedContext<TRoute>,\n) => Promise<HandlerResponse> | HandlerResponse;\n\n/**\n * Configuration for a simple handler (no use case).\n */\nexport interface SimpleHandlerConfig<TRoute extends RouteDefinition> {\n readonly handler: SimpleHandlerFn<TRoute>;\n readonly middleware?: readonly MiddlewareFunction[];\n}\n\n/**\n * Union of all handler config types.\n * Used internally to store handlers in the builder.\n */\nexport type AnyHandlerConfig<TRoute extends RouteDefinition, TInput = unknown, TOutput = unknown> =\n | RouteHandlerConfig<TRoute, TInput, TOutput>\n | SimpleHandlerConfig<TRoute>;\n\n/**\n * Type guard to check if config is a simple handler.\n */\nexport function isSimpleHandlerConfig(\n config: AnyHandlerConfig<RouteDefinition, unknown, unknown>,\n): config is SimpleHandlerConfig<RouteDefinition> {\n return 'handler' in config && typeof config.handler === 'function';\n}\n\n/**\n * Configuration mapping route keys to handlers.\n *\n * Each route key maps to a `RouteHandlerConfig` with:\n * - The route definition for that key (provides request/response types)\n * - User-defined input/output types for the use case\n *\n * The `TInput` and `TOutput` types are inferred from the `useCase` property,\n * so you don't need to specify them explicitly.\n */\n// TInput/TOutput are user-defined per route - any is required for heterogeneous route configs\nexport type ServerRoutesConfig<T extends RouterConfig> = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [K in RouterKeys<T>]: RouteHandlerConfig<GetRoute<T, K>, any, any>;\n};\n\n/**\n * Options for creating server routes.\n */\nexport interface CreateServerRoutesOptions {\n /**\n * Global middleware to run before all handlers.\n */\n readonly middleware?: readonly MiddlewareFunction[];\n\n /**\n * Whether to validate incoming requests against route schemas.\n * When enabled, invalid requests throw InvalidRequestError.\n * @default true\n */\n readonly validateRequest?: boolean;\n\n /**\n * Whether to validate outgoing responses against route schemas.\n * When enabled, invalid responses throw ControllerError.\n * Useful for catching bugs and ensuring API contract compliance.\n * @default true\n */\n readonly validateResponse?: boolean;\n\n /**\n * Context factory to create handler context.\n */\n readonly createContext?: (rawRequest: unknown) => HandlerContext;\n\n /**\n * Allow partial handler configuration (not all routes need handlers).\n * When true, missing handlers are silently skipped.\n * When false (default), missing handlers throw an error.\n * @default false\n * @internal Used by builder pattern's buildPartial()\n */\n readonly allowPartial?: boolean;\n}\n\n// ============================================================================\n// Route Input (for framework adapters)\n// ============================================================================\n\n/**\n * Route input compatible with framework adapters.\n * This is the output of serverRoutes().build().\n */\nexport interface UnifiedRouteInput {\n /**\n * HTTP method.\n */\n readonly method: HttpMethod;\n\n /**\n * URL path pattern.\n */\n readonly path: string;\n\n /**\n * Handler function.\n */\n readonly handler: (\n rawRequest: RawHttpRequest,\n context?: HandlerContext,\n ) => Promise<HandlerResponse>;\n\n /**\n * Route metadata for documentation.\n */\n readonly metadata: {\n readonly operationId?: string;\n readonly summary?: string;\n readonly description?: string;\n readonly tags?: readonly string[];\n readonly deprecated?: boolean;\n };\n}\n\n/**\n * Raw HTTP request from the framework.\n */\nexport interface RawHttpRequest {\n readonly method: string;\n readonly url: string;\n readonly headers: Record<string, string | string[] | undefined>;\n readonly body?: unknown;\n readonly query?: Record<string, string | string[] | undefined>;\n readonly params?: Record<string, string>;\n}\n","/**\n * @fileoverview Builder pattern for creating type-safe server routes.\n *\n * The `serverRoutes` function returns a builder that provides 100% type inference\n * for all handler parameters - no manual type annotations required.\n *\n * @module unified/server/server-routes-builder\n */\n\nimport type { RouterConfig, RouterDefinition, GetRoute, RouterKeys } from '../route/types';\nimport type {\n AnyHandlerConfig,\n CreateServerRoutesOptions,\n HandlerResponse,\n MiddlewareFunction,\n RouteHandlerConfig,\n SimpleHandlerConfig,\n SimpleHandlerFn,\n TypedContext,\n UnifiedRouteInput,\n UseCasePort,\n ValidatedRequest,\n} from './types';\nimport { createServerRoutesInternal } from './create-server-routes';\nimport type { RouteDefinition } from '../route/types';\n\n// ============================================================================\n// Builder Types\n// ============================================================================\n\n/**\n * Error type displayed when attempting to build() with missing handlers.\n * The `___missingRoutes` property shows which routes are missing.\n */\nexport interface MissingHandlersError<TMissing extends string> {\n /**\n * This error indicates that not all routes have handlers.\n * Use buildPartial() to build with only the defined handlers,\n * or add handlers for the missing routes.\n */\n (options?: never): never;\n /** Routes that are missing handlers */\n readonly ___missingRoutes: TMissing;\n}\n\n/**\n * Handler configuration for the builder pattern.\n * Identical to RouteHandlerConfig but with proper TypedContext.\n */\nexport interface BuilderHandlerConfig<TRoute extends RouteDefinition, TInput, TOutput> {\n /**\n * Maps the validated HTTP request to use case input.\n * Both `req` and `ctx` are fully typed based on route schemas.\n */\n readonly requestMapper: (req: ValidatedRequest<TRoute>, ctx: TypedContext<TRoute>) => TInput;\n\n /**\n * The use case to execute.\n */\n readonly useCase: UseCasePort<TInput, TOutput>;\n\n /**\n * Maps the use case output to an HTTP response.\n */\n readonly responseMapper: (output: TOutput) => HandlerResponse;\n\n /**\n * Middleware to run before the handler.\n */\n readonly middleware?: readonly MiddlewareFunction[];\n}\n\n/**\n * Builder interface for creating type-safe server routes.\n *\n * Each `.handle()` call captures the specific route type and provides\n * full type inference for requestMapper, useCase, and responseMapper.\n *\n * @typeParam T - The router configuration type\n * @typeParam THandled - Union of route keys that have handlers (accumulates)\n *\n * @example\n * ```typescript\n * const routes = serverRoutes(projectRouter)\n * .handle('projects.create', {\n * requestMapper: (req, ctx) => ({\n * name: req.body.name, // Fully typed!\n * createdBy: ctx.userId, // Fully typed!\n * }),\n * useCase: createProjectUseCase,\n * responseMapper: (output) => ({\n * status: 201 as const,\n * body: { projectId: output.projectId },\n * }),\n * })\n * .handle('projects.list', { ... })\n * .build();\n * ```\n */\nexport interface ServerRoutesBuilder<T extends RouterConfig, THandled extends string = never> {\n /**\n * Register a simple handler for a route.\n * The handler receives validated request and context, returns response directly.\n *\n * @param key - The route key (e.g., 'projects.get')\n * @param handlerOrConfig - Simple handler function or configuration with handler and optional middleware\n * @returns A new builder with the route key added to handled routes\n */\n handle<K extends Exclude<RouterKeys<T>, THandled>>(\n key: K,\n handlerOrConfig: SimpleHandlerFn<GetRoute<T, K>> | SimpleHandlerConfig<GetRoute<T, K>>,\n ): ServerRoutesBuilder<T, THandled | K>;\n\n /**\n * Register a handler using the use case pattern.\n * Follows: requestMapper → useCase.execute() → responseMapper\n *\n * @param key - The route key (e.g., 'projects.create')\n * @param config - Handler configuration with requestMapper, useCase, responseMapper\n * @returns A new builder with the route key added to handled routes\n */\n handleWithUseCase<K extends Exclude<RouterKeys<T>, THandled>, TInput, TOutput>(\n key: K,\n config: BuilderHandlerConfig<GetRoute<T, K>, TInput, TOutput>,\n ): ServerRoutesBuilder<T, THandled | K>;\n\n /**\n * Build the routes array for framework registration.\n *\n * This method is only available when ALL routes have handlers.\n * If some routes are missing handlers, use `buildPartial()` instead.\n *\n * @param options - Optional configuration (validation, middleware)\n * @returns Array of route inputs for framework registration\n *\n * @throws {Error} At compile time if routes are missing (type error)\n */\n build: [Exclude<RouterKeys<T>, THandled>] extends [never]\n ? (options?: CreateServerRoutesOptions) => UnifiedRouteInput[]\n : MissingHandlersError<Exclude<RouterKeys<T>, THandled>>;\n\n /**\n * Build routes for only the defined handlers.\n *\n * Use this when you only want to register handlers for some routes,\n * not all routes in the router. No compile-time enforcement.\n *\n * @param options - Optional configuration (validation, middleware)\n * @returns Array of route inputs for framework registration\n */\n buildPartial(options?: CreateServerRoutesOptions): UnifiedRouteInput[];\n}\n\n// ============================================================================\n// Builder Implementation\n// ============================================================================\n\n/**\n * Internal builder implementation.\n *\n * Uses an immutable pattern where each handle() call returns a new\n * builder instance with the updated handlers map.\n */\nclass ServerRoutesBuilderImpl<T extends RouterConfig, THandled extends string = never> {\n private readonly router: T | RouterDefinition<T>;\n private readonly handlers: Map<string, AnyHandlerConfig<RouteDefinition, unknown, unknown>>;\n\n constructor(\n router: T | RouterDefinition<T>,\n handlers?: Map<string, AnyHandlerConfig<RouteDefinition, unknown, unknown>>,\n ) {\n this.router = router;\n this.handlers = handlers ?? new Map();\n }\n\n handle<K extends Exclude<RouterKeys<T>, THandled>>(\n key: K,\n handlerOrConfig: SimpleHandlerFn<GetRoute<T, K>> | SimpleHandlerConfig<GetRoute<T, K>>,\n ): ServerRoutesBuilder<T, THandled | K> {\n // Normalize function to config object\n const config: SimpleHandlerConfig<RouteDefinition> =\n typeof handlerOrConfig === 'function'\n ? { handler: handlerOrConfig as SimpleHandlerFn<RouteDefinition> }\n : (handlerOrConfig as SimpleHandlerConfig<RouteDefinition>);\n\n const newHandlers = new Map(this.handlers);\n newHandlers.set(key as string, config);\n\n return new ServerRoutesBuilderImpl<T, THandled | K>(\n this.router,\n newHandlers,\n ) as unknown as ServerRoutesBuilder<T, THandled | K>;\n }\n\n handleWithUseCase<K extends Exclude<RouterKeys<T>, THandled>, TInput, TOutput>(\n key: K,\n config: BuilderHandlerConfig<GetRoute<T, K>, TInput, TOutput>,\n ): ServerRoutesBuilder<T, THandled | K> {\n // Create new handlers map (immutable pattern)\n const newHandlers = new Map(this.handlers);\n newHandlers.set(key as string, config as RouteHandlerConfig<RouteDefinition, unknown, unknown>);\n\n // Return new builder with updated type\n // Cast through unknown is safe: the type system tracks THandled | K through the interface\n // The conditional type on `build` cannot be proven at compile time, hence the cast\n return new ServerRoutesBuilderImpl<T, THandled | K>(\n this.router,\n newHandlers,\n ) as unknown as ServerRoutesBuilder<T, THandled | K>;\n }\n\n // The build method's type is determined by the interface conditional type\n // At runtime, it always works the same way - the conditional type only affects compile-time\n build(options?: CreateServerRoutesOptions): UnifiedRouteInput[] {\n return createServerRoutesInternal(this.router, Object.fromEntries(this.handlers), options);\n }\n\n buildPartial(options?: CreateServerRoutesOptions): UnifiedRouteInput[] {\n return createServerRoutesInternal(this.router, Object.fromEntries(this.handlers), {\n ...options,\n allowPartial: true,\n });\n }\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Creates a type-safe server routes builder for a router.\n *\n * The builder pattern provides 100% type inference for all handler parameters:\n * - `req.body`, `req.query`, `req.pathParams`, `req.headers` are typed from route schemas\n * - `ctx` is typed from the route's context schema\n * - `output` in responseMapper is typed from the use case\n *\n * @param router - Router definition or router config\n * @returns Builder for registering handlers\n *\n * @example Basic usage\n * ```typescript\n * import { serverRoutes } from '@cosmneo/onion-lasagna/http/server';\n * import { projectRouter } from './router';\n *\n * const routes = serverRoutes(projectRouter)\n * .handle('projects.create', {\n * requestMapper: (req, ctx) => ({\n * name: req.body.name,\n * createdBy: ctx.userId,\n * }),\n * useCase: createProjectUseCase,\n * responseMapper: (output) => ({\n * status: 201 as const,\n * body: { projectId: output.projectId },\n * }),\n * })\n * .handle('projects.list', {\n * requestMapper: (req) => ({\n * page: req.query.page ?? 1,\n * limit: req.query.limit ?? 20,\n * }),\n * useCase: listProjectsUseCase,\n * responseMapper: (output) => ({\n * status: 200 as const,\n * body: output.projects,\n * }),\n * })\n * .build();\n *\n * // Register with framework\n * registerHonoRoutes(app, routes);\n * ```\n *\n * @example Partial build (only some routes)\n * ```typescript\n * const routes = serverRoutes(projectRouter)\n * .handle('projects.create', { ... })\n * // Skip other routes\n * .buildPartial(); // No type error even with missing routes\n * ```\n *\n * @example With options\n * ```typescript\n * const routes = serverRoutes(projectRouter)\n * .handle('projects.create', { ... })\n * .handle('projects.list', { ... })\n * .build({\n * validateRequest: true,\n * validateResponse: process.env.NODE_ENV !== 'production',\n * middleware: [loggingMiddleware],\n * });\n * ```\n */\nexport function serverRoutes<T extends RouterConfig>(\n router: T | RouterDefinition<T>,\n): ServerRoutesBuilder<T, never> {\n // Cast through unknown is safe: initial builder has no handlers (THandled = never)\n return new ServerRoutesBuilderImpl(router) as unknown as ServerRoutesBuilder<T, never>;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  mapErrorToEventResult
3
- } from "./chunk-4YBAV6LZ.js";
3
+ } from "./chunk-BHSAOT5B.js";
4
4
  import {
5
5
  collectEventHandlers,
6
6
  generateHandlerId,
@@ -188,4 +188,4 @@ export {
188
188
  isSimpleEventHandlerConfig,
189
189
  eventRoutes
190
190
  };
191
- //# sourceMappingURL=chunk-4HMXTGHK.js.map
191
+ //# sourceMappingURL=chunk-5MULXOAU.js.map
@@ -5,6 +5,9 @@ import {
5
5
  } from "./chunk-CBTICRSM.js";
6
6
 
7
7
  // src/presentation/events/asyncapi/generate.ts
8
+ function encodeJsonPointerToken(token) {
9
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
10
+ }
8
11
  function generateAsyncAPI(router, config) {
9
12
  const routerConfig = isEventRouterDefinition(router) ? router.handlers : router;
10
13
  const handlers = collectEventHandlers(routerConfig);
@@ -13,8 +16,15 @@ function generateAsyncAPI(router, config) {
13
16
  const allTags = /* @__PURE__ */ new Set();
14
17
  for (const { key, handler } of handlers) {
15
18
  const handlerId = generateHandlerId(key);
19
+ if (operations[handlerId] !== void 0) {
20
+ throw new Error(
21
+ `Duplicate handlerId "${handlerId}": two event handlers produce the same identifier after camelCase conversion. Rename one of the router keys to make them unique.`
22
+ );
23
+ }
16
24
  const channelId = handler.eventType;
17
- const message = buildMessage(handler.eventType, handler.payload, handler.docs);
25
+ const escapedChannelId = encodeJsonPointerToken(channelId);
26
+ const escapedHandlerId = encodeJsonPointerToken(handlerId);
27
+ const message = buildMessage(handler.eventType, handler.payload, handler.context, handler.docs);
18
28
  if (!channels[channelId]) {
19
29
  channels[channelId] = {
20
30
  address: handler.eventType,
@@ -26,8 +36,8 @@ function generateAsyncAPI(router, config) {
26
36
  channels[channelId] = { ...channels[channelId], messages: channelMessages };
27
37
  const operation = {
28
38
  action: "receive",
29
- channel: { $ref: `#/channels/${channelId}` },
30
- messages: [{ $ref: `#/channels/${channelId}/messages/${handlerId}` }]
39
+ channel: { $ref: `#/channels/${escapedChannelId}` },
40
+ messages: [{ $ref: `#/channels/${escapedChannelId}/messages/${escapedHandlerId}` }]
31
41
  };
32
42
  if (handler.docs.summary) {
33
43
  operation.summary = handler.docs.summary;
@@ -70,7 +80,7 @@ function generateAsyncAPI(router, config) {
70
80
  }
71
81
  return spec;
72
82
  }
73
- function buildMessage(eventType, payload, docs) {
83
+ function buildMessage(eventType, payload, context, docs) {
74
84
  const message = {
75
85
  name: eventType
76
86
  };
@@ -87,10 +97,14 @@ function buildMessage(eventType, payload, docs) {
87
97
  const schema = payload.toJsonSchema();
88
98
  message.payload = schema;
89
99
  }
100
+ if (context) {
101
+ const headersSchema = context.toJsonSchema();
102
+ message.headers = headersSchema;
103
+ }
90
104
  return message;
91
105
  }
92
106
 
93
107
  export {
94
108
  generateAsyncAPI
95
109
  };
96
- //# sourceMappingURL=chunk-KJ4JGZOE.js.map
110
+ //# sourceMappingURL=chunk-AJWFTEC5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/presentation/events/asyncapi/generate.ts"],"sourcesContent":["/**\n * @fileoverview AsyncAPI specification generation from event router definitions.\n *\n * The `generateAsyncAPI` function creates a complete AsyncAPI 3.0 specification\n * from an event router definition. All handler payload schemas are converted to\n * JSON Schema and included in the specification.\n *\n * @module events/asyncapi/generate\n */\n\nimport type { SchemaAdapter } from '../../http/schema/types';\nimport type { EventRouterConfig, EventRouterDefinition } from '../handler/types';\nimport { isEventRouterDefinition, collectEventHandlers } from '../handler/types';\nimport { generateHandlerId } from '../handler/utils';\nimport type {\n AsyncAPIConfig,\n AsyncAPISpec,\n AsyncAPIChannel,\n AsyncAPIOperation,\n AsyncAPIMessage,\n AsyncAPITag,\n} from './types';\n\n/**\n * Encodes a string for use inside a JSON Pointer (RFC 6901).\n *\n * Per RFC 6901 §3:\n * - `~` must be encoded as `~0`\n * - `/` must be encoded as `~1`\n *\n * The order matters: ~ must be escaped before /.\n *\n * @param token - Raw token (e.g. an eventType string)\n * @returns RFC6901-safe token for embedding in a $ref JSON Pointer\n */\nfunction encodeJsonPointerToken(token: string): string {\n return token.replace(/~/g, '~0').replace(/\\//g, '~1');\n}\n\n/**\n * Generates an AsyncAPI 3.0 specification from an event router definition.\n *\n * This function walks the event router structure, extracts JSON schemas from\n * all handler payload definitions, and builds a complete AsyncAPI 3.0 specification.\n *\n * Handler context schemas (event metadata) are included as message `headers`\n * so that consumers can see the full message contract.\n *\n * @param router - Event router definition or raw config\n * @param config - AsyncAPI configuration (info, servers, tags, etc.)\n * @returns Complete AsyncAPI specification\n *\n * @throws {Error} If two router keys produce the same camelCase handlerId (C07-5).\n *\n * @example\n * ```typescript\n * import { generateAsyncAPI } from '@cosmneo/onion-lasagna/events/asyncapi';\n * import { notificationEventRouter } from './events/router';\n *\n * const spec = generateAsyncAPI(notificationEventRouter, {\n * info: {\n * title: 'My Event API',\n * version: '1.0.0',\n * description: 'Domain events for the application',\n * },\n * });\n *\n * app.get('/asyncapi.json', (c) => c.json(spec));\n * ```\n */\nexport function generateAsyncAPI<T extends EventRouterConfig>(\n router: T | EventRouterDefinition<T>,\n config: AsyncAPIConfig,\n): AsyncAPISpec {\n const routerConfig = isEventRouterDefinition(router) ? router.handlers : router;\n const handlers = collectEventHandlers(routerConfig);\n\n const channels: Record<string, AsyncAPIChannel> = {};\n const operations: Record<string, AsyncAPIOperation> = {};\n const allTags = new Set<string>();\n\n for (const { key, handler } of handlers) {\n const handlerId = generateHandlerId(key);\n\n // C07-5: Guard against duplicate handlerId — two keys that collapse to the same\n // camelCase ID would silently drop the first operation.\n if (operations[handlerId] !== undefined) {\n throw new Error(\n `Duplicate handlerId \"${handlerId}\": two event handlers produce the same identifier ` +\n `after camelCase conversion. Rename one of the router keys to make them unique.`,\n );\n }\n\n const channelId = handler.eventType;\n\n // RFC6901: the eventType is used raw as the channel key (it is a semantic address,\n // not part of a JSON Pointer), but it MUST be escaped when interpolated into $ref\n // pointers (e.g. `#/channels/<escaped>`). C07-8.\n const escapedChannelId = encodeJsonPointerToken(channelId);\n const escapedHandlerId = encodeJsonPointerToken(handlerId);\n\n // Build message (includes payload + context/metadata schema)\n const message = buildMessage(handler.eventType, handler.payload, handler.context, handler.docs);\n\n // Build channel (group by eventType — multiple handlers may share a channel)\n if (!channels[channelId]) {\n channels[channelId] = {\n address: handler.eventType,\n messages: {},\n };\n }\n const channelMessages = { ...(channels[channelId]!.messages ?? {}) };\n channelMessages[handlerId] = message;\n channels[channelId] = { ...channels[channelId]!, messages: channelMessages };\n\n // Build operation (one per handler) — use RFC6901-escaped tokens in $refs\n const operation: AsyncAPIOperation = {\n action: 'receive',\n channel: { $ref: `#/channels/${escapedChannelId}` },\n messages: [{ $ref: `#/channels/${escapedChannelId}/messages/${escapedHandlerId}` }],\n };\n\n if (handler.docs.summary) {\n (operation as { summary: string }).summary = handler.docs.summary;\n }\n if (handler.docs.description) {\n (operation as { description: string }).description = handler.docs.description;\n }\n if (handler.docs.deprecated) {\n (operation as { deprecated: boolean }).deprecated = true;\n }\n if (handler.docs.tags && handler.docs.tags.length > 0) {\n (operation as { tags: readonly AsyncAPITag[] }).tags = handler.docs.tags.map((t) => ({\n name: t,\n }));\n for (const tag of handler.docs.tags) {\n allTags.add(tag);\n }\n }\n\n operations[handlerId] = operation;\n }\n\n // Merge custom tags with collected tags (AsyncAPI 3.0: tags live inside info)\n const tags: AsyncAPITag[] = config.tags ? [...config.tags] : [];\n for (const tagName of allTags) {\n if (!tags.some((t) => t.name === tagName)) {\n tags.push({ name: tagName });\n }\n }\n\n const info = tags.length > 0 ? { ...config.info, tags } : config.info;\n\n // Build the specification\n const spec: AsyncAPISpec = {\n asyncapi: config.asyncapi ?? '3.0.0',\n info,\n defaultContentType: config.defaultContentType ?? 'application/json',\n channels,\n operations,\n };\n\n if (config.servers && Object.keys(config.servers).length > 0) {\n (spec as { servers: typeof config.servers }).servers = config.servers;\n }\n\n if (config.externalDocs) {\n (spec as { externalDocs: typeof config.externalDocs }).externalDocs = config.externalDocs;\n }\n\n return spec;\n}\n\n/**\n * Builds an AsyncAPI message from a handler definition.\n *\n * - `payload` schema → message `payload` field\n * - `context` schema → message `headers` field (runtime-validated event metadata)\n */\nfunction buildMessage(\n eventType: string,\n payload: unknown,\n context: unknown,\n docs: {\n readonly summary?: string;\n readonly description?: string;\n readonly tags?: readonly string[];\n readonly deprecated?: boolean;\n },\n): AsyncAPIMessage {\n const message: AsyncAPIMessage = {\n name: eventType,\n };\n\n if (docs.summary) {\n (message as { summary: string }).summary = docs.summary;\n }\n if (docs.description) {\n (message as { description: string }).description = docs.description;\n }\n if (docs.deprecated) {\n (message as { deprecated: boolean }).deprecated = true;\n }\n\n if (payload) {\n const schema = (payload as SchemaAdapter).toJsonSchema();\n (message as { payload: typeof schema }).payload = schema;\n }\n\n // MISSED: context/metadata schema was not included in the generated doc.\n // Map context → message `headers` (AsyncAPI 3.0 convention for envelope metadata).\n if (context) {\n const headersSchema = (context as SchemaAdapter).toJsonSchema();\n (message as { headers: typeof headersSchema }).headers = headersSchema;\n }\n\n return message;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isErrorType
3
- } from "./chunk-HNEAH6OZ.js";
3
+ } from "./chunk-RUDRJY45.js";
4
4
 
5
5
  // src/presentation/events/shared/error-mapping.ts
6
6
  var DLQ_ERROR_TYPES = [
@@ -12,7 +12,9 @@ var DLQ_ERROR_TYPES = [
12
12
  "AccessDeniedError",
13
13
  "ForbiddenError",
14
14
  "UnauthorizedError",
15
- "InvariantViolationError"
15
+ "InvariantViolationError",
16
+ // DomainError subclasses
17
+ "PartialLoadError"
16
18
  ];
17
19
  var RETRY_ERROR_TYPES = [
18
20
  "NotFoundError",
@@ -21,8 +23,7 @@ var RETRY_ERROR_TYPES = [
21
23
  "DbError",
22
24
  "NetworkError",
23
25
  "TimeoutError",
24
- "ExternalServiceError",
25
- "PersistenceError"
26
+ "ExternalServiceError"
26
27
  ];
27
28
  function mapErrorToEventResult(error) {
28
29
  for (const typeName of DLQ_ERROR_TYPES) {
@@ -51,4 +52,4 @@ function mapErrorToEventResult(error) {
51
52
  export {
52
53
  mapErrorToEventResult
53
54
  };
54
- //# sourceMappingURL=chunk-4YBAV6LZ.js.map
55
+ //# sourceMappingURL=chunk-BHSAOT5B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/presentation/events/shared/error-mapping.ts"],"sourcesContent":["/**\n * @fileoverview Error-to-EventResult mapping for event handlers.\n *\n * Maps caught errors to event processing outcomes (ack/retry/dlq).\n * Uses the same name-based type checking as the HTTP error mapper\n * for compatibility with bundled/minified code.\n *\n * @module events/shared/error-mapping\n */\n\nimport { isErrorType } from '../../http/shared/error-mapping';\nimport type { EventResult } from './types';\n\n// ============================================================================\n// Error Type Classifications\n// ============================================================================\n\n/**\n * Error types that indicate permanent failures (send to DLQ).\n * These errors will not resolve on retry.\n * Includes concrete DomainError subclasses so cross-realm name-checks work\n * even when instanceof fails (C04-1 / C15-1).\n * 'PersistenceError' is intentionally absent — it does not exist (C04-4 / C15-2).\n */\nconst DLQ_ERROR_TYPES = [\n 'ObjectValidationError',\n 'InvalidRequestError',\n 'UseCaseError',\n 'DomainError',\n 'UnprocessableError',\n 'AccessDeniedError',\n 'ForbiddenError',\n 'UnauthorizedError',\n 'InvariantViolationError',\n // DomainError subclasses\n 'PartialLoadError',\n];\n\n/**\n * Error types that indicate transient failures (retry).\n * These errors may resolve on subsequent attempts.\n * 'PersistenceError' is intentionally absent — it does not exist (C04-4 / C15-2).\n */\nconst RETRY_ERROR_TYPES = [\n 'NotFoundError',\n 'ConflictError',\n 'InfraError',\n 'DbError',\n 'NetworkError',\n 'TimeoutError',\n 'ExternalServiceError',\n];\n\n// ============================================================================\n// Mapping Function\n// ============================================================================\n\n/**\n * Maps a caught error to an EventResult.\n *\n * Default mapping strategy:\n *\n * | Error Type | Outcome | Rationale |\n * |------------------------------------------------|---------|----------------------------------------|\n * | ObjectValidationError, InvalidRequestError | dlq | Bad payload — retrying won't help |\n * | UseCaseError | dlq | Business rule rejection — permanent |\n * | DomainError, InvariantViolationError | dlq | Domain invariant — permanent |\n * | UnprocessableError | dlq | Valid but not processable — permanent |\n * | AccessDeniedError, ForbiddenError, Unauthorized | dlq | Permission — permanent |\n * | NotFoundError | retry | Entity might not exist yet |\n * | ConflictError | retry | Concurrent write — may resolve |\n * | InfraError, DbError, NetworkError, TimeoutError | retry | Infrastructure/transient |\n * | Unknown | retry | Conservative — don't lose messages |\n *\n * @param error - The caught error\n * @returns An EventResult indicating how the message should be handled\n */\nexport function mapErrorToEventResult(error: unknown): EventResult {\n // Check DLQ errors first (permanent failures)\n for (const typeName of DLQ_ERROR_TYPES) {\n if (isErrorType(error, typeName)) {\n return {\n outcome: 'dlq',\n reason: (error as { message: string }).message,\n };\n }\n }\n\n // Check retry errors (transient failures)\n for (const typeName of RETRY_ERROR_TYPES) {\n if (isErrorType(error, typeName)) {\n return {\n outcome: 'retry',\n reason: (error as { message: string }).message,\n };\n }\n }\n\n // Unknown errors — retry conservatively to avoid losing messages\n const message =\n error instanceof Error ? error.message : 'Unknown error occurred during event processing';\n\n return {\n outcome: 'retry',\n reason: message,\n };\n}\n"],"mappings":";;;;;AAwBA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AACF;AAOA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA0BO,SAAS,sBAAsB,OAA6B;AAEjE,aAAW,YAAY,iBAAiB;AACtC,QAAI,YAAY,OAAO,QAAQ,GAAG;AAChC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAS,MAA8B;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAGA,aAAW,YAAY,mBAAmB;AACxC,QAAI,YAAY,OAAO,QAAQ,GAAG;AAChC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAS,MAA8B;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU;AAE3C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;","names":[]}