@fluojs/http 1.0.0-beta.1

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +142 -0
  3. package/README.md +144 -0
  4. package/dist/adapter.d.ts +58 -0
  5. package/dist/adapter.d.ts.map +1 -0
  6. package/dist/adapter.js +42 -0
  7. package/dist/adapters/binding.d.ts +11 -0
  8. package/dist/adapters/binding.d.ts.map +1 -0
  9. package/dist/adapters/binding.js +185 -0
  10. package/dist/adapters/dto-validation-adapter.d.ts +10 -0
  11. package/dist/adapters/dto-validation-adapter.d.ts.map +1 -0
  12. package/dist/adapters/dto-validation-adapter.js +46 -0
  13. package/dist/client-identity.d.ts +21 -0
  14. package/dist/client-identity.d.ts.map +1 -0
  15. package/dist/client-identity.js +108 -0
  16. package/dist/context/request-context.d.ts +53 -0
  17. package/dist/context/request-context.d.ts.map +1 -0
  18. package/dist/context/request-context.js +89 -0
  19. package/dist/context/sse.d.ts +21 -0
  20. package/dist/context/sse.d.ts.map +1 -0
  21. package/dist/context/sse.js +106 -0
  22. package/dist/decorators.d.ts +188 -0
  23. package/dist/decorators.d.ts.map +1 -0
  24. package/dist/decorators.js +378 -0
  25. package/dist/dispatch/dispatch-content-negotiation.d.ts +9 -0
  26. package/dist/dispatch/dispatch-content-negotiation.d.ts.map +1 -0
  27. package/dist/dispatch/dispatch-content-negotiation.js +164 -0
  28. package/dist/dispatch/dispatch-error-policy.d.ts +3 -0
  29. package/dist/dispatch/dispatch-error-policy.d.ts.map +1 -0
  30. package/dist/dispatch/dispatch-error-policy.js +24 -0
  31. package/dist/dispatch/dispatch-handler-policy.d.ts +3 -0
  32. package/dist/dispatch/dispatch-handler-policy.d.ts.map +1 -0
  33. package/dist/dispatch/dispatch-handler-policy.js +21 -0
  34. package/dist/dispatch/dispatch-response-policy.d.ts +7 -0
  35. package/dist/dispatch/dispatch-response-policy.d.ts.map +1 -0
  36. package/dist/dispatch/dispatch-response-policy.js +45 -0
  37. package/dist/dispatch/dispatch-routing-policy.d.ts +4 -0
  38. package/dist/dispatch/dispatch-routing-policy.d.ts.map +1 -0
  39. package/dist/dispatch/dispatch-routing-policy.js +14 -0
  40. package/dist/dispatch/dispatcher.d.ts +36 -0
  41. package/dist/dispatch/dispatcher.d.ts.map +1 -0
  42. package/dist/dispatch/dispatcher.js +196 -0
  43. package/dist/errors.d.ts +23 -0
  44. package/dist/errors.d.ts.map +1 -0
  45. package/dist/errors.js +41 -0
  46. package/dist/exceptions.d.ts +174 -0
  47. package/dist/exceptions.d.ts.map +1 -0
  48. package/dist/exceptions.js +222 -0
  49. package/dist/guards.d.ts +3 -0
  50. package/dist/guards.d.ts.map +1 -0
  51. package/dist/guards.js +19 -0
  52. package/dist/index.d.ts +15 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +14 -0
  55. package/dist/input-error-detail.d.ts +10 -0
  56. package/dist/input-error-detail.d.ts.map +1 -0
  57. package/dist/input-error-detail.js +8 -0
  58. package/dist/interceptors.d.ts +3 -0
  59. package/dist/interceptors.d.ts.map +1 -0
  60. package/dist/interceptors.js +22 -0
  61. package/dist/internal.d.ts +3 -0
  62. package/dist/internal.d.ts.map +1 -0
  63. package/dist/internal.js +2 -0
  64. package/dist/mapping.d.ts +7 -0
  65. package/dist/mapping.d.ts.map +1 -0
  66. package/dist/mapping.js +244 -0
  67. package/dist/middleware/correlation.d.ts +3 -0
  68. package/dist/middleware/correlation.d.ts.map +1 -0
  69. package/dist/middleware/correlation.js +19 -0
  70. package/dist/middleware/cors.d.ts +11 -0
  71. package/dist/middleware/cors.d.ts.map +1 -0
  72. package/dist/middleware/cors.js +57 -0
  73. package/dist/middleware/middleware.d.ts +8 -0
  74. package/dist/middleware/middleware.d.ts.map +1 -0
  75. package/dist/middleware/middleware.js +64 -0
  76. package/dist/middleware/rate-limit.d.ts +39 -0
  77. package/dist/middleware/rate-limit.d.ts.map +1 -0
  78. package/dist/middleware/rate-limit.js +106 -0
  79. package/dist/middleware/security-headers.d.ts +12 -0
  80. package/dist/middleware/security-headers.d.ts.map +1 -0
  81. package/dist/middleware/security-headers.js +47 -0
  82. package/dist/route-path.d.ts +15 -0
  83. package/dist/route-path.d.ts.map +1 -0
  84. package/dist/route-path.js +69 -0
  85. package/dist/types.d.ts +274 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +114 -0
  88. package/package.json +58 -0
