@cosmneo/onion-lasagna 0.2.0 → 0.3.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/dist/chunk-36IO4BM7.js +113 -0
- package/dist/chunk-36IO4BM7.js.map +1 -0
- package/dist/chunk-4BVOLXDJ.js +54 -0
- package/dist/chunk-4BVOLXDJ.js.map +1 -0
- package/dist/{chunk-GGSAAZPM.js → chunk-AUMHMWDD.js} +19 -20
- package/dist/chunk-AUMHMWDD.js.map +1 -0
- package/dist/chunk-CBTICRSM.js +34 -0
- package/dist/chunk-CBTICRSM.js.map +1 -0
- package/dist/chunk-H5TNDC5U.js +138 -0
- package/dist/chunk-H5TNDC5U.js.map +1 -0
- package/dist/chunk-KJ4JGZOE.js +96 -0
- package/dist/chunk-KJ4JGZOE.js.map +1 -0
- package/dist/chunk-MF2JDREK.js +168 -0
- package/dist/chunk-MF2JDREK.js.map +1 -0
- package/dist/chunk-RVSBIYY4.js +1 -0
- package/dist/chunk-RVSBIYY4.js.map +1 -0
- package/dist/{chunk-PUVAB3JX.js → chunk-XIRJ73IO.js} +38 -36
- package/dist/chunk-XIRJ73IO.js.map +1 -0
- package/dist/{chunk-DS7TE6KZ.js → chunk-XP6PLTV2.js} +11 -3
- package/dist/chunk-XP6PLTV2.js.map +1 -0
- package/dist/chunk-XWKHOLIP.js +191 -0
- package/dist/chunk-XWKHOLIP.js.map +1 -0
- package/dist/event-router-definition.type-CP9uTlux.d.cts +150 -0
- package/dist/event-router-definition.type-D8CG-kjZ.d.ts +150 -0
- package/dist/events/asyncapi/index.cjs +143 -0
- package/dist/events/asyncapi/index.cjs.map +1 -0
- package/dist/events/asyncapi/index.d.cts +184 -0
- package/dist/events/asyncapi/index.d.ts +184 -0
- package/dist/events/asyncapi/index.js +8 -0
- package/dist/events/asyncapi/index.js.map +1 -0
- package/dist/events/handler/index.cjs +171 -0
- package/dist/events/handler/index.cjs.map +1 -0
- package/dist/events/handler/index.d.cts +153 -0
- package/dist/events/handler/index.d.ts +153 -0
- package/dist/events/handler/index.js +21 -0
- package/dist/events/handler/index.js.map +1 -0
- package/dist/events/index.cjs +497 -0
- package/dist/events/index.cjs.map +1 -0
- package/dist/events/index.d.cts +9 -0
- package/dist/events/index.d.ts +9 -0
- package/dist/events/index.js +41 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/server/index.cjs +291 -0
- package/dist/events/server/index.cjs.map +1 -0
- package/dist/events/server/index.d.cts +281 -0
- package/dist/events/server/index.d.ts +281 -0
- package/dist/events/server/index.js +16 -0
- package/dist/events/server/index.js.map +1 -0
- package/dist/events/shared/index.cjs +85 -0
- package/dist/events/shared/index.cjs.map +1 -0
- package/dist/events/shared/index.d.cts +35 -0
- package/dist/events/shared/index.d.ts +35 -0
- package/dist/events/shared/index.js +13 -0
- package/dist/events/shared/index.js.map +1 -0
- package/dist/global.js +3 -3
- package/dist/http/index.cjs +563 -93
- package/dist/http/index.cjs.map +1 -1
- package/dist/http/index.d.cts +5 -3
- package/dist/http/index.d.ts +5 -3
- package/dist/http/index.js +30 -12
- package/dist/http/openapi/index.cjs +43 -35
- package/dist/http/openapi/index.cjs.map +1 -1
- package/dist/http/openapi/index.d.cts +8 -34
- package/dist/http/openapi/index.d.ts +8 -34
- package/dist/http/openapi/index.js +2 -2
- package/dist/http/route/index.cjs +106 -9
- package/dist/http/route/index.cjs.map +1 -1
- package/dist/http/route/index.d.cts +133 -227
- package/dist/http/route/index.d.ts +133 -227
- package/dist/http/route/index.js +5 -2
- package/dist/http/server/index.cjs +24 -19
- package/dist/http/server/index.cjs.map +1 -1
- package/dist/http/server/index.d.cts +4 -261
- package/dist/http/server/index.d.ts +4 -261
- package/dist/http/server/index.js +2 -2
- package/dist/http/shared/index.cjs.map +1 -1
- package/dist/http/shared/index.d.cts +10 -14
- package/dist/http/shared/index.d.ts +10 -14
- package/dist/http/shared/index.js +11 -127
- package/dist/http/shared/index.js.map +1 -1
- package/dist/index.js +6 -6
- package/dist/{router-definition.type-ynBhT16T.d.cts → router-definition.type-BElX-Pl4.d.cts} +169 -256
- package/dist/{router-definition.type-DORVlLNk.d.ts → router-definition.type-DxG8ncJZ.d.ts} +169 -256
- package/dist/types-B6Q1iCgf.d.ts +262 -0
- package/dist/types-afYpL7Ap.d.cts +262 -0
- package/dist/types-cke61juH.d.cts +42 -0
- package/dist/types-cke61juH.d.ts +42 -0
- package/package.json +35 -2
- package/dist/chunk-BZULBF4N.js +0 -82
- package/dist/chunk-BZULBF4N.js.map +0 -1
- package/dist/chunk-DS7TE6KZ.js.map +0 -1
- package/dist/chunk-GGSAAZPM.js.map +0 -1
- package/dist/chunk-PUVAB3JX.js.map +0 -1
|
@@ -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 (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 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 (route.request.params) {\n const jsonSchema = (route.request.params as SchemaAdapter).toJsonSchema();\n if (jsonSchema.properties && typeof jsonSchema.properties === 'object') {\n const propSchema = (jsonSchema.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":[]}
|
|
@@ -25,7 +25,7 @@ function normalizePath(path) {
|
|
|
25
25
|
|
|
26
26
|
// src/presentation/http/route/types/router-definition.type.ts
|
|
27
27
|
function isRouteDefinition(value) {
|
|
28
|
-
return typeof value === "object" && value !== null && "method" in value && "path" in value && "
|
|
28
|
+
return typeof value === "object" && value !== null && "method" in value && "path" in value && "_types" in value;
|
|
29
29
|
}
|
|
30
30
|
function isRouterDefinition(value) {
|
|
31
31
|
return typeof value === "object" && value !== null && "_isRouter" in value && value._isRouter === true;
|
|
@@ -45,6 +45,13 @@ function collectRoutes(config, basePath = "") {
|
|
|
45
45
|
return routes;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// src/presentation/http/route/utils.ts
|
|
49
|
+
function generateOperationId(key) {
|
|
50
|
+
return key.split(".").map(
|
|
51
|
+
(segment, index) => index === 0 ? segment : segment.charAt(0).toUpperCase() + segment.slice(1)
|
|
52
|
+
).join("");
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
export {
|
|
49
56
|
pathToRegex,
|
|
50
57
|
getPathParamNames,
|
|
@@ -53,6 +60,7 @@ export {
|
|
|
53
60
|
normalizePath,
|
|
54
61
|
isRouteDefinition,
|
|
55
62
|
isRouterDefinition,
|
|
56
|
-
collectRoutes
|
|
63
|
+
collectRoutes,
|
|
64
|
+
generateOperationId
|
|
57
65
|
};
|
|
58
|
-
//# sourceMappingURL=chunk-
|
|
66
|
+
//# sourceMappingURL=chunk-XP6PLTV2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/presentation/http/route/types/path-params.type.ts","../src/presentation/http/route/types/router-definition.type.ts","../src/presentation/http/route/utils.ts"],"sourcesContent":["/**\n * @fileoverview Path parameter extraction types.\n *\n * These types enable TypeScript to extract path parameter names from\n * URL path templates at compile time, providing full type safety for\n * path parameters in routes.\n *\n * @module unified/route/types/path-params\n */\n\n/**\n * Extracts parameter names from a path template string.\n *\n * Supports both `:param` and `{param}` syntaxes for maximum compatibility\n * with different routing conventions.\n *\n * @example Colon syntax (Express-style)\n * ```typescript\n * type Params = ExtractPathParamNames<'/users/:userId/posts/:postId'>;\n * // 'userId' | 'postId'\n * ```\n *\n * @example Brace syntax (OpenAPI-style)\n * ```typescript\n * type Params = ExtractPathParamNames<'/users/{userId}/posts/{postId}'>;\n * // 'userId' | 'postId'\n * ```\n *\n * @example No parameters\n * ```typescript\n * type Params = ExtractPathParamNames<'/users'>;\n * // never\n * ```\n */\nexport type ExtractPathParamNames<T extends string> =\n // Match :param followed by more path\n T extends `${string}:${infer Param}/${infer Rest}`\n ? Param | ExtractPathParamNames<`/${Rest}`>\n : // Match :param at end\n T extends `${string}:${infer Param}`\n ? Param\n : // Match {param} followed by more path\n T extends `${string}{${infer Param}}/${infer Rest}`\n ? Param | ExtractPathParamNames<`/${Rest}`>\n : // Match {param} at end\n T extends `${string}{${infer Param}}`\n ? Param\n : never;\n\n/**\n * Creates an object type with all path parameters as string properties.\n *\n * @example\n * ```typescript\n * type Params = PathParams<'/projects/:projectId/tasks/:taskId'>;\n * // { projectId: string; taskId: string }\n *\n * type NoParams = PathParams<'/projects'>;\n * // Record<string, never> (empty object type)\n * ```\n */\nexport type PathParams<T extends string> =\n ExtractPathParamNames<T> extends never\n ? Record<string, never>\n : Record<ExtractPathParamNames<T>, string>;\n\n/**\n * Checks if a path has any parameters.\n *\n * @example\n * ```typescript\n * type HasParams = HasPathParams<'/users/:id'>; // true\n * type NoParams = HasPathParams<'/users'>; // false\n * ```\n */\nexport type HasPathParams<T extends string> = ExtractPathParamNames<T> extends never ? false : true;\n\n/**\n * Converts a path template with parameters to a regex pattern.\n * This is used internally for route matching.\n *\n * @example\n * ```typescript\n * pathToRegex('/users/:id/posts/:postId')\n * // /^\\/users\\/([^\\/]+)\\/posts\\/([^\\/]+)\\/?$/\n * ```\n */\nexport function pathToRegex(path: string): RegExp {\n const pattern = path\n // Escape special regex characters except : and {}\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n // Replace :param with capture group\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n // Replace {param} with capture group\n .replace(/\\\\\\{([a-zA-Z_][a-zA-Z0-9_]*)\\\\\\}/g, '([^/]+)');\n\n return new RegExp(`^${pattern}/?$`);\n}\n\n/**\n * Extracts parameter names from a path string at runtime.\n *\n * @example\n * ```typescript\n * getPathParamNames('/users/:userId/posts/:postId')\n * // ['userId', 'postId']\n * ```\n */\nexport function getPathParamNames(path: string): string[] {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- regex capture group always exists\n const colonParams = [...path.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)].map((m) => m[1]!);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- regex capture group always exists\n const braceParams = [...path.matchAll(/\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}/g)].map((m) => m[1]!);\n return [...colonParams, ...braceParams];\n}\n\n/**\n * Checks if a path has any parameters at runtime.\n */\nexport function hasPathParams(path: string): boolean {\n return /:([a-zA-Z_][a-zA-Z0-9_]*)/.test(path) || /\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}/.test(path);\n}\n\n/**\n * Replaces path parameters with actual values.\n *\n * @example\n * ```typescript\n * buildPath('/users/:userId/posts/:postId', { userId: '123', postId: '456' })\n * // '/users/123/posts/456'\n *\n * buildPath('/users/{userId}', { userId: '123' })\n * // '/users/123'\n * ```\n */\nexport function buildPath(template: string, params: Record<string, string>): string {\n let result = template;\n\n // Replace :param syntax\n for (const [key, value] of Object.entries(params)) {\n result = result.replace(`:${key}`, encodeURIComponent(value));\n result = result.replace(`{${key}}`, encodeURIComponent(value));\n }\n\n return result;\n}\n\n/**\n * Normalizes a path template to use consistent :param syntax.\n *\n * @example\n * ```typescript\n * normalizePath('/users/{userId}')\n * // '/users/:userId'\n * ```\n */\nexport function normalizePath(path: string): string {\n return path.replace(/\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}/g, ':$1');\n}\n","/**\n * @fileoverview Router definition types for grouping routes.\n *\n * A router is a hierarchical grouping of routes that enables:\n * - Organized API structure\n * - Nested client method generation\n * - Grouped OpenAPI tags\n *\n * @module unified/route/types/router-definition\n */\n\nimport type { RouteDefinition } from './route-definition.type';\nimport type { HttpMethod } from './http.type';\nimport type { SchemaAdapter } from '../../schema/types';\n\n// ============================================================================\n// Router Types\n// ============================================================================\n\n/**\n * A router entry can be a route definition, a nested router config, or a router definition.\n */\nexport type RouterEntry =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | RouteDefinition<HttpMethod, string, unknown, unknown, unknown, unknown, unknown, any>\n | RouterConfig\n | RouterDefinition;\n\n/**\n * Configuration for a router (group of routes).\n */\nexport interface RouterConfig {\n readonly [key: string]: RouterEntry;\n}\n\n/**\n * Router-level defaults applied to all child routes.\n */\nexport interface RouterDefaults {\n /**\n * Default tags for all routes in this router.\n * Merged with route-specific tags.\n */\n readonly tags?: readonly string[];\n\n /**\n * Default context schema for all routes in this router.\n * Applied to routes that don't define their own context.\n */\n readonly context?: SchemaAdapter;\n}\n\n/**\n * A fully defined router.\n */\nexport interface RouterDefinition<T extends RouterConfig = RouterConfig> {\n /**\n * The routes and nested routers in this router.\n */\n readonly routes: T;\n\n /**\n * Base path prefix for all routes in this router.\n */\n readonly basePath?: string;\n\n /**\n * Default values applied to all child routes.\n */\n readonly defaults?: RouterDefaults;\n\n /**\n * Marker to identify this as a router.\n * @internal\n */\n readonly _isRouter: true;\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Checks if a value is a RouteDefinition.\n */\nexport function isRouteDefinition(value: unknown): value is RouteDefinition {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'method' in value &&\n 'path' in value &&\n '_types' in value\n );\n}\n\n/**\n * Checks if a value is a RouterDefinition.\n */\nexport function isRouterDefinition(value: unknown): value is RouterDefinition {\n return (\n typeof value === 'object' &&\n value !== null &&\n '_isRouter' in value &&\n (value as RouterDefinition)._isRouter === true\n );\n}\n\n// ============================================================================\n// Utility Types\n// ============================================================================\n\n/**\n * Flattens a router into a map of path keys to route definitions.\n */\nexport type FlattenRouter<\n T extends RouterConfig,\n Prefix extends string = '',\n> = T extends RouterConfig\n ? {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [K in keyof T]: T[K] extends RouteDefinition<any, any, any, any, any, any, any, any>\n ? { [P in `${Prefix}${K & string}`]: T[K] }\n : T[K] extends RouterConfig\n ? FlattenRouter<T[K], `${Prefix}${K & string}.`>\n : never;\n }[keyof T] extends infer U\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n U extends Record<string, RouteDefinition<any, any, any, any, any, any, any, any>>\n ? U\n : never\n : never\n : never;\n\n/**\n * Gets all route keys from a router.\n */\nexport type RouterKeys<T extends RouterConfig, Prefix extends string = ''> = T extends RouterConfig\n ? {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [K in keyof T]: T[K] extends RouteDefinition<any, any, any, any, any, any, any, any>\n ? `${Prefix}${K & string}`\n : T[K] extends RouterConfig\n ? RouterKeys<T[K], `${Prefix}${K & string}.`>\n : never;\n }[keyof T]\n : never;\n\n/**\n * Gets a route by its dotted key path.\n */\nexport type GetRoute<\n T extends RouterConfig,\n K extends string,\n> = K extends `${infer Head}.${infer Tail}`\n ? Head extends keyof T\n ? T[Head] extends RouterConfig\n ? GetRoute<T[Head], Tail>\n : never\n : never\n : K extends keyof T\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T[K] extends RouteDefinition<any, any, any, any, any, any, any, any>\n ? T[K]\n : never\n : never;\n\n// ============================================================================\n// Deep Merge Types\n// ============================================================================\n\n/**\n * Deep-merges two router configs at the type level.\n */\nexport type DeepMergeTwo<A extends RouterConfig, B extends RouterConfig> = {\n readonly [K in keyof A | keyof B]: K extends keyof A\n ? K extends keyof B\n ? A[K] extends RouterConfig\n ? B[K] extends RouterConfig\n ? DeepMergeTwo<A[K], B[K]>\n : B[K]\n : B[K]\n : A[K]\n : K extends keyof B\n ? B[K]\n : never;\n};\n\n/**\n * Recursively deep-merges N router configs left-to-right.\n */\nexport type DeepMergeAll<T extends readonly RouterConfig[]> = T extends readonly [\n infer Only extends RouterConfig,\n]\n ? Only\n : T extends readonly [\n infer First extends RouterConfig,\n infer Second extends RouterConfig,\n ...infer Rest extends readonly RouterConfig[],\n ]\n ? DeepMergeAll<[DeepMergeTwo<First, Second>, ...Rest]>\n : RouterConfig;\n\n/**\n * Recursively flattens complex types for clean IDE hover display.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type PrettifyDeep<T> = T extends (...args: any[]) => any\n ? T\n : T extends object\n ? { readonly [K in keyof T]: PrettifyDeep<T[K]> }\n : T;\n\n/**\n * Collects all routes from a router into an array.\n */\nexport function collectRoutes(\n config: RouterConfig,\n basePath = '',\n): { key: string; route: RouteDefinition }[] {\n const routes: { key: string; route: RouteDefinition }[] = [];\n\n for (const [key, value] of Object.entries(config)) {\n const fullKey = basePath ? `${basePath}.${key}` : key;\n\n if (isRouteDefinition(value)) {\n routes.push({ key: fullKey, route: value });\n } else if (isRouterDefinition(value)) {\n routes.push(...collectRoutes(value.routes, fullKey));\n } else if (typeof value === 'object' && value !== null) {\n routes.push(...collectRoutes(value as RouterConfig, fullKey));\n }\n }\n\n return routes;\n}\n","/**\n * @fileoverview Route utility functions.\n *\n * @module unified/route/utils\n */\n\n/**\n * Generates an operationId from a router key path.\n *\n * Converts dotted key paths to camelCase:\n * - `\"users.list\"` → `\"usersList\"`\n * - `\"organizations.members.get\"` → `\"organizationsMembersGet\"`\n *\n * @param key - The dotted router key path\n * @returns A camelCase operationId string\n */\nexport function generateOperationId(key: string): string {\n return key\n .split('.')\n .map((segment, index) =>\n index === 0 ? segment : segment.charAt(0).toUpperCase() + segment.slice(1),\n )\n .join('');\n}\n"],"mappings":";AAuFO,SAAS,YAAY,MAAsB;AAChD,QAAM,UAAU,KAEb,QAAQ,uBAAuB,MAAM,EAErC,QAAQ,8BAA8B,SAAS,EAE/C,QAAQ,qCAAqC,SAAS;AAEzD,SAAO,IAAI,OAAO,IAAI,OAAO,KAAK;AACpC;AAWO,SAAS,kBAAkB,MAAwB;AAExD,QAAM,cAAc,CAAC,GAAG,KAAK,SAAS,4BAA4B,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAE;AAErF,QAAM,cAAc,CAAC,GAAG,KAAK,SAAS,+BAA+B,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAE;AACxF,SAAO,CAAC,GAAG,aAAa,GAAG,WAAW;AACxC;AAKO,SAAS,cAAc,MAAuB;AACnD,SAAO,4BAA4B,KAAK,IAAI,KAAK,+BAA+B,KAAK,IAAI;AAC3F;AAcO,SAAS,UAAU,UAAkB,QAAwC;AAClF,MAAI,SAAS;AAGb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,aAAS,OAAO,QAAQ,IAAI,GAAG,IAAI,mBAAmB,KAAK,CAAC;AAC5D,aAAS,OAAO,QAAQ,IAAI,GAAG,KAAK,mBAAmB,KAAK,CAAC;AAAA,EAC/D;AAEA,SAAO;AACT;AAWO,SAAS,cAAc,MAAsB;AAClD,SAAO,KAAK,QAAQ,iCAAiC,KAAK;AAC5D;;;ACzEO,SAAS,kBAAkB,OAA0C;AAC1E,SACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,UAAU,SACV,YAAY;AAEhB;AAKO,SAAS,mBAAmB,OAA2C;AAC5E,SACE,OAAO,UAAU,YACjB,UAAU,QACV,eAAe,SACd,MAA2B,cAAc;AAE9C;AA8GO,SAAS,cACd,QACA,WAAW,IACgC;AAC3C,QAAM,SAAoD,CAAC;AAE3D,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAM,UAAU,WAAW,GAAG,QAAQ,IAAI,GAAG,KAAK;AAElD,QAAI,kBAAkB,KAAK,GAAG;AAC5B,aAAO,KAAK,EAAE,KAAK,SAAS,OAAO,MAAM,CAAC;AAAA,IAC5C,WAAW,mBAAmB,KAAK,GAAG;AACpC,aAAO,KAAK,GAAG,cAAc,MAAM,QAAQ,OAAO,CAAC;AAAA,IACrD,WAAW,OAAO,UAAU,YAAY,UAAU,MAAM;AACtD,aAAO,KAAK,GAAG,cAAc,OAAuB,OAAO,CAAC;AAAA,IAC9D;AAAA,EACF;AAEA,SAAO;AACT;;;AC1NO,SAAS,oBAAoB,KAAqB;AACvD,SAAO,IACJ,MAAM,GAAG,EACT;AAAA,IAAI,CAAC,SAAS,UACb,UAAU,IAAI,UAAU,QAAQ,OAAO,CAAC,EAAE,YAAY,IAAI,QAAQ,MAAM,CAAC;AAAA,EAC3E,EACC,KAAK,EAAE;AACZ;","names":[]}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mapErrorToEventResult
|
|
3
|
+
} from "./chunk-4BVOLXDJ.js";
|
|
4
|
+
import {
|
|
5
|
+
collectEventHandlers,
|
|
6
|
+
generateHandlerId,
|
|
7
|
+
isEventRouterDefinition
|
|
8
|
+
} from "./chunk-CBTICRSM.js";
|
|
9
|
+
|
|
10
|
+
// src/presentation/events/server/types.ts
|
|
11
|
+
function isSimpleEventHandlerConfig(config) {
|
|
12
|
+
return "handler" in config && typeof config.handler === "function";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/presentation/events/server/create-event-routes.ts
|
|
16
|
+
function createEventRoutesInternal(router, handlers, options) {
|
|
17
|
+
const config = isEventRouterDefinition(router) ? router.handlers : router;
|
|
18
|
+
const collectedHandlers = collectEventHandlers(config);
|
|
19
|
+
const result = [];
|
|
20
|
+
const resolvedOptions = {
|
|
21
|
+
...options,
|
|
22
|
+
validatePayload: options?.validatePayload ?? true,
|
|
23
|
+
allowPartial: options?.allowPartial ?? false
|
|
24
|
+
};
|
|
25
|
+
for (const { key, handler: handlerDef } of collectedHandlers) {
|
|
26
|
+
const handlerConfig = handlers[key];
|
|
27
|
+
if (!handlerConfig) {
|
|
28
|
+
if (resolvedOptions.allowPartial) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Missing handler for event "${key}". All event handlers must have a handler configuration.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
result.push(createEventHandler(key, handlerDef, handlerConfig, resolvedOptions));
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function createEventHandler(key, handlerDef, config, options) {
|
|
40
|
+
const middleware = config.middleware ?? [];
|
|
41
|
+
const globalMiddleware = options?.middleware ?? [];
|
|
42
|
+
const allMiddleware = [...globalMiddleware, ...middleware];
|
|
43
|
+
const shouldValidatePayload = options.validatePayload ?? true;
|
|
44
|
+
const errorMapper = options.errorMapper ?? mapErrorToEventResult;
|
|
45
|
+
return {
|
|
46
|
+
eventType: handlerDef.eventType,
|
|
47
|
+
metadata: {
|
|
48
|
+
handlerId: generateHandlerId(key),
|
|
49
|
+
summary: handlerDef.docs.summary,
|
|
50
|
+
description: handlerDef.docs.description,
|
|
51
|
+
tags: handlerDef.docs.tags,
|
|
52
|
+
deprecated: handlerDef.docs.deprecated
|
|
53
|
+
},
|
|
54
|
+
handler: async (rawEvent) => {
|
|
55
|
+
try {
|
|
56
|
+
let validatedContext = rawEvent.metadata;
|
|
57
|
+
if (handlerDef.context) {
|
|
58
|
+
const contextResult = validateContextData(handlerDef, rawEvent.metadata);
|
|
59
|
+
if (!contextResult.success) {
|
|
60
|
+
const errors = contextResult.errors ?? [];
|
|
61
|
+
return {
|
|
62
|
+
outcome: "dlq",
|
|
63
|
+
reason: `Context validation failed: ${errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
validatedContext = contextResult.data;
|
|
67
|
+
}
|
|
68
|
+
let validatedPayload = rawEvent.payload;
|
|
69
|
+
if (shouldValidatePayload && handlerDef.payload) {
|
|
70
|
+
const payloadResult = validatePayloadData(handlerDef, rawEvent.payload);
|
|
71
|
+
if (!payloadResult.success) {
|
|
72
|
+
const errors = payloadResult.errors ?? [];
|
|
73
|
+
return {
|
|
74
|
+
outcome: "dlq",
|
|
75
|
+
reason: `Payload validation failed: ${errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
validatedPayload = payloadResult.data;
|
|
79
|
+
}
|
|
80
|
+
const validatedEvent = {
|
|
81
|
+
payload: validatedPayload,
|
|
82
|
+
raw: rawEvent
|
|
83
|
+
};
|
|
84
|
+
const executePipeline = async () => {
|
|
85
|
+
if (isSimpleEventHandlerConfig(config)) {
|
|
86
|
+
return config.handler(
|
|
87
|
+
validatedEvent,
|
|
88
|
+
validatedContext
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
const { payloadMapper, useCase, resultMapper } = config;
|
|
92
|
+
const input = payloadMapper(
|
|
93
|
+
validatedEvent,
|
|
94
|
+
validatedContext
|
|
95
|
+
);
|
|
96
|
+
const output = await useCase.execute(input);
|
|
97
|
+
return resultMapper ? resultMapper(output) : { outcome: "ack" };
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
if (allMiddleware.length === 0) {
|
|
101
|
+
return await executePipeline();
|
|
102
|
+
}
|
|
103
|
+
let index = 0;
|
|
104
|
+
const next = async () => {
|
|
105
|
+
if (index >= allMiddleware.length) {
|
|
106
|
+
return executePipeline();
|
|
107
|
+
}
|
|
108
|
+
const mw = allMiddleware[index++];
|
|
109
|
+
return mw(rawEvent, next);
|
|
110
|
+
};
|
|
111
|
+
return await next();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return errorMapper(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function validatePayloadData(handler, payload) {
|
|
119
|
+
const schema = handler.payload;
|
|
120
|
+
if (!schema) return { success: true, data: payload };
|
|
121
|
+
const result = schema.validate(payload);
|
|
122
|
+
if (result.success) {
|
|
123
|
+
return { success: true, data: result.data };
|
|
124
|
+
}
|
|
125
|
+
const errors = result.issues.map((issue) => ({
|
|
126
|
+
...issue,
|
|
127
|
+
path: ["payload", ...issue.path]
|
|
128
|
+
}));
|
|
129
|
+
return { success: false, errors };
|
|
130
|
+
}
|
|
131
|
+
function validateContextData(handler, metadata) {
|
|
132
|
+
const schema = handler.context;
|
|
133
|
+
if (!schema) return { success: true, data: metadata };
|
|
134
|
+
const result = schema.validate(metadata);
|
|
135
|
+
if (result.success) {
|
|
136
|
+
return { success: true, data: result.data };
|
|
137
|
+
}
|
|
138
|
+
const errors = result.issues.map((issue) => ({
|
|
139
|
+
...issue,
|
|
140
|
+
path: ["context", ...issue.path]
|
|
141
|
+
}));
|
|
142
|
+
return { success: false, errors };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/presentation/events/server/event-routes-builder.ts
|
|
146
|
+
var EventRoutesBuilderImpl = class _EventRoutesBuilderImpl {
|
|
147
|
+
router;
|
|
148
|
+
handlers;
|
|
149
|
+
constructor(router, handlers) {
|
|
150
|
+
this.router = router;
|
|
151
|
+
this.handlers = handlers ?? /* @__PURE__ */ new Map();
|
|
152
|
+
}
|
|
153
|
+
handle(key, handlerOrConfig) {
|
|
154
|
+
const config = typeof handlerOrConfig === "function" ? { handler: handlerOrConfig } : handlerOrConfig;
|
|
155
|
+
const newHandlers = new Map(this.handlers);
|
|
156
|
+
newHandlers.set(key, config);
|
|
157
|
+
return new _EventRoutesBuilderImpl(
|
|
158
|
+
this.router,
|
|
159
|
+
newHandlers
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
handleWithUseCase(key, config) {
|
|
163
|
+
const newHandlers = new Map(this.handlers);
|
|
164
|
+
newHandlers.set(
|
|
165
|
+
key,
|
|
166
|
+
config
|
|
167
|
+
);
|
|
168
|
+
return new _EventRoutesBuilderImpl(
|
|
169
|
+
this.router,
|
|
170
|
+
newHandlers
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
build(options) {
|
|
174
|
+
return createEventRoutesInternal(this.router, Object.fromEntries(this.handlers), options);
|
|
175
|
+
}
|
|
176
|
+
buildPartial(options) {
|
|
177
|
+
return createEventRoutesInternal(this.router, Object.fromEntries(this.handlers), {
|
|
178
|
+
...options,
|
|
179
|
+
allowPartial: true
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
function eventRoutes(router) {
|
|
184
|
+
return new EventRoutesBuilderImpl(router);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export {
|
|
188
|
+
isSimpleEventHandlerConfig,
|
|
189
|
+
eventRoutes
|
|
190
|
+
};
|
|
191
|
+
//# sourceMappingURL=chunk-XWKHOLIP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/presentation/events/server/types.ts","../src/presentation/events/server/create-event-routes.ts","../src/presentation/events/server/event-routes-builder.ts"],"sourcesContent":["/**\n * @fileoverview Server types for the event handler system.\n *\n * @module events/server/types\n */\n\nimport type { EventHandlerDefinition, EventRouterConfig, EventRouterKeys } from '../handler/types';\nimport type { EventResult } from '../shared/types';\n\n// Re-export UseCasePort from HTTP — same interface, no duplication\nexport type { UseCasePort } from '../../http/server/types';\n\n// ============================================================================\n// Raw Event (from messaging system)\n// ============================================================================\n\n/**\n * Event metadata provided by the messaging system.\n */\nexport interface EventMetadata {\n /** Unique event identifier for idempotency checks. */\n readonly eventId: string;\n\n /** ISO 8601 timestamp of when the event occurred. */\n readonly timestamp: string;\n\n /** Correlation ID for distributed tracing. */\n readonly correlationId?: string;\n\n /** Source system or bounded context that emitted the event. */\n readonly source?: string;\n\n /** Number of delivery attempts (1-based). */\n readonly attemptCount?: number;\n\n /** Additional metadata from the messaging system. */\n readonly [key: string]: unknown;\n}\n\n/**\n * Raw event from the messaging system.\n * This is the input to event handlers before validation.\n */\nexport interface RawEvent {\n /** Event type string for routing. */\n readonly type: string;\n\n /** Event payload (unvalidated). */\n readonly payload: unknown;\n\n /** Event metadata from the messaging system. */\n readonly metadata: EventMetadata;\n}\n\n// ============================================================================\n// Validated Event\n// ============================================================================\n\n/**\n * A validated event with typed payload.\n * This is what handlers receive after validation passes.\n */\nexport interface ValidatedEvent<THandler extends EventHandlerDefinition> {\n /** Validated event payload. */\n readonly payload: THandler['_types']['payload'];\n\n /** Raw event object for advanced use cases. */\n readonly raw: RawEvent;\n}\n\n/**\n * Typed event context based on handler definition.\n * If the handler defines a context schema, this will be the validated type.\n * Otherwise, it falls back to the generic EventMetadata.\n */\nexport type TypedEventContext<THandler extends EventHandlerDefinition> =\n THandler['_types']['context'] extends undefined ? EventMetadata : THandler['_types']['context'];\n\n// ============================================================================\n// Handler Types\n// ============================================================================\n\n/**\n * Handler configuration using the use case pattern.\n *\n * @typeParam THandler - The event handler definition type\n * @typeParam TInput - Use case input type\n * @typeParam TOutput - Use case output type\n */\nexport interface EventHandlerConfig<\n THandler extends EventHandlerDefinition,\n TInput = void,\n TOutput = void,\n> {\n /**\n * Maps the validated event payload to use case input.\n * Both `event` and `ctx` are fully typed based on handler schemas.\n */\n readonly payloadMapper: (\n event: ValidatedEvent<THandler>,\n ctx: TypedEventContext<THandler>,\n ) => TInput;\n\n /** The use case to execute. */\n readonly useCase: { execute(input?: TInput): Promise<TOutput> };\n\n /**\n * Maps the use case output to an EventResult.\n * If omitted, defaults to `{ outcome: 'ack' }`.\n */\n readonly resultMapper?: (output: TOutput) => EventResult;\n\n /** Middleware to run before the handler. */\n readonly middleware?: readonly EventMiddlewareFunction[];\n}\n\n/**\n * Simple handler function that directly returns an EventResult.\n */\nexport type SimpleEventHandlerFn<THandler extends EventHandlerDefinition> = (\n event: ValidatedEvent<THandler>,\n ctx: TypedEventContext<THandler>,\n) => Promise<EventResult> | EventResult;\n\n/**\n * Configuration for a simple handler (no use case).\n */\nexport interface SimpleEventHandlerConfig<THandler extends EventHandlerDefinition> {\n readonly handler: SimpleEventHandlerFn<THandler>;\n readonly middleware?: readonly EventMiddlewareFunction[];\n}\n\n/**\n * Union of all event handler config types.\n * Used internally to store handlers in the builder.\n */\nexport type AnyEventHandlerConfig<\n THandler extends EventHandlerDefinition,\n TInput = unknown,\n TOutput = unknown,\n> = EventHandlerConfig<THandler, TInput, TOutput> | SimpleEventHandlerConfig<THandler>;\n\n/**\n * Type guard to check if config is a simple event handler.\n */\nexport function isSimpleEventHandlerConfig(\n config: AnyEventHandlerConfig<EventHandlerDefinition, unknown, unknown>,\n): config is SimpleEventHandlerConfig<EventHandlerDefinition> {\n return 'handler' in config && typeof config.handler === 'function';\n}\n\n/**\n * Event middleware function type.\n */\nexport type EventMiddlewareFunction = (\n event: RawEvent,\n next: () => Promise<EventResult>,\n) => Promise<EventResult>;\n\n// ============================================================================\n// Server Configuration\n// ============================================================================\n\n/**\n * Configuration mapping handler keys to handler configs.\n */\n// TInput/TOutput are user-defined per handler - any is required for heterogeneous configs\nexport type EventRoutesConfig<T extends EventRouterConfig> = Record<\n EventRouterKeys<T>,\n EventHandlerConfig<any, any, any>\n>;\n\n/**\n * Options for creating event routes.\n */\nexport interface CreateEventRoutesOptions {\n /** Global middleware to run before all handlers. */\n readonly middleware?: readonly EventMiddlewareFunction[];\n\n /**\n * Whether to validate incoming event payloads against handler schemas.\n * When enabled, invalid payloads result in a `dlq` outcome.\n * @default true\n */\n readonly validatePayload?: boolean;\n\n /**\n * Custom error mapper to override default error-to-EventResult mapping.\n * If not provided, uses the built-in `mapErrorToEventResult`.\n */\n readonly errorMapper?: (error: unknown) => EventResult;\n\n /**\n * Allow partial handler configuration (not all handlers need to be wired).\n * @default false\n * @internal Used by builder pattern's buildPartial()\n */\n readonly allowPartial?: boolean;\n}\n\n// ============================================================================\n// Unified Event Input (for messaging adapters)\n// ============================================================================\n\n/**\n * Event input compatible with messaging adapters.\n * This is the output of eventRoutes().build().\n */\nexport interface UnifiedEventInput {\n /** Event type string for routing. */\n readonly eventType: string;\n\n /** Handler function. */\n readonly handler: (rawEvent: RawEvent) => Promise<EventResult>;\n\n /** Handler metadata for documentation. */\n readonly metadata: {\n readonly handlerId?: string;\n readonly summary?: string;\n readonly description?: string;\n readonly tags?: readonly string[];\n readonly deprecated?: boolean;\n };\n}\n","/**\n * @fileoverview Internal implementation for creating event routes with auto-validation.\n *\n * Generates event handlers from an event router definition.\n * Each handler automatically validates incoming event payloads and context\n * against the handler's schemas.\n *\n * @module events/server/create-event-routes\n * @internal\n */\n\nimport type { SchemaAdapter, ValidationIssue } from '../../http/schema/types';\nimport type {\n EventRouterConfig,\n EventRouterDefinition,\n EventHandlerDefinition,\n} from '../handler/types';\nimport { isEventRouterDefinition, collectEventHandlers } from '../handler/types';\nimport { generateHandlerId } from '../handler/utils';\nimport { mapErrorToEventResult } from '../shared/error-mapping';\nimport type { EventResult } from '../shared/types';\nimport type {\n AnyEventHandlerConfig,\n CreateEventRoutesOptions,\n EventMetadata,\n RawEvent,\n UnifiedEventInput,\n ValidatedEvent,\n} from './types';\nimport { isSimpleEventHandlerConfig } from './types';\n\n/**\n * Internal implementation for creating event routes.\n * Used by the builder pattern (eventRoutes).\n *\n * @internal\n */\nexport function createEventRoutesInternal<T extends EventRouterConfig>(\n router: T | EventRouterDefinition<T>,\n handlers: Record<string, AnyEventHandlerConfig<EventHandlerDefinition, unknown, unknown>>,\n options?: CreateEventRoutesOptions,\n): UnifiedEventInput[] {\n const config = isEventRouterDefinition(router) ? router.handlers : router;\n const collectedHandlers = collectEventHandlers(config);\n\n const result: UnifiedEventInput[] = [];\n\n const resolvedOptions: CreateEventRoutesOptions = {\n ...options,\n validatePayload: options?.validatePayload ?? true,\n allowPartial: options?.allowPartial ?? false,\n };\n\n for (const { key, handler: handlerDef } of collectedHandlers) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handlerConfig = handlers[key] as\n | AnyEventHandlerConfig<EventHandlerDefinition, any, any>\n | undefined;\n\n if (!handlerConfig) {\n if (resolvedOptions.allowPartial) {\n continue;\n }\n throw new Error(\n `Missing handler for event \"${key}\". All event handlers must have a handler configuration.`,\n );\n }\n\n result.push(createEventHandler(key, handlerDef, handlerConfig, resolvedOptions));\n }\n\n return result;\n}\n\n/**\n * Creates a single event handler with validation and error mapping.\n */\nfunction createEventHandler(\n key: string,\n handlerDef: EventHandlerDefinition,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config: AnyEventHandlerConfig<EventHandlerDefinition, any, any>,\n options: CreateEventRoutesOptions,\n): UnifiedEventInput {\n const middleware = config.middleware ?? [];\n const globalMiddleware = options?.middleware ?? [];\n const allMiddleware = [...globalMiddleware, ...middleware];\n const shouldValidatePayload = options.validatePayload ?? true;\n const errorMapper = options.errorMapper ?? mapErrorToEventResult;\n\n return {\n eventType: handlerDef.eventType,\n metadata: {\n handlerId: generateHandlerId(key),\n summary: handlerDef.docs.summary,\n description: handlerDef.docs.description,\n tags: handlerDef.docs.tags as string[],\n deprecated: handlerDef.docs.deprecated,\n },\n handler: async (rawEvent: RawEvent): Promise<EventResult> => {\n try {\n // Validate context (if schema defined)\n let validatedContext: unknown = rawEvent.metadata;\n if (handlerDef.context) {\n const contextResult = validateContextData(handlerDef, rawEvent.metadata);\n if (!contextResult.success) {\n const errors = contextResult.errors ?? [];\n return {\n outcome: 'dlq',\n reason: `Context validation failed: ${errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ')}`,\n };\n }\n validatedContext = contextResult.data;\n }\n\n // Validate payload (if enabled and schema defined)\n let validatedPayload: unknown = rawEvent.payload;\n if (shouldValidatePayload && handlerDef.payload) {\n const payloadResult = validatePayloadData(handlerDef, rawEvent.payload);\n if (!payloadResult.success) {\n const errors = payloadResult.errors ?? [];\n return {\n outcome: 'dlq',\n reason: `Payload validation failed: ${errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ')}`,\n };\n }\n validatedPayload = payloadResult.data;\n }\n\n const validatedEvent: ValidatedEventInternal = {\n payload: validatedPayload,\n raw: rawEvent,\n };\n\n // Execute the pipeline\n const executePipeline = async (): Promise<EventResult> => {\n if (isSimpleEventHandlerConfig(config)) {\n return config.handler(\n validatedEvent as unknown as ValidatedEvent<EventHandlerDefinition>,\n validatedContext as EventMetadata,\n );\n } else {\n const { payloadMapper, useCase, resultMapper } = config;\n\n const input = payloadMapper(\n validatedEvent as unknown as ValidatedEvent<EventHandlerDefinition>,\n validatedContext as EventMetadata,\n );\n\n const output = await useCase.execute(input);\n\n return resultMapper ? resultMapper(output) : { outcome: 'ack' };\n }\n };\n\n if (allMiddleware.length === 0) {\n return await executePipeline();\n }\n\n // Build middleware chain\n let index = 0;\n const next = async (): Promise<EventResult> => {\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(rawEvent, next);\n };\n\n return await next();\n } catch (error) {\n return errorMapper(error);\n }\n },\n };\n}\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\ninterface ValidationResultInternal {\n success: boolean;\n errors?: ValidationIssue[];\n data?: unknown;\n}\n\n/**\n * Validates event payload against the handler's payload schema.\n */\nfunction validatePayloadData(\n handler: EventHandlerDefinition,\n payload: unknown,\n): ValidationResultInternal {\n const schema = handler.payload as SchemaAdapter | undefined;\n if (!schema) return { success: true, data: payload };\n\n const result = schema.validate(payload);\n if (result.success) {\n return { success: true, data: result.data };\n }\n\n const errors = result.issues.map((issue) => ({\n ...issue,\n path: ['payload', ...issue.path],\n }));\n\n return { success: false, errors };\n}\n\n/**\n * Validates event metadata against the handler's context schema.\n */\nfunction validateContextData(\n handler: EventHandlerDefinition,\n metadata: EventMetadata,\n): ValidationResultInternal {\n const schema = handler.context as SchemaAdapter | undefined;\n if (!schema) return { success: true, data: metadata };\n\n const result = schema.validate(metadata);\n if (result.success) {\n return { success: true, data: result.data };\n }\n\n const errors = result.issues.map((issue) => ({\n ...issue,\n path: ['context', ...issue.path],\n }));\n\n return { success: false, errors };\n}\n\n/**\n * Internal validated event type with unknown fields.\n * Used inside createEventHandler where specific types are erased.\n */\ninterface ValidatedEventInternal {\n readonly payload: unknown;\n readonly raw: RawEvent;\n}\n","/**\n * @fileoverview Builder pattern for creating type-safe event routes.\n *\n * The `eventRoutes` function returns a builder that provides 100% type inference\n * for all handler parameters — no manual type annotations required.\n *\n * @module events/server/event-routes-builder\n */\n\nimport type {\n EventRouterConfig,\n EventRouterDefinition,\n GetEventHandler,\n EventRouterKeys,\n EventHandlerDefinition,\n} from '../handler/types';\nimport type { EventResult } from '../shared/types';\nimport type {\n AnyEventHandlerConfig,\n CreateEventRoutesOptions,\n EventHandlerConfig,\n EventMiddlewareFunction,\n SimpleEventHandlerConfig,\n SimpleEventHandlerFn,\n TypedEventContext,\n UnifiedEventInput,\n ValidatedEvent,\n} from './types';\nimport { createEventRoutesInternal } from './create-event-routes';\n\n// ============================================================================\n// Builder Types\n// ============================================================================\n\n/**\n * Error type displayed when attempting to build() with missing handlers.\n * The `___missingHandlers` property shows which handlers are missing.\n */\nexport interface MissingHandlersError<TMissing extends string> {\n /**\n * This error indicates that not all event handlers have been wired.\n * Use buildPartial() to build with only the defined handlers,\n * or add handlers for the missing events.\n */\n (options?: never): never;\n /** Event handlers that are missing. */\n readonly ___missingHandlers: TMissing;\n}\n\n/**\n * Handler configuration for the builder pattern.\n */\nexport interface BuilderEventHandlerConfig<\n THandler extends EventHandlerDefinition,\n TInput,\n TOutput,\n> {\n /**\n * Maps the validated event payload to use case input.\n * Both `event` and `ctx` are fully typed based on handler schemas.\n */\n readonly payloadMapper: (\n event: ValidatedEvent<THandler>,\n ctx: TypedEventContext<THandler>,\n ) => TInput;\n\n /** The use case to execute. */\n readonly useCase: { execute(input?: TInput): Promise<TOutput> };\n\n /**\n * Maps the use case output to an EventResult.\n * If omitted, defaults to `{ outcome: 'ack' }`.\n */\n readonly resultMapper?: (output: TOutput) => EventResult;\n\n /** Middleware to run before the handler. */\n readonly middleware?: readonly EventMiddlewareFunction[];\n}\n\n/**\n * Builder interface for creating type-safe event routes.\n *\n * Each `.handle()` call captures the specific handler type and provides\n * full type inference for payloadMapper and useCase.\n *\n * @typeParam T - The event router configuration type\n * @typeParam THandled - Union of handler keys that have been wired (accumulates)\n *\n * @example\n * ```typescript\n * const routes = eventRoutes(ticketEvents)\n * .handleWithUseCase('created', {\n * payloadMapper: (event, ctx) => ({\n * ticketId: event.payload.ticketId, // Fully typed!\n * correlationId: ctx.correlationId, // Fully typed!\n * }),\n * useCase: sendNotificationUseCase,\n * })\n * .handle('assigned', async (event) => {\n * await notifyAssignee(event.payload);\n * return { outcome: 'ack' as const };\n * })\n * .build();\n * ```\n */\nexport interface EventRoutesBuilder<T extends EventRouterConfig, THandled extends string = never> {\n /**\n * Register a simple handler for an event.\n * The handler receives validated event and context, returns EventResult directly.\n */\n handle<K extends Exclude<EventRouterKeys<T>, THandled>>(\n key: K,\n handlerOrConfig:\n | SimpleEventHandlerFn<GetEventHandler<T, K>>\n | SimpleEventHandlerConfig<GetEventHandler<T, K>>,\n ): EventRoutesBuilder<T, THandled | K>;\n\n /**\n * Register a handler using the use case pattern.\n * Follows: payloadMapper → useCase.execute() → resultMapper (or ack)\n */\n handleWithUseCase<K extends Exclude<EventRouterKeys<T>, THandled>, TInput, TOutput>(\n key: K,\n config: BuilderEventHandlerConfig<GetEventHandler<T, K>, TInput, TOutput>,\n ): EventRoutesBuilder<T, THandled | K>;\n\n /**\n * Build the event routes array for messaging adapter registration.\n *\n * This method is only available when ALL handlers have been wired.\n * If some handlers are missing, use `buildPartial()` instead.\n */\n build: [Exclude<EventRouterKeys<T>, THandled>] extends [never]\n ? (options?: CreateEventRoutesOptions) => UnifiedEventInput[]\n : MissingHandlersError<Exclude<EventRouterKeys<T>, THandled>>;\n\n /**\n * Build event routes for only the defined handlers.\n * No compile-time enforcement of completeness.\n */\n buildPartial(options?: CreateEventRoutesOptions): UnifiedEventInput[];\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 EventRoutesBuilderImpl<T extends EventRouterConfig, THandled extends string = never> {\n private readonly router: T | EventRouterDefinition<T>;\n private readonly handlers: Map<\n string,\n AnyEventHandlerConfig<EventHandlerDefinition, unknown, unknown>\n >;\n\n constructor(\n router: T | EventRouterDefinition<T>,\n handlers?: Map<string, AnyEventHandlerConfig<EventHandlerDefinition, unknown, unknown>>,\n ) {\n this.router = router;\n this.handlers = handlers ?? new Map();\n }\n\n handle<K extends Exclude<EventRouterKeys<T>, THandled>>(\n key: K,\n handlerOrConfig:\n | SimpleEventHandlerFn<GetEventHandler<T, K>>\n | SimpleEventHandlerConfig<GetEventHandler<T, K>>,\n ): EventRoutesBuilder<T, THandled | K> {\n const config: SimpleEventHandlerConfig<EventHandlerDefinition> =\n typeof handlerOrConfig === 'function'\n ? { handler: handlerOrConfig as SimpleEventHandlerFn<EventHandlerDefinition> }\n : (handlerOrConfig as SimpleEventHandlerConfig<EventHandlerDefinition>);\n\n const newHandlers = new Map(this.handlers);\n newHandlers.set(key as string, config);\n\n return new EventRoutesBuilderImpl<T, THandled | K>(\n this.router,\n newHandlers,\n ) as unknown as EventRoutesBuilder<T, THandled | K>;\n }\n\n handleWithUseCase<K extends Exclude<EventRouterKeys<T>, THandled>, TInput, TOutput>(\n key: K,\n config: BuilderEventHandlerConfig<GetEventHandler<T, K>, TInput, TOutput>,\n ): EventRoutesBuilder<T, THandled | K> {\n const newHandlers = new Map(this.handlers);\n newHandlers.set(\n key as string,\n config as EventHandlerConfig<EventHandlerDefinition, unknown, unknown>,\n );\n\n return new EventRoutesBuilderImpl<T, THandled | K>(\n this.router,\n newHandlers,\n ) as unknown as EventRoutesBuilder<T, THandled | K>;\n }\n\n build(options?: CreateEventRoutesOptions): UnifiedEventInput[] {\n return createEventRoutesInternal(this.router, Object.fromEntries(this.handlers), options);\n }\n\n buildPartial(options?: CreateEventRoutesOptions): UnifiedEventInput[] {\n return createEventRoutesInternal(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 event routes builder for an event router.\n *\n * The builder pattern provides 100% type inference for all handler parameters:\n * - `event.payload` is typed from the handler's payload schema\n * - `ctx` is typed from the handler's context schema (or EventMetadata)\n * - `output` in resultMapper is typed from the use case\n *\n * @param router - Event router definition or event router config\n * @returns Builder for registering handlers\n *\n * @example Basic usage\n * ```typescript\n * import { eventRoutes } from '@cosmneo/onion-lasagna/events/server';\n * import { ticketEvents } from './router';\n *\n * const routes = eventRoutes(ticketEvents)\n * .handleWithUseCase('created', {\n * payloadMapper: (event, ctx) => ({\n * ticketId: event.payload.ticketId,\n * correlationId: ctx.correlationId,\n * }),\n * useCase: sendNotificationUseCase,\n * })\n * .handle('assigned', async (event) => {\n * await notifyAssignee(event.payload);\n * return { outcome: 'ack' as const };\n * })\n * .build();\n * ```\n */\nexport function eventRoutes<T extends EventRouterConfig>(\n router: T | EventRouterDefinition<T>,\n): EventRoutesBuilder<T, never> {\n return new EventRoutesBuilderImpl(router) as unknown as EventRoutesBuilder<T, never>;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { SchemaAdapter } from './http/schema/types.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Core event handler definition types.
|
|
5
|
+
*
|
|
6
|
+
* An event handler definition captures all information needed for:
|
|
7
|
+
* - Type-safe event payload validation
|
|
8
|
+
* - Server-side event routing and dispatching
|
|
9
|
+
* - AsyncAPI / documentation generation
|
|
10
|
+
*
|
|
11
|
+
* @module events/handler/types/event-handler-definition
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Event handler documentation for AsyncAPI / docs generation.
|
|
16
|
+
*/
|
|
17
|
+
interface EventHandlerDocumentation {
|
|
18
|
+
/** Short summary of the handler. */
|
|
19
|
+
readonly summary?: string;
|
|
20
|
+
/** Detailed description. Supports Markdown. */
|
|
21
|
+
readonly description?: string;
|
|
22
|
+
/** Tags for grouping event handlers. */
|
|
23
|
+
readonly tags?: readonly string[];
|
|
24
|
+
/** Whether this handler is deprecated. @default false */
|
|
25
|
+
readonly deprecated?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A fully defined event handler with computed types.
|
|
29
|
+
* This is the output of `defineEventHandler()`.
|
|
30
|
+
*/
|
|
31
|
+
interface EventHandlerDefinition<TEventType extends string = string, TPayload = undefined, TContext = undefined> {
|
|
32
|
+
/** Event type string used for routing (e.g., 'ticket.created'). */
|
|
33
|
+
readonly eventType: TEventType;
|
|
34
|
+
/** Payload validation schema. */
|
|
35
|
+
readonly payload: SchemaAdapter | undefined;
|
|
36
|
+
/** Context validation schema (validates event metadata). */
|
|
37
|
+
readonly context: SchemaAdapter | undefined;
|
|
38
|
+
/** Handler documentation. */
|
|
39
|
+
readonly docs: EventHandlerDocumentation;
|
|
40
|
+
/**
|
|
41
|
+
* Phantom types for TypeScript inference.
|
|
42
|
+
* Never accessed at runtime.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
readonly _types: {
|
|
46
|
+
readonly eventType: TEventType;
|
|
47
|
+
readonly payload: TPayload;
|
|
48
|
+
readonly context: TContext;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Infers the event type string from a handler definition. */
|
|
52
|
+
type InferEventType<T> = T extends EventHandlerDefinition<infer TEventType, unknown, unknown> ? TEventType : never;
|
|
53
|
+
/** Infers the payload type from a handler definition. */
|
|
54
|
+
type InferEventPayload<T> = T extends EventHandlerDefinition<string, infer TPayload, unknown> ? TPayload : never;
|
|
55
|
+
/** Infers the context type from a handler definition. */
|
|
56
|
+
type InferEventContext<T> = T extends EventHandlerDefinition<string, unknown, infer TContext> ? TContext : never;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @fileoverview Event router definition types for grouping event handlers.
|
|
60
|
+
*
|
|
61
|
+
* Mirrors the HTTP router definition pattern with hierarchical grouping,
|
|
62
|
+
* dotted-key access, and deep merge support.
|
|
63
|
+
*
|
|
64
|
+
* @module events/handler/types/event-router-definition
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A router entry can be an event handler definition, a nested config, or a router definition.
|
|
69
|
+
*/
|
|
70
|
+
type EventRouterEntry = EventHandlerDefinition<string, any, any> | EventRouterConfig | EventRouterDefinition;
|
|
71
|
+
/**
|
|
72
|
+
* Configuration for an event router (group of event handlers).
|
|
73
|
+
*/
|
|
74
|
+
interface EventRouterConfig {
|
|
75
|
+
readonly [key: string]: EventRouterEntry;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Router-level defaults applied to all child event handlers.
|
|
79
|
+
*/
|
|
80
|
+
interface EventRouterDefaults {
|
|
81
|
+
/** Default tags for all handlers. Merged with handler-specific tags. */
|
|
82
|
+
readonly tags?: readonly string[];
|
|
83
|
+
/** Default context schema. Applied to handlers that don't define their own. */
|
|
84
|
+
readonly context?: SchemaAdapter;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A fully defined event router.
|
|
88
|
+
*/
|
|
89
|
+
interface EventRouterDefinition<T extends EventRouterConfig = EventRouterConfig> {
|
|
90
|
+
/** The handlers and nested routers in this router. */
|
|
91
|
+
readonly handlers: T;
|
|
92
|
+
/** Default values applied to all child handlers. */
|
|
93
|
+
readonly defaults?: EventRouterDefaults;
|
|
94
|
+
/**
|
|
95
|
+
* Marker to identify this as an event router.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
readonly _isEventRouter: true;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Checks if a value is an EventHandlerDefinition.
|
|
102
|
+
*/
|
|
103
|
+
declare function isEventHandlerDefinition(value: unknown): value is EventHandlerDefinition;
|
|
104
|
+
/**
|
|
105
|
+
* Checks if a value is an EventRouterDefinition.
|
|
106
|
+
*/
|
|
107
|
+
declare function isEventRouterDefinition(value: unknown): value is EventRouterDefinition;
|
|
108
|
+
/**
|
|
109
|
+
* Flattens an event router into a map of dotted keys to handler definitions.
|
|
110
|
+
*/
|
|
111
|
+
type FlattenEventRouter<T extends EventRouterConfig, Prefix extends string = ''> = T extends EventRouterConfig ? {
|
|
112
|
+
[K in keyof T]: T[K] extends EventHandlerDefinition<any, any, any> ? {
|
|
113
|
+
[P in `${Prefix}${K & string}`]: T[K];
|
|
114
|
+
} : T[K] extends EventRouterConfig ? FlattenEventRouter<T[K], `${Prefix}${K & string}.`> : never;
|
|
115
|
+
}[keyof T] extends infer U ? U extends Record<string, EventHandlerDefinition<any, any, any>> ? U : never : never : never;
|
|
116
|
+
/**
|
|
117
|
+
* Gets all handler keys from an event router.
|
|
118
|
+
*/
|
|
119
|
+
type EventRouterKeys<T extends EventRouterConfig, Prefix extends string = ''> = T extends EventRouterConfig ? {
|
|
120
|
+
[K in keyof T]: T[K] extends EventHandlerDefinition<any, any, any> ? `${Prefix}${K & string}` : T[K] extends EventRouterConfig ? EventRouterKeys<T[K], `${Prefix}${K & string}.`> : never;
|
|
121
|
+
}[keyof T] : never;
|
|
122
|
+
/**
|
|
123
|
+
* Gets an event handler by its dotted key path.
|
|
124
|
+
*/
|
|
125
|
+
type GetEventHandler<T extends EventRouterConfig, K extends string> = K extends `${infer Head}.${infer Tail}` ? Head extends keyof T ? T[Head] extends EventRouterConfig ? GetEventHandler<T[Head], Tail> : never : never : K extends keyof T ? T[K] extends EventHandlerDefinition<any, any, any> ? T[K] : never : never;
|
|
126
|
+
/**
|
|
127
|
+
* Deep-merges two event router configs at the type level.
|
|
128
|
+
*/
|
|
129
|
+
type DeepMergeTwo<A extends EventRouterConfig, B extends EventRouterConfig> = {
|
|
130
|
+
readonly [K in keyof A | keyof B]: K extends keyof A ? K extends keyof B ? A[K] extends EventRouterConfig ? B[K] extends EventRouterConfig ? DeepMergeTwo<A[K], B[K]> : B[K] : B[K] : A[K] : K extends keyof B ? B[K] : never;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Recursively deep-merges N event router configs left-to-right.
|
|
134
|
+
*/
|
|
135
|
+
type DeepMergeAll<T extends readonly EventRouterConfig[]> = T extends readonly [
|
|
136
|
+
infer Only extends EventRouterConfig
|
|
137
|
+
] ? Only : T extends readonly [
|
|
138
|
+
infer First extends EventRouterConfig,
|
|
139
|
+
infer Second extends EventRouterConfig,
|
|
140
|
+
...infer Rest extends readonly EventRouterConfig[]
|
|
141
|
+
] ? DeepMergeAll<[DeepMergeTwo<First, Second>, ...Rest]> : EventRouterConfig;
|
|
142
|
+
/**
|
|
143
|
+
* Collects all event handlers from a router into a flat array.
|
|
144
|
+
*/
|
|
145
|
+
declare function collectEventHandlers(config: EventRouterConfig, basePath?: string): {
|
|
146
|
+
key: string;
|
|
147
|
+
handler: EventHandlerDefinition;
|
|
148
|
+
}[];
|
|
149
|
+
|
|
150
|
+
export { type DeepMergeTwo as D, type EventHandlerDefinition as E, type FlattenEventRouter as F, type GetEventHandler as G, type InferEventType as I, type EventHandlerDocumentation as a, type EventRouterEntry as b, type EventRouterConfig as c, type EventRouterDefaults as d, type EventRouterDefinition as e, type EventRouterKeys as f, type InferEventPayload as g, type InferEventContext as h, isEventHandlerDefinition as i, isEventRouterDefinition as j, collectEventHandlers as k, type DeepMergeAll as l };
|