@fluojs/http
Advanced tools
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"} |
+261
-52
@@ -1,2 +0,2 @@ | ||
| import { getStandardMetadataBag as readStandardMetadataBag, metadataSymbol } from '@fluojs/core/internal'; | ||
| import { defineControllerMetadata, defineDtoFieldBindingMetadata, defineRouteMetadata, ensureMetadataSymbol, getControllerMetadata, getDtoFieldBindingMetadata, getRouteMetadata, getStandardMetadataBag as readStandardMetadataBag } from '@fluojs/core/internal'; | ||
| import { validateRoutePath } from './route-path.js'; | ||
@@ -6,2 +6,5 @@ const standardControllerMetadataKey = Symbol.for('fluo.standard.controller'); | ||
| const standardDtoBindingMetadataKey = Symbol.for('fluo.standard.dto-binding'); | ||
| const legacyRouteMetadataStore = new WeakMap(); | ||
| const legacyDtoBindingMetadataStore = new WeakMap(); | ||
| ensureMetadataSymbol(); | ||
| function normalizeProducesMediaTypes(mediaTypes) { | ||
@@ -28,5 +31,112 @@ const normalized = []; | ||
| function getStandardMetadataBag(metadata) { | ||
| void metadataSymbol; | ||
| return metadata; | ||
| return typeof metadata === 'object' && metadata !== null ? metadata : {}; | ||
| } | ||
| function isStandardDecoratorContext(value) { | ||
| return typeof value === 'object' && value !== null && 'kind' in value && 'name' in value; | ||
| } | ||
| function isMetadataPropertyKey(value) { | ||
| return typeof value === 'string' || typeof value === 'symbol'; | ||
| } | ||
| function getLegacyRouteRecord(target, propertyKey) { | ||
| let routeMap = legacyRouteMetadataStore.get(target); | ||
| if (!routeMap) { | ||
| routeMap = new Map(); | ||
| legacyRouteMetadataStore.set(target, routeMap); | ||
| } | ||
| let record = routeMap.get(propertyKey); | ||
| if (!record) { | ||
| record = {}; | ||
| routeMap.set(propertyKey, record); | ||
| } | ||
| return record; | ||
| } | ||
| function flushLegacyRouteMetadata(target, propertyKey, record) { | ||
| if (!record.method || record.path === undefined) { | ||
| return; | ||
| } | ||
| const existing = getRouteMetadata(target, propertyKey); | ||
| const metadata = { | ||
| guards: mergeUnique(existing?.guards, record.guards ?? []), | ||
| headers: record.headers ?? existing?.headers, | ||
| interceptors: mergeUnique(existing?.interceptors, record.interceptors ?? []), | ||
| method: record.method, | ||
| path: record.path, | ||
| redirect: record.redirect ?? existing?.redirect, | ||
| request: record.request ?? existing?.request, | ||
| successStatus: record.successStatus ?? existing?.successStatus, | ||
| version: record.version ?? existing?.version | ||
| }; | ||
| defineRouteMetadata(target, propertyKey, metadata); | ||
| } | ||
| function mergeLegacyRouteMetadata(target, propertyKey, partial) { | ||
| const record = getLegacyRouteRecord(target, propertyKey); | ||
| Object.assign(record, partial); | ||
| flushLegacyRouteMetadata(target, propertyKey, record); | ||
| } | ||
| function defineLegacyControllerMetadata(target, partial) { | ||
| const existing = getControllerMetadata(target); | ||
| defineControllerMetadata(target, { | ||
| basePath: partial.basePath ?? existing?.basePath ?? '', | ||
| guards: partial.guards ?? existing?.guards, | ||
| interceptors: partial.interceptors ?? existing?.interceptors, | ||
| version: partial.version ?? existing?.version | ||
| }); | ||
| } | ||
| function getLegacyDtoBindingRecord(target, propertyKey) { | ||
| let bindingMap = legacyDtoBindingMetadataStore.get(target); | ||
| if (!bindingMap) { | ||
| bindingMap = new Map(); | ||
| legacyDtoBindingMetadataStore.set(target, bindingMap); | ||
| } | ||
| let record = bindingMap.get(propertyKey); | ||
| if (!record) { | ||
| record = {}; | ||
| bindingMap.set(propertyKey, record); | ||
| } | ||
| return record; | ||
| } | ||
| function mergeLegacyDtoBinding(target, propertyKey, partial) { | ||
| const record = getLegacyDtoBindingRecord(target, propertyKey); | ||
| Object.assign(record, partial); | ||
| const source = record.source ?? getDtoFieldBindingMetadata(target, propertyKey)?.source; | ||
| if (!source) { | ||
| return; | ||
| } | ||
| defineDtoFieldBindingMetadata(target, propertyKey, { | ||
| converter: record.converter, | ||
| key: record.key, | ||
| optional: record.optional, | ||
| source | ||
| }); | ||
| } | ||
| function appendLegacyRouteHeader(target, propertyKey, name, value) { | ||
| const record = getLegacyRouteRecord(target, propertyKey); | ||
| record.headers = [...(record.headers ?? []), { | ||
| name, | ||
| value | ||
| }]; | ||
| flushLegacyRouteMetadata(target, propertyKey, record); | ||
| } | ||
| function appendLegacyControllerGuards(target, guards) { | ||
| const existing = getControllerMetadata(target); | ||
| defineLegacyControllerMetadata(target, { | ||
| guards: mergeUnique(existing?.guards, guards) | ||
| }); | ||
| } | ||
| function appendLegacyControllerInterceptors(target, interceptors) { | ||
| const existing = getControllerMetadata(target); | ||
| defineLegacyControllerMetadata(target, { | ||
| interceptors: mergeUnique(existing?.interceptors, interceptors) | ||
| }); | ||
| } | ||
| function appendLegacyRouteGuards(target, propertyKey, guards) { | ||
| const record = getLegacyRouteRecord(target, propertyKey); | ||
| record.guards = mergeUnique(record.guards, guards); | ||
| flushLegacyRouteMetadata(target, propertyKey, record); | ||
| } | ||
| function appendLegacyRouteInterceptors(target, propertyKey, interceptors) { | ||
| const record = getLegacyRouteRecord(target, propertyKey); | ||
| record.interceptors = mergeUnique(record.interceptors, interceptors); | ||
| flushLegacyRouteMetadata(target, propertyKey, record); | ||
| } | ||
| function getStandardControllerRecord(metadata) { | ||
@@ -82,6 +192,15 @@ const bag = getStandardMetadataBag(metadata); | ||
| validateRoutePath(path, `@${method}() path`); | ||
| const decorator = (_value, context) => { | ||
| const route = getStandardRouteRecord(context.metadata, context.name); | ||
| route.method = method; | ||
| route.path = path; | ||
| const decorator = (valueOrTarget, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| const route = getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name); | ||
| route.method = method; | ||
| route.path = path; | ||
| return; | ||
| } | ||
| if (isMetadataPropertyKey(contextOrPropertyKey)) { | ||
| mergeLegacyRouteMetadata(valueOrTarget, contextOrPropertyKey, { | ||
| method, | ||
| path | ||
| }); | ||
| } | ||
| }; | ||
@@ -93,4 +212,12 @@ return decorator; | ||
| return value => { | ||
| const decorator = (_target, context) => { | ||
| apply(getStandardRouteRecord(context.metadata, context.name), value); | ||
| const decorator = (valueOrTarget, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| apply(getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name), value); | ||
| return; | ||
| } | ||
| if (isMetadataPropertyKey(contextOrPropertyKey)) { | ||
| const record = getLegacyRouteRecord(valueOrTarget, contextOrPropertyKey); | ||
| apply(record, value); | ||
| flushLegacyRouteMetadata(valueOrTarget, contextOrPropertyKey, record); | ||
| } | ||
| }; | ||
@@ -102,7 +229,16 @@ return decorator; | ||
| return key => { | ||
| const decorator = (_value, context) => { | ||
| mergeStandardDtoBinding(context.metadata, context.name, { | ||
| key, | ||
| source | ||
| }); | ||
| const decorator = (valueOrTarget, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| mergeStandardDtoBinding(contextOrPropertyKey.metadata, contextOrPropertyKey.name, { | ||
| key, | ||
| source | ||
| }); | ||
| return; | ||
| } | ||
| if (isMetadataPropertyKey(contextOrPropertyKey) && valueOrTarget && typeof valueOrTarget === 'object') { | ||
| mergeLegacyDtoBinding(valueOrTarget, contextOrPropertyKey, { | ||
| key, | ||
| source | ||
| }); | ||
| } | ||
| }; | ||
@@ -121,4 +257,10 @@ return decorator; | ||
| validateRoutePath(basePath, '@Controller() base path'); | ||
| const decorator = (_target, context) => { | ||
| getStandardControllerRecord(context.metadata).basePath = basePath; | ||
| const decorator = (target, context) => { | ||
| if (isStandardDecoratorContext(context)) { | ||
| getStandardControllerRecord(context.metadata).basePath = basePath; | ||
| return; | ||
| } | ||
| defineLegacyControllerMetadata(target, { | ||
| basePath | ||
| }); | ||
| }; | ||
@@ -135,8 +277,22 @@ return decorator; | ||
| export function Version(version) { | ||
| const decorator = (_target, context) => { | ||
| if (context.kind === 'class') { | ||
| getStandardControllerRecord(context.metadata).version = version; | ||
| const decorator = (target, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| if (contextOrPropertyKey.kind === 'class') { | ||
| getStandardControllerRecord(contextOrPropertyKey.metadata).version = version; | ||
| return; | ||
| } | ||
| getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name).version = version; | ||
| return; | ||
| } | ||
| getStandardRouteRecord(context.metadata, context.name).version = version; | ||
| if (isMetadataPropertyKey(contextOrPropertyKey)) { | ||
| mergeLegacyRouteMetadata(target, contextOrPropertyKey, { | ||
| version | ||
| }); | ||
| return; | ||
| } | ||
| if (typeof target === 'function') { | ||
| defineLegacyControllerMetadata(target, { | ||
| version | ||
| }); | ||
| } | ||
| }; | ||
@@ -245,3 +401,3 @@ return decorator; | ||
| const routeMap = bag?.[standardRouteMetadataKey]; | ||
| const produces = routeMap?.get(propertyKey)?.produces; | ||
| const produces = routeMap?.get(propertyKey)?.produces ?? legacyRouteMetadataStore.get(controllerToken.prototype)?.get(propertyKey)?.produces; | ||
| return produces ? [...produces] : undefined; | ||
@@ -292,6 +448,14 @@ } | ||
| export function Optional() { | ||
| const decorator = (_value, context) => { | ||
| mergeStandardDtoBinding(context.metadata, context.name, { | ||
| optional: true | ||
| }); | ||
| const decorator = (valueOrTarget, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| mergeStandardDtoBinding(contextOrPropertyKey.metadata, contextOrPropertyKey.name, { | ||
| optional: true | ||
| }); | ||
| return; | ||
| } | ||
| if (isMetadataPropertyKey(contextOrPropertyKey) && valueOrTarget && typeof valueOrTarget === 'object') { | ||
| mergeLegacyDtoBinding(valueOrTarget, contextOrPropertyKey, { | ||
| optional: true | ||
| }); | ||
| } | ||
| }; | ||
@@ -308,6 +472,14 @@ return decorator; | ||
| export function Convert(converter) { | ||
| const decorator = (_value, context) => { | ||
| mergeStandardDtoBinding(context.metadata, context.name, { | ||
| converter | ||
| }); | ||
| const decorator = (valueOrTarget, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| mergeStandardDtoBinding(contextOrPropertyKey.metadata, contextOrPropertyKey.name, { | ||
| converter | ||
| }); | ||
| return; | ||
| } | ||
| if (isMetadataPropertyKey(contextOrPropertyKey) && valueOrTarget && typeof valueOrTarget === 'object') { | ||
| mergeLegacyDtoBinding(valueOrTarget, contextOrPropertyKey, { | ||
| converter | ||
| }); | ||
| } | ||
| }; | ||
@@ -325,8 +497,14 @@ return decorator; | ||
| export function Header(name, value) { | ||
| const decorator = (_target, context) => { | ||
| const route = getStandardRouteRecord(context.metadata, context.name); | ||
| route.headers = [...(route.headers ?? []), { | ||
| name, | ||
| value | ||
| }]; | ||
| const decorator = (valueOrTarget, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| const route = getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name); | ||
| route.headers = [...(route.headers ?? []), { | ||
| name, | ||
| value | ||
| }]; | ||
| return; | ||
| } | ||
| if (isMetadataPropertyKey(contextOrPropertyKey)) { | ||
| appendLegacyRouteHeader(valueOrTarget, contextOrPropertyKey, name, value); | ||
| } | ||
| }; | ||
@@ -344,7 +522,18 @@ return decorator; | ||
| export function Redirect(url, statusCode) { | ||
| const decorator = (_target, context) => { | ||
| getStandardRouteRecord(context.metadata, context.name).redirect = { | ||
| url, | ||
| statusCode | ||
| }; | ||
| const decorator = (valueOrTarget, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name).redirect = { | ||
| url, | ||
| statusCode | ||
| }; | ||
| return; | ||
| } | ||
| if (isMetadataPropertyKey(contextOrPropertyKey)) { | ||
| mergeLegacyRouteMetadata(valueOrTarget, contextOrPropertyKey, { | ||
| redirect: { | ||
| url, | ||
| statusCode | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
@@ -361,10 +550,20 @@ return decorator; | ||
| export function UseGuards(...guards) { | ||
| const decorator = (_target, context) => { | ||
| if (context.kind === 'class') { | ||
| const controller = getStandardControllerRecord(context.metadata); | ||
| controller.guards = mergeUnique(controller.guards, guards); | ||
| const decorator = (target, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| if (contextOrPropertyKey.kind === 'class') { | ||
| const controller = getStandardControllerRecord(contextOrPropertyKey.metadata); | ||
| controller.guards = mergeUnique(controller.guards, guards); | ||
| return; | ||
| } | ||
| const route = getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name); | ||
| route.guards = mergeUnique(route.guards, guards); | ||
| return; | ||
| } | ||
| const route = getStandardRouteRecord(context.metadata, context.name); | ||
| route.guards = mergeUnique(route.guards, guards); | ||
| if (isMetadataPropertyKey(contextOrPropertyKey)) { | ||
| appendLegacyRouteGuards(target, contextOrPropertyKey, guards); | ||
| return; | ||
| } | ||
| if (typeof target === 'function') { | ||
| appendLegacyControllerGuards(target, guards); | ||
| } | ||
| }; | ||
@@ -381,12 +580,22 @@ return decorator; | ||
| export function UseInterceptors(...interceptors) { | ||
| const decorator = (_target, context) => { | ||
| if (context.kind === 'class') { | ||
| const controller = getStandardControllerRecord(context.metadata); | ||
| controller.interceptors = mergeUnique(controller.interceptors, interceptors); | ||
| const decorator = (target, contextOrPropertyKey) => { | ||
| if (isStandardDecoratorContext(contextOrPropertyKey)) { | ||
| if (contextOrPropertyKey.kind === 'class') { | ||
| const controller = getStandardControllerRecord(contextOrPropertyKey.metadata); | ||
| controller.interceptors = mergeUnique(controller.interceptors, interceptors); | ||
| return; | ||
| } | ||
| const route = getStandardRouteRecord(contextOrPropertyKey.metadata, contextOrPropertyKey.name); | ||
| route.interceptors = mergeUnique(route.interceptors, interceptors); | ||
| return; | ||
| } | ||
| const route = getStandardRouteRecord(context.metadata, context.name); | ||
| route.interceptors = mergeUnique(route.interceptors, interceptors); | ||
| if (isMetadataPropertyKey(contextOrPropertyKey)) { | ||
| appendLegacyRouteInterceptors(target, contextOrPropertyKey, interceptors); | ||
| return; | ||
| } | ||
| if (typeof target === 'function') { | ||
| appendLegacyControllerInterceptors(target, interceptors); | ||
| } | ||
| }; | ||
| return decorator; | ||
| } |
+4
-4
@@ -13,3 +13,3 @@ { | ||
| ], | ||
| "version": "1.0.0-beta.9", | ||
| "version": "1.0.0-beta.10", | ||
| "private": false, | ||
@@ -45,5 +45,5 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@fluojs/core": "^1.0.0-beta.2", | ||
| "@fluojs/validation": "^1.0.0-beta.1", | ||
| "@fluojs/di": "^1.0.0-beta.5" | ||
| "@fluojs/core": "^1.0.0-beta.4", | ||
| "@fluojs/validation": "^1.0.0-beta.3", | ||
| "@fluojs/di": "^1.0.0-beta.6" | ||
| }, | ||
@@ -50,0 +50,0 @@ "devDependencies": { |
+23
-2
@@ -116,2 +116,20 @@ # @fluojs/http | ||
| ### Versioning | ||
| `createHandlerMapping(...)`은 `VersioningType`과 `versioning` option을 통해 URI, header, media-type, custom versioning strategy를 지원합니다. Route registration은 exact/static match를 fallback보다 앞에 두고, 동등하게 정규화된 route는 registration order를 보존합니다. | ||
| ### Request context helper | ||
| Framework integration이 명시적인 request context boundary나 typed per-request storage가 필요할 때 `runWithRequestContext(...)`, `assertRequestContext()`, `createRequestContext(...)`, `createContextKey(...)`, `getContextValue(...)`, `setContextValue(...)`를 사용합니다. | ||
| ### Fast-path observability | ||
| Dispatcher는 adapter와 diagnostics를 위해 `FAST_PATH_ELIGIBILITY_SYMBOL`, `FAST_PATH_STATS_SYMBOL`, `formatFastPathStats(...)`, `getDispatcherFastPathStats(...)`로 fast-path observability를 노출합니다. | ||
| ### Bun decorator bundling compatibility | ||
| Fluo의 HTTP 데코레이터는 TC39 표준 데코레이터이며, runtime 또는 compiler가 표준 decorator context를 제공하면 계속 `context.metadata`를 통해 metadata를 기록합니다. Bun이 legacy TypeScript decorator transform으로 애플리케이션을 번들링하는 경우에도 controller, route, DTO binding, guard/interceptor, header, redirect, versioning, status, request DTO, `@Produces(...)` metadata를 Fluo 내부 metadata store에 기록하여 생성된 Bun bundle의 route mapping 동작을 보존합니다. | ||
| 이 호환 경로는 Bun bundle output을 위한 실행 fallback입니다. 애플리케이션 소스는 계속 Fluo 표준 데코레이터를 사용해야 하며, `emitDecoratorMetadata`를 켜거나 `reflect-metadata`에 의존해서는 안 됩니다. | ||
| ## 요청 정리와 런타임 이식성 | ||
@@ -129,4 +147,6 @@ | ||
| - **핵심 런타임 타입**: `RequestContext`, `FrameworkRequest`, `FrameworkResponse`, `SseResponse` | ||
| - **예외**: `BadRequestException`, `UnauthorizedException`, `ForbiddenException`, `NotFoundException`, `InternalServerErrorException`, `PayloadTooLargeException` | ||
| - **헬퍼**: `createHandlerMapping`, `createDispatcher`, `forRoutes`, `normalizeRoutePattern`, `matchRoutePattern`, `isMiddlewareRouteConfig`, `createCorrelationMiddleware`, `createCorsMiddleware`, `createRateLimitMiddleware`, `createSecurityHeadersMiddleware`, `getCurrentRequestContext`, `encodeSseComment`, `encodeSseMessage` | ||
| - **Adapter API**: `HttpApplicationAdapter`, `createNoopHttpApplicationAdapter`, `createServerBackedHttpAdapterRealtimeCapability`, `createUnsupportedHttpAdapterRealtimeCapability`, `createFetchStyleHttpAdapterRealtimeCapability` | ||
| - **예외와 오류**: `HttpException`, `BadRequestException`, `UnauthorizedException`, `ForbiddenException`, `NotFoundException`, `ConflictException`, `NotAcceptableException`, `TooManyRequestsException`, `InternalServerErrorException`, `PayloadTooLargeException`, `createErrorResponse`, `RouteConflictError`, `InvalidRoutePathError`, `HandlerNotFoundError`, `RequestAbortedError` | ||
| - **헬퍼**: `createHandlerMapping`, `createDispatcher`, `forRoutes`, `normalizeRoutePattern`, `matchRoutePattern`, `isMiddlewareRouteConfig`, `createCorrelationMiddleware`, `createCorsMiddleware`, `createRateLimitMiddleware`, `createSecurityHeadersMiddleware`, `runWithRequestContext`, `getCurrentRequestContext`, `assertRequestContext`, `createRequestContext`, `createContextKey`, `getContextValue`, `setContextValue`, `encodeSseComment`, `encodeSseMessage` | ||
| - **Option type**: `CorsOptions`, `RateLimitOptions`, `RateLimitStore`, `SecurityHeadersOptions`, `SseSendOptions` | ||
@@ -139,2 +159,3 @@ ## 내부 서브경로 (`@fluojs/http/internal`) | ||
| - `bindRawRequestNativeRouteHandoff(...)` / `attachFrameworkRequestNativeRouteHandoff(...)`: public dispatcher API를 넓히지 않고 의미 보존이 가능한 native route match를 재사용하기 위한 내부 adapter/runtime 헬퍼. | ||
| - `consumeRawRequestNativeRouteHandoff(...)` / `readFrameworkRequestNativeRouteHandoff(...)`: native route handoff를 읽거나 소비하기 위한 내부 helper. | ||
| - Native route handoff는 framework request에 붙는 시점의 method와 path를 함께 스냅샷합니다. app middleware가 handler matching 전에 둘 중 하나를 rewrite하면 dispatcher는 stale handoff를 무시하고 일반 route matching으로 fallback합니다. | ||
@@ -141,0 +162,0 @@ - `isRoutePathNormalizationSensitive(path)`: duplicate slash와 trailing slash 요청을 generic dispatcher 경로에 남기기 위한 내부 guard. |
+23
-2
@@ -118,2 +118,20 @@ # @fluojs/http | ||
| ### Versioning | ||
| `createHandlerMapping(...)` supports URI, header, media-type, and custom versioning strategies through `VersioningType` and the `versioning` option. Route registration keeps exact/static matches ahead of fallbacks while preserving registration order for equivalent normalized routes. | ||
| ### Request context helpers | ||
| Use `runWithRequestContext(...)`, `assertRequestContext()`, `createRequestContext(...)`, `createContextKey(...)`, `getContextValue(...)`, and `setContextValue(...)` when framework integrations need explicit request context boundaries or typed per-request storage. | ||
| ### Fast-path observability | ||
| The dispatcher exposes fast-path observability for adapters and diagnostics through `FAST_PATH_ELIGIBILITY_SYMBOL`, `FAST_PATH_STATS_SYMBOL`, `formatFastPathStats(...)`, and `getDispatcherFastPathStats(...)`. | ||
| ### Bun decorator bundling compatibility | ||
| Fluo's HTTP decorators are standard TC39 decorators and continue to record metadata through `context.metadata` when the runtime or compiler provides the standard decorator context. When Bun bundles an application through its legacy TypeScript decorator transform, the same controller, route, DTO binding, guard/interceptor, header, redirect, versioning, status, request DTO, and `@Produces(...)` metadata is recorded through Fluo's internal metadata stores so generated Bun bundles preserve route mapping behavior. | ||
| This compatibility path is an execution fallback for Bun bundle output; application source should still use Fluo's standard decorators and should not enable `emitDecoratorMetadata` or rely on `reflect-metadata`. | ||
| ## Request Cleanup and Portability | ||
@@ -131,4 +149,6 @@ | ||
| - **Core runtime types**: `RequestContext`, `FrameworkRequest`, `FrameworkResponse`, `SseResponse` | ||
| - **Exceptions**: `BadRequestException`, `UnauthorizedException`, `ForbiddenException`, `NotFoundException`, `InternalServerErrorException`, `PayloadTooLargeException` | ||
| - **Helpers**: `createHandlerMapping`, `createDispatcher`, `forRoutes`, `normalizeRoutePattern`, `matchRoutePattern`, `isMiddlewareRouteConfig`, `createCorrelationMiddleware`, `createCorsMiddleware`, `createRateLimitMiddleware`, `createSecurityHeadersMiddleware`, `getCurrentRequestContext`, `encodeSseComment`, `encodeSseMessage` | ||
| - **Adapter API**: `HttpApplicationAdapter`, `createNoopHttpApplicationAdapter`, `createServerBackedHttpAdapterRealtimeCapability`, `createUnsupportedHttpAdapterRealtimeCapability`, `createFetchStyleHttpAdapterRealtimeCapability` | ||
| - **Exceptions and errors**: `HttpException`, `BadRequestException`, `UnauthorizedException`, `ForbiddenException`, `NotFoundException`, `ConflictException`, `NotAcceptableException`, `TooManyRequestsException`, `InternalServerErrorException`, `PayloadTooLargeException`, `createErrorResponse`, `RouteConflictError`, `InvalidRoutePathError`, `HandlerNotFoundError`, `RequestAbortedError` | ||
| - **Helpers**: `createHandlerMapping`, `createDispatcher`, `forRoutes`, `normalizeRoutePattern`, `matchRoutePattern`, `isMiddlewareRouteConfig`, `createCorrelationMiddleware`, `createCorsMiddleware`, `createRateLimitMiddleware`, `createSecurityHeadersMiddleware`, `runWithRequestContext`, `getCurrentRequestContext`, `assertRequestContext`, `createRequestContext`, `createContextKey`, `getContextValue`, `setContextValue`, `encodeSseComment`, `encodeSseMessage` | ||
| - **Option types**: `CorsOptions`, `RateLimitOptions`, `RateLimitStore`, `SecurityHeadersOptions`, `SseSendOptions` | ||
@@ -141,2 +161,3 @@ ## Internal Subpath (`@fluojs/http/internal`) | ||
| - `bindRawRequestNativeRouteHandoff(...)` / `attachFrameworkRequestNativeRouteHandoff(...)`: Internal adapter/runtime helpers for reusing semantically safe native route matches without widening the public dispatcher API. | ||
| - `consumeRawRequestNativeRouteHandoff(...)` / `readFrameworkRequestNativeRouteHandoff(...)`: Internal helpers for reading or consuming native route handoffs. | ||
| - Native route handoffs snapshot the framework request method and path when attached; if app middleware rewrites either value before handler matching, the dispatcher ignores the stale handoff and falls back to normal route matching. | ||
@@ -143,0 +164,0 @@ - `isRoutePathNormalizationSensitive(path)`: Internal guard for keeping duplicate-slash and trailing-slash requests on the generic dispatcher path. |
255663
5.44%5284
4.12%176
13.55%Updated
Updated