@@ -0,0 +1,222 @@
1
+ import { FluoError } from '@fluojs/core';
2
+
3
+ /**
4
+ * Detailed error information for field-level validation or binding failures.
5
+ */
6
+
7
+ /**
8
+ * Optional metadata used when creating an {@link HttpException}.
9
+ */
10
+
11
+ /**
12
+ * Canonical HTTP error response envelope.
13
+ */
14
+
15
+ /**
16
+ * Base HTTP exception type used by the dispatcher error serializer.
17
+ */
18
+ export class HttpException extends FluoError {
19
+ /** Detailed field-level or source-level error info. */
20
+ details;
21
+ /** HTTP status code. */
22
+ status;
23
+
24
+ /**
25
+ * Creates an HTTP exception with status, message, and optional structured error metadata.
26
+ *
27
+ * @param status HTTP status code used by the dispatcher when writing the error response.
28
+ * @param message Human-readable error message exposed in the serialized envelope.
29
+ * @param options Optional structured metadata including `code`, `details`, `meta`, and `cause`.
30
+ */
31
+ constructor(status, message, options = {}) {
32
+ super(message, {
33
+ cause: options.cause,
34
+ code: options.code,
35
+ meta: options.meta
36
+ });
37
+ this.details = options.details ? [...options.details] : undefined;
38
+ this.status = status;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * HTTP 400 Bad Request exception.
44
+ */
45
+ export class BadRequestException extends HttpException {
46
+ /**
47
+ * Creates a 400 Bad Request exception.
48
+ *
49
+ * @param message Human-readable reason for rejecting the request.
50
+ * @param options Optional structured details and metadata serialized in the error envelope.
51
+ */
52
+ constructor(message = 'Bad request.', options = {}) {
53
+ super(400, message, {
54
+ ...options,
55
+ code: 'BAD_REQUEST'
56
+ });
57
+ }
58
+ }
59
+
60
+ /**
61
+ * HTTP 401 Unauthorized exception.
62
+ */
63
+ export class UnauthorizedException extends HttpException {
64
+ /**
65
+ * Creates a 401 Unauthorized exception.
66
+ *
67
+ * @param message Human-readable authentication failure reason.
68
+ * @param options Optional structured details and metadata serialized in the error envelope.
69
+ */
70
+ constructor(message = 'Authentication required.', options = {}) {
71
+ super(401, message, {
72
+ ...options,
73
+ code: 'UNAUTHORIZED'
74
+ });
75
+ }
76
+ }
77
+
78
+ /**
79
+ * HTTP 403 Forbidden exception.
80
+ */
81
+ export class ForbiddenException extends HttpException {
82
+ /**
83
+ * Creates a 403 Forbidden exception.
84
+ *
85
+ * @param message Human-readable authorization failure reason.
86
+ * @param options Optional structured details and metadata serialized in the error envelope.
87
+ */
88
+ constructor(message = 'Access denied.', options = {}) {
89
+ super(403, message, {
90
+ ...options,
91
+ code: 'FORBIDDEN'
92
+ });
93
+ }
94
+ }
95
+
96
+ /**
97
+ * HTTP 404 Not Found exception.
98
+ */
99
+ export class NotFoundException extends HttpException {
100
+ /**
101
+ * Creates a 404 Not Found exception.
102
+ *
103
+ * @param message Human-readable missing-resource reason.
104
+ * @param options Optional structured details and metadata serialized in the error envelope.
105
+ */
106
+ constructor(message = 'Resource not found.', options = {}) {
107
+ super(404, message, {
108
+ ...options,
109
+ code: 'NOT_FOUND'
110
+ });
111
+ }
112
+ }
113
+
114
+ /**
115
+ * HTTP 409 Conflict exception.
116
+ */
117
+ export class ConflictException extends HttpException {
118
+ /**
119
+ * Creates a 409 Conflict exception.
120
+ *
121
+ * @param message Human-readable conflict reason.
122
+ * @param options Optional structured details and metadata serialized in the error envelope.
123
+ */
124
+ constructor(message = 'Conflict.', options = {}) {
125
+ super(409, message, {
126
+ ...options,
127
+ code: 'CONFLICT'
128
+ });
129
+ }
130
+ }
131
+
132
+ /**
133
+ * HTTP 406 Not Acceptable exception.
134
+ */
135
+ export class NotAcceptableException extends HttpException {
136
+ /**
137
+ * Creates a 406 Not Acceptable exception.
138
+ *
139
+ * @param message Human-readable content-negotiation failure reason.
140
+ * @param options Optional structured details and metadata serialized in the error envelope.
141
+ */
142
+ constructor(message = 'Not acceptable.', options = {}) {
143
+ super(406, message, {
144
+ ...options,
145
+ code: 'NOT_ACCEPTABLE'
146
+ });
147
+ }
148
+ }
149
+
150
+ /**
151
+ * HTTP 429 Too Many Requests exception.
152
+ */
153
+ export class TooManyRequestsException extends HttpException {
154
+ /**
155
+ * Creates a 429 Too Many Requests exception.
156
+ *
157
+ * @param message Human-readable throttling reason.
158
+ * @param options Optional structured details and metadata serialized in the error envelope.
159
+ */
160
+ constructor(message = 'Too many requests.', options = {}) {
161
+ super(429, message, {
162
+ ...options,
163
+ code: 'TOO_MANY_REQUESTS'
164
+ });
165
+ }
166
+ }
167
+
168
+ /**
169
+ * HTTP 413 Payload Too Large exception.
170
+ */
171
+ export class PayloadTooLargeException extends HttpException {
172
+ /**
173
+ * Creates a 413 Payload Too Large exception.
174
+ *
175
+ * @param message Human-readable payload-size failure reason.
176
+ * @param options Optional structured details and metadata serialized in the error envelope.
177
+ */
178
+ constructor(message = 'Payload too large.', options = {}) {
179
+ super(413, message, {
180
+ ...options,
181
+ code: 'PAYLOAD_TOO_LARGE'
182
+ });
183
+ }
184
+ }
185
+
186
+ /**
187
+ * HTTP 500 Internal Server Error exception.
188
+ */
189
+ export class InternalServerErrorException extends HttpException {
190
+ /**
191
+ * Creates a 500 Internal Server Error exception.
192
+ *
193
+ * @param message Human-readable server-side failure reason.
194
+ * @param options Optional structured details and metadata serialized in the error envelope.
195
+ */
196
+ constructor(message = 'Internal server error.', options = {}) {
197
+ super(500, message, {
198
+ ...options,
199
+ code: 'INTERNAL_SERVER_ERROR'
200
+ });
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Converts an {@link HttpException} to the standard serialized error envelope.
206
+ *
207
+ * @param error HTTP exception produced by application code or the dispatcher pipeline.
208
+ * @param requestId Optional request identifier attached by runtime correlation middleware.
209
+ * @returns The canonical `{ error: ... }` payload returned to HTTP clients.
210
+ */
211
+ export function createErrorResponse(error, requestId) {
212
+ return {
213
+ error: {
214
+ code: error.code,
215
+ details: error.details,
216
+ message: error.message,
217
+ meta: error.meta,
218
+ requestId,
219
+ status: error.status
220
+ }
221
+ };
222
+ }
@@ -0,0 +1,3 @@
1
+ import type { GuardContext, GuardLike } from './types.js';
2
+ export declare function runGuardChain(definitions: GuardLike[], context: GuardContext): Promise<void>;
3
+ //# sourceMappingURL=guards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAS,YAAY,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;AAcjF,wBAAsB,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CASlG"}
package/dist/guards.js ADDED
@@ -0,0 +1,19 @@
1
+ import { ForbiddenException } from './exceptions.js';
2
+ function isGuard(value) {
3
+ return typeof value === 'object' && value !== null && 'canActivate' in value;
4
+ }
5
+ async function resolveGuard(definition, requestContext) {
6
+ if (isGuard(definition)) {
7
+ return definition;
8
+ }
9
+ return requestContext.container.resolve(definition);
10
+ }
11
+ export async function runGuardChain(definitions, context) {
12
+ for (const definition of definitions) {
13
+ const guard = await resolveGuard(definition, context.requestContext);
14
+ const result = await guard.canActivate(context);
15
+ if (result === false) {
16
+ throw new ForbiddenException('Access denied.');
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,15 @@
1
+ export * from './adapter.js';
2
+ export * from './middleware/correlation.js';
3
+ export * from './middleware/cors.js';
4
+ export { All, Controller, Convert, Delete, FromBody, FromCookie, FromHeader, FromPath, FromQuery, Get, Head, Header, HttpCode, Optional, Options, Patch, Post, Produces, Put, Redirect, RequestDto, UseGuards, UseInterceptors, Version, } from './decorators.js';
5
+ export * from './dispatch/dispatcher.js';
6
+ export * from './errors.js';
7
+ export * from './exceptions.js';
8
+ export * from './mapping.js';
9
+ export { forRoutes, isMiddlewareRouteConfig, matchRoutePattern, normalizeRoutePattern, } from './middleware/middleware.js';
10
+ export * from './middleware/rate-limit.js';
11
+ export * from './context/request-context.js';
12
+ export * from './middleware/security-headers.js';
13
+ export * from './context/sse.js';
14
+ export * from './types.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,OAAO,EACL,GAAG,EACH,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAQ,EACR,UAAU,EACV,UAAU,EACV,QAAQ,EACR,SAAS,EACT,GAAG,EACH,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,GAAG,EACH,QAAQ,EACR,UAAU,EACV,SAAS,EACT,eAAe,EACf,OAAO,GACR,MAAM,iBAAiB,CAAC;AACzB,cAAc,0BAA0B,CAAC;AACzC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,OAAO,EACL,SAAS,EACT,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export * from './adapter.js';
2
+ export * from './middleware/correlation.js';
3
+ export * from './middleware/cors.js';
4
+ export { All, Controller, Convert, Delete, FromBody, FromCookie, FromHeader, FromPath, FromQuery, Get, Head, Header, HttpCode, Optional, Options, Patch, Post, Produces, Put, Redirect, RequestDto, UseGuards, UseInterceptors, Version } from './decorators.js';
5
+ export * from './dispatch/dispatcher.js';
6
+ export * from './errors.js';
7
+ export * from './exceptions.js';
8
+ export * from './mapping.js';
9
+ export { forRoutes, isMiddlewareRouteConfig, matchRoutePattern, normalizeRoutePattern } from './middleware/middleware.js';
10
+ export * from './middleware/rate-limit.js';
11
+ export * from './context/request-context.js';
12
+ export * from './middleware/security-headers.js';
13
+ export * from './context/sse.js';
14
+ export * from './types.js';
@@ -0,0 +1,10 @@
1
+ import type { MetadataSource } from '@fluojs/core';
2
+ import type { HttpExceptionDetail } from './exceptions.js';
3
+ export interface InputErrorDetail {
4
+ code: string;
5
+ field?: string;
6
+ message: string;
7
+ source?: MetadataSource;
8
+ }
9
+ export declare function toInputErrorDetail(detail: InputErrorDetail): HttpExceptionDetail;
10
+ //# sourceMappingURL=input-error-detail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-error-detail.d.ts","sourceRoot":"","sources":["../src/input-error-detail.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,mBAAmB,CAOhF"}
@@ -0,0 +1,8 @@
1
+ export function toInputErrorDetail(detail) {
2
+ return {
3
+ code: detail.code,
4
+ field: detail.field,
5
+ message: detail.message,
6
+ source: detail.source
7
+ };
8
+ }
@@ -0,0 +1,3 @@
1
+ import type { InterceptorContext, InterceptorLike } from './types.js';
2
+ export declare function runInterceptorChain(definitions: InterceptorLike[], context: InterceptorContext, terminal: () => Promise<unknown>): Promise<unknown>;
3
+ //# sourceMappingURL=interceptors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptors.d.ts","sourceRoot":"","sources":["../src/interceptors.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,kBAAkB,EAClB,eAAe,EAEhB,MAAM,YAAY,CAAC;AAiBpB,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,eAAe,EAAE,EAC9B,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAC/B,OAAO,CAAC,OAAO,CAAC,CAelB"}
@@ -0,0 +1,22 @@
1
+ function isInterceptor(value) {
2
+ return typeof value === 'object' && value !== null && 'intercept' in value;
3
+ }
4
+ async function resolveInterceptor(definition, requestContext) {
5
+ if (isInterceptor(definition)) {
6
+ return definition;
7
+ }
8
+ return requestContext.container.resolve(definition);
9
+ }
10
+ export async function runInterceptorChain(definitions, context, terminal) {
11
+ let next = {
12
+ handle: terminal
13
+ };
14
+ for (const definition of [...definitions].reverse()) {
15
+ const interceptor = await resolveInterceptor(definition, context.requestContext);
16
+ const previous = next;
17
+ next = {
18
+ handle: () => Promise.resolve(interceptor.intercept(context, previous))
19
+ };
20
+ }
21
+ return next.handle();
22
+ }
@@ -0,0 +1,3 @@
1
+ export { DefaultBinder } from './adapters/binding.js';
2
+ export { resolveClientIdentity } from './client-identity.js';
3
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { DefaultBinder } from './adapters/binding.js';
2
+ export { resolveClientIdentity } from './client-identity.js';
@@ -0,0 +1,7 @@
1
+ import type { HandlerMapping, HandlerSource, VersioningOptions } from './types.js';
2
+ interface CreateHandlerMappingOptions {
3
+ versioning?: VersioningOptions;
4
+ }
5
+ export declare function createHandlerMapping(sources: HandlerSource[], options?: CreateHandlerMappingOptions): HandlerMapping;
6
+ export {};
7
+ //# sourceMappingURL=mapping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAIV,cAAc,EAEd,aAAa,EAIb,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAOpB,UAAU,2BAA2B;IACnC,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAmQD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,OAAO,CAAC,EAAE,2BAA2B,GAAG,cAAc,CA0DpH"}
@@ -0,0 +1,244 @@
1
+ import { getControllerMetadata, getRouteMetadata } from '@fluojs/core/internal';
2
+ import { getRouteProducesMetadata } from './decorators.js';
3
+ import { RouteConflictError } from './errors.js';
4
+ import { extractRoutePathParams, matchRoutePath, normalizeRoutePath, parseRoutePath } from './route-path.js';
5
+ import { VersioningType } from './types.js';
6
+ function joinPaths(basePath, routePath) {
7
+ return normalizeRoutePath(`${basePath}/${routePath}`);
8
+ }
9
+ function normalizeVersionSegment(version) {
10
+ const normalized = version.trim().replace(/^v/i, '');
11
+ return `v${normalized}`;
12
+ }
13
+ function applyVersionPrefix(path, version) {
14
+ if (!version) {
15
+ return path;
16
+ }
17
+ return joinPaths(`/${normalizeVersionSegment(version)}`, path);
18
+ }
19
+ function normalizeVersionValue(version) {
20
+ return version.trim().replace(/^v/i, '');
21
+ }
22
+ function readHeaderValue(request, headerName) {
23
+ const normalizedHeaderName = headerName.trim().toLowerCase();
24
+ if (!normalizedHeaderName) {
25
+ return undefined;
26
+ }
27
+ for (const [key, raw] of Object.entries(request.headers)) {
28
+ if (key.toLowerCase() !== normalizedHeaderName) {
29
+ continue;
30
+ }
31
+ const values = Array.isArray(raw) ? raw : [raw];
32
+ for (const value of values) {
33
+ const normalized = value?.trim();
34
+ if (normalized) {
35
+ return normalized;
36
+ }
37
+ }
38
+ }
39
+ return undefined;
40
+ }
41
+ function escapeRegExp(value) {
42
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
43
+ }
44
+ function extractVersionFromMediaType(request, key) {
45
+ const accept = readHeaderValue(request, 'accept');
46
+ if (!accept) {
47
+ return undefined;
48
+ }
49
+ const escapedKey = escapeRegExp(key);
50
+ const matcher = new RegExp(`${escapedKey}([^;,+\\s]+)`, 'i');
51
+ const mediaTypes = accept.split(',').map(value => value.trim()).filter(Boolean);
52
+ for (const mediaType of mediaTypes) {
53
+ const match = mediaType.match(matcher);
54
+ const extracted = match?.[1]?.trim();
55
+ if (extracted) {
56
+ return extracted;
57
+ }
58
+ }
59
+ return undefined;
60
+ }
61
+ function resolveVersioning(options) {
62
+ const versioning = options?.versioning;
63
+ if (!versioning || versioning.type === undefined || versioning.type === VersioningType.URI) {
64
+ return {
65
+ extractor: () => undefined,
66
+ type: VersioningType.URI
67
+ };
68
+ }
69
+ if (versioning.type === VersioningType.HEADER) {
70
+ return {
71
+ extractor: request => readHeaderValue(request, versioning.header),
72
+ type: VersioningType.HEADER
73
+ };
74
+ }
75
+ if (versioning.type === VersioningType.MEDIA_TYPE) {
76
+ return {
77
+ extractor: request => extractVersionFromMediaType(request, versioning.key ?? 'v='),
78
+ type: VersioningType.MEDIA_TYPE
79
+ };
80
+ }
81
+ if (versioning.type === VersioningType.CUSTOM) {
82
+ return {
83
+ extractor: versioning.extractor,
84
+ type: VersioningType.CUSTOM
85
+ };
86
+ }
87
+ return {
88
+ extractor: () => undefined,
89
+ type: VersioningType.URI
90
+ };
91
+ }
92
+ function resolveRequestVersion(request, versioning) {
93
+ const raw = versioning.extractor(request);
94
+ const values = Array.isArray(raw) ? raw : [raw];
95
+ for (const value of values) {
96
+ if (typeof value !== 'string') {
97
+ continue;
98
+ }
99
+ const normalized = normalizeVersionValue(value);
100
+ if (normalized) {
101
+ return normalized;
102
+ }
103
+ }
104
+ return undefined;
105
+ }
106
+ function matchesRouteVersion(descriptor, requestVersion) {
107
+ const routeVersion = descriptor.route.version;
108
+ if (!routeVersion) {
109
+ return requestVersion === undefined;
110
+ }
111
+ if (!requestVersion) {
112
+ return false;
113
+ }
114
+ return normalizeVersionValue(routeVersion) === requestVersion;
115
+ }
116
+ function getControllerMethodNames(controllerToken) {
117
+ return Object.getOwnPropertyNames(controllerToken.prototype).filter(propertyKey => propertyKey !== 'constructor');
118
+ }
119
+ function splitIncomingPathSegments(path) {
120
+ return normalizeRoutePath(path).split('/').filter(Boolean);
121
+ }
122
+ function buildDescriptorIndex(descriptors) {
123
+ const index = new Map();
124
+ for (const descriptor of descriptors) {
125
+ const method = descriptor.route.method;
126
+ const segments = parseRoutePath(descriptor.route.path, `Registered ${descriptor.route.method} route path`);
127
+ const segmentCount = segments.length;
128
+ let methodMap = index.get(method);
129
+ if (!methodMap) {
130
+ methodMap = new Map();
131
+ index.set(method, methodMap);
132
+ }
133
+ let bucket = methodMap.get(segmentCount);
134
+ if (!bucket) {
135
+ bucket = [];
136
+ methodMap.set(segmentCount, bucket);
137
+ }
138
+ bucket.push({
139
+ descriptor,
140
+ segments
141
+ });
142
+ }
143
+ return index;
144
+ }
145
+ function createHandlerDescriptors(source, versioning) {
146
+ const controllerMetadata = getControllerMetadata(source.controllerToken) ?? {
147
+ basePath: ''
148
+ };
149
+ const descriptors = [];
150
+ for (const propertyKey of getControllerMethodNames(source.controllerToken)) {
151
+ const routeMetadata = getRouteMetadata(source.controllerToken.prototype, propertyKey);
152
+ if (!routeMetadata) {
153
+ continue;
154
+ }
155
+ const effectiveVersion = routeMetadata.version ?? controllerMetadata.version;
156
+ const routePath = joinPaths(controllerMetadata.basePath, routeMetadata.path);
157
+ const effectivePath = versioning.type === VersioningType.URI ? applyVersionPrefix(routePath, effectiveVersion) : routePath;
158
+ const produces = getRouteProducesMetadata(source.controllerToken, propertyKey);
159
+ descriptors.push({
160
+ controllerToken: source.controllerToken,
161
+ metadata: {
162
+ controllerPath: controllerMetadata.basePath,
163
+ effectivePath,
164
+ effectiveVersion,
165
+ moduleMiddleware: [...(source.moduleMiddleware ?? [])],
166
+ moduleType: source.moduleType,
167
+ pathParams: extractRoutePathParams(effectivePath)
168
+ },
169
+ methodName: String(propertyKey),
170
+ route: {
171
+ ...routeMetadata,
172
+ ...(produces ? {
173
+ produces
174
+ } : {}),
175
+ guards: [...(controllerMetadata.guards ?? []), ...(routeMetadata.guards ?? [])],
176
+ interceptors: [...(controllerMetadata.interceptors ?? []), ...(routeMetadata.interceptors ?? [])],
177
+ path: effectivePath,
178
+ version: effectiveVersion
179
+ }
180
+ });
181
+ }
182
+ return descriptors;
183
+ }
184
+ function buildDescriptorList(sources, versioning) {
185
+ const descriptors = sources.flatMap(source => createHandlerDescriptors(source, versioning));
186
+ const seen = new Set();
187
+ for (const descriptor of descriptors) {
188
+ const routeVersion = descriptor.route.version === undefined ? '<none>' : normalizeVersionValue(descriptor.route.version);
189
+ const routeKey = `${descriptor.route.method}:${descriptor.route.path}:${routeVersion}`;
190
+ if (seen.has(routeKey)) {
191
+ throw new RouteConflictError(`Duplicate route registration detected for ${routeKey}.`);
192
+ }
193
+ seen.add(routeKey);
194
+ }
195
+ return descriptors;
196
+ }
197
+ export function createHandlerMapping(sources, options) {
198
+ const versioning = resolveVersioning(options);
199
+ const descriptors = buildDescriptorList(sources, versioning);
200
+ const descriptorIndex = buildDescriptorIndex(descriptors);
201
+ return {
202
+ descriptors,
203
+ match(request) {
204
+ const method = request.method.toUpperCase();
205
+ const requestVersion = versioning.type === VersioningType.URI ? undefined : resolveRequestVersion(request, versioning);
206
+ const incomingSegments = splitIncomingPathSegments(request.path);
207
+ const candidates = [...(descriptorIndex.get(method)?.get(incomingSegments.length) ?? []), ...(descriptorIndex.get('ALL')?.get(incomingSegments.length) ?? [])];
208
+ let firstUnversionedMatch;
209
+ for (const candidate of candidates) {
210
+ const params = matchRoutePath(candidate.segments, incomingSegments);
211
+ if (!params) {
212
+ continue;
213
+ }
214
+ if (versioning.type === VersioningType.URI) {
215
+ return {
216
+ descriptor: candidate.descriptor,
217
+ params
218
+ };
219
+ }
220
+ if (matchesRouteVersion(candidate.descriptor, requestVersion)) {
221
+ return {
222
+ descriptor: candidate.descriptor,
223
+ params
224
+ };
225
+ }
226
+ if (candidate.descriptor.route.version === undefined && !firstUnversionedMatch) {
227
+ firstUnversionedMatch = {
228
+ descriptor: candidate.descriptor,
229
+ params
230
+ };
231
+ }
232
+ }
233
+ if (versioning.type !== VersioningType.URI) {
234
+ if (firstUnversionedMatch) {
235
+ return {
236
+ descriptor: firstUnversionedMatch.descriptor,
237
+ params: firstUnversionedMatch.params
238
+ };
239
+ }
240
+ }
241
+ return undefined;
242
+ }
243
+ };
244
+ }
@@ -0,0 +1,3 @@
1
+ import type { Middleware } from '../types.js';
2
+ export declare function createCorrelationMiddleware(): Middleware;
3
+ //# sourceMappingURL=correlation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correlation.d.ts","sourceRoot":"","sources":["../../src/middleware/correlation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAY9C,wBAAgB,2BAA2B,IAAI,UAAU,CAYxD"}
@@ -0,0 +1,19 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ const REQUEST_ID_HEADER = 'x-request-id';
3
+ const CORRELATION_ID_HEADER = 'x-correlation-id';
4
+ function resolveInboundRequestId(headers) {
5
+ const requestId = headers[REQUEST_ID_HEADER] ?? headers[CORRELATION_ID_HEADER];
6
+ const value = Array.isArray(requestId) ? requestId[0] : requestId;
7
+ return value ?? randomUUID();
8
+ }
9
+ export function createCorrelationMiddleware() {
10
+ return {
11
+ async handle(context, next) {
12
+ if (!context.requestContext.requestId) {
13
+ context.requestContext.requestId = resolveInboundRequestId(context.request.headers);
14
+ }
15
+ context.response.setHeader(REQUEST_ID_HEADER, context.requestContext.requestId);
16
+ await next();
17
+ }
18
+ };
19
+ }
@@ -0,0 +1,11 @@
1
+ import type { Middleware } from '../types.js';
2
+ export interface CorsOptions {
3
+ allowCredentials?: boolean;
4
+ allowHeaders?: string[];
5
+ allowMethods?: string[];
6
+ allowOrigin?: string | string[] | ((origin: string | undefined) => string | undefined);
7
+ exposeHeaders?: string[];
8
+ maxAge?: number;
9
+ }
10
+ export declare function createCorsMiddleware(options?: CorsOptions): Middleware;
11
+ //# sourceMappingURL=cors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/middleware/cors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC;IACvF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA4BD,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,WAAgB,GAAG,UAAU,CA0D1E"}