1/**
2 * Builds server-side routers for Effect HTTP applications.
3 *
4 * `HttpRouter` collects routes and middleware while an application layer is
5 * being built. Once the router is complete, it handles each
6 * `HttpServerRequest` by finding a matching route and producing an
7 * `HttpServerResponse`. The module also includes helpers for route definitions,
8 * prefixes, parameters, request decoding, CORS, and running the router.
9 *
10 * @stability unstable
11 * @since 4.0.0
12 */
13import * as Arr from "../Array.ts"
14import * as Context from "../Context.ts"
15import * as Effect from "../Effect.ts"
16import { compose, dual, identity } from "../Function.ts"
17import { fiberEnterInterruptibleUnsafe } from "../internal/effect.ts"
18import * as Layer from "../Layer.ts"
19import * as Option from "../Option.ts"
20import type { ReadonlyRecord } from "../Record.ts"
21import * as Schema from "../Schema.ts"
22import type { ParseOptions } from "../SchemaAST.ts"
23import * as Scope from "../Scope.ts"
24import * as Tracer from "../Tracer.ts"
25import type * as Types from "../Types.ts"
26import * as FindMyWay from "./FindMyWay.ts"
27import * as HttpEffect from "./HttpEffect.ts"
28import * as HttpIncomingMessage from "./HttpIncomingMessage.ts"
29import type * as HttpMethod from "./HttpMethod.ts"
30import * as HttpMiddleware from "./HttpMiddleware.ts"
31import * as HttpServer from "./HttpServer.ts"
32import * as HttpServerError from "./HttpServerError.ts"
33import * as HttpServerRequest from "./HttpServerRequest.ts"
34import * as HttpServerResponse from "./HttpServerResponse.ts"
35
36const TypeId = "~effect/http/HttpRouter"
37
38/**
39 * Defines the service interface for registering HTTP routes and middleware.
40 *
41 * **Details**
42 *
43 * An `HttpRouter` can add routes, apply path prefixes, install global middleware,
44 * and expose the registered routes as an Effect that handles the current server
45 * request.
46 *
47 * @stability unstable
48 * @category services
49 * @since 4.0.0
50 */
51export interface HttpRouter {
52 readonly [TypeId]: typeof TypeId
53
54 readonly prefixed: (prefix: string) => HttpRouter
55
56 readonly add: <E = never, R = never>(
57 method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "QUERY",
58 path: PathInput,
59 handler:
60 | HttpServerResponse.HttpServerResponse
61 | Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
62 | ((request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>),
63 options?: { readonly uninterruptible?: boolean | undefined } | undefined
64 ) => Effect.Effect<
65 void,
66 never,
67 Request.From<"Requires", Exclude<R, Provided>> | Request.From<"Error", E>
68 >
69
70 readonly addAll: <const Routes extends ReadonlyArray<Route<any, any>>>(
71 routes: Routes
72 ) => Effect.Effect<
73 void,
74 never,
75 | Request.From<"Requires", Exclude<Route.Context<Routes[number]>, Provided>>
76 | Request.From<"Error", Route.Error<Routes[number]>>
77 >
78
79 readonly addGlobalMiddleware: <E, R>(
80 middleware:
81 & ((
82 effect: Effect.Effect<HttpServerResponse.HttpServerResponse, Types.unhandled>
83 ) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>)
84 & (Types.unhandled extends E ? unknown : "You cannot handle any errors")
85 ) => Effect.Effect<
86 void,
87 never,
88 | Request.From<"GlobalRequires", Exclude<R, GlobalProvided>>
89 | Request.From<"GlobalError", Exclude<E, Types.unhandled>>
90 >
91
92 // @effect-diagnostics-next-line lazyEffect:off
93 readonly asHttpEffect: () => Effect.Effect<
94 HttpServerResponse.HttpServerResponse,
95 unknown,
96 HttpServerRequest.HttpServerRequest | Scope.Scope
97 >
98}
99
100/**
101 * Service tag for the HTTP router used while constructing an HTTP application.
102 * Route and middleware layers require this service to register themselves with
103 * the router.
104 *
105 * @stability unstable
106 * @category services
107 * @since 4.0.0
108 */
109export const HttpRouter: Context.Service<HttpRouter, HttpRouter> = Context.Service<HttpRouter>(
110 "effect/http/HttpRouter"
111)
112
113/**
114 * Constructs an empty `HttpRouter` service.
115 *
116 * **Details**
117 *
118 * The returned router accepts route and middleware registrations and later routes
119 * the current `HttpServerRequest` to the matching `HttpServerResponse`.
120 *
121 * @stability unstable
122 * @category constructors
123 * @since 4.0.0
124 */
125export const make = Effect.gen(function*() {
126 const router = FindMyWay.make<Route<any, never>>(yield* RouterConfig)
127 const middleware = new Set<middleware.Fn>()
128
129 const addAll = <const Routes extends ReadonlyArray<Route<any, any>>>(
130 routes: Routes
131 ): Effect.Effect<
132 void,
133 never,
134 | Request.From<"Requires", Exclude<Route.Context<Routes[number]>, Provided>>
135 | Request.From<"Error", Route.Error<Routes[number]>>
136 > =>
137 Effect.contextWith((context: Context.Context<never>) => {
138 const middleware = getMiddleware(context)
139 const applyMiddleware = (effect: Effect.Effect<HttpServerResponse.HttpServerResponse>) => {
140 for (let i = 0; i < middleware.length; i++) {
141 effect = middleware[i](effect)
142 }
143 return effect
144 }
145 for (let i = 0; i < routes.length; i++) {
146 const route = middleware.length === 0 ? routes[i] : makeRoute({
147 ...routes[i],
148 handler: applyMiddleware(routes[i].handler as Effect.Effect<HttpServerResponse.HttpServerResponse>)
149 })
150 if (route.method === "*") {
151 if (route.path.endsWith("/*")) {
152 router.all(route.path, route as any)
153 router.all(route.path.slice(0, -2) as any, route as any)
154 } else {
155 router.all(route.path, route as any)
156 }
157 } else {
158 if (route.path.endsWith("/*")) {
159 router.on(route.method, route.path, route as any)
160 router.on(route.method, route.path.slice(0, -2) as any, route as any)
161 } else {
162 router.on(route.method, route.path, route as any)
163 }
164 }
165 }
166 return Effect.void
167 })
168
169 return HttpRouter.of({
170 [TypeId]: TypeId,
171 prefixed(this: HttpRouter, prefix: string) {
172 prefix = removeTrailingSlash(prefix as PathInput)
173 return HttpRouter.of({
174 ...this,
175 prefixed: (newPrefix: string) => this.prefixed(prefixPath(newPrefix, prefix)),
176 addAll: (routes) => addAll(routes.map(prefixRoute(prefix))) as any,
177 add: (method, path, handler, options) =>
178 addAll([
179 makeRoute({
180 method,
181 path: prefixPath(path, prefix) as PathInput,
182 handler: HttpServerResponse.isHttpServerResponse(handler) ?
183 Effect.succeed(handler) :
184 Effect.isEffect(handler)
185 ? handler
186 : Effect.flatMap(HttpServerRequest.HttpServerRequest, handler),
187 uninterruptible: options?.uninterruptible ?? false,
188 prefix
189 })
190 ])
191 })
192 },
193 addAll,
194 add: (method, path, handler, options) => addAll([route(method, path, handler, options)]),
195 addGlobalMiddleware: (middleware_) =>
196 Effect.sync(() => {
197 middleware.add(middleware_ as any)
198 }),
199 asHttpEffect() {
200 let handler = Effect.withFiber<HttpServerResponse.HttpServerResponse, unknown>((fiber) => {
201 let context = fiber.context
202 const request = Context.getUnsafe(context, HttpServerRequest.HttpServerRequest)
203 let result = router.find(request.method, request.url)
204 if (result === undefined && request.method === "HEAD") {
205 result = router.find("GET", request.url)
206 }
207 if (result === undefined) {
208 return Effect.fail(
209 new HttpServerError.HttpServerError({
210 reason: new HttpServerError.RouteNotFound({ request })
211 })
212 )
213 }
214 const route = result.handler
215 if (Option.isSome(route.prefix)) {
216 context = Context.add(
217 context,
218 HttpServerRequest.HttpServerRequest,
219 sliceRequestUrl(request, route.prefix.value)
220 )
221 }
222 context = Context.add(context, HttpServerRequest.ParsedSearchParams, result.searchParams)
223 context = Context.add(context, RouteContext, {
224 route,
225 params: result.params
226 })
227
228 if (fiber.getRef(Tracer.Tracer) !== Tracer.nativeTracer) {
229 const span = Context.getOrUndefined(context, Tracer.ParentSpan)
230 if (span !== undefined && span._tag === "Span" && span.sampled) {
231 span.attribute("http.route", route.path)
232 }
233 }
234 // The enclosing request restores the fiber context on exit.
235 fiber.setContext(context)
236 if (!route.uninterruptible) {
237 const interrupted = fiberEnterInterruptibleUnsafe(fiber)
238 if (interrupted !== undefined) return interrupted
239 }
240 return route.handler as Effect.Effect<
241 HttpServerResponse.HttpServerResponse,
242 unknown
243 >
244 })
245 if (middleware.size === 0) return handler
246 for (const fn of Arr.reverse(middleware)) {
247 handler = fn(handler as any)
248 }
249 return handler
250 }
251 })
252})
253
254function sliceRequestUrl(request: HttpServerRequest.HttpServerRequest, prefix: string) {
255 const prefexLen = prefix.length
256 return request.modify({ url: request.url.length <= prefexLen ? "/" : request.url.slice(prefexLen) })
257}
258
259/**
260 * Context reference for low-level router configuration.
261 *
262 * **Details**
263 *
264 * The value is passed to the route matcher when an `HttpRouter` is created and
265 * defaults to an empty configuration.
266 *
267 * @stability unstable
268 * @category services
269 * @since 4.0.0
270 */
271export const RouterConfig = Context.Reference<Partial<FindMyWay.RouterConfig>>(
272 "effect/http/HttpRouter/RouterConfig",
273 { defaultValue: () => ({}) }
274)
275
276/**
277 * Service for the matched HTTP route in the current request.
278 *
279 * **When to use**
280 *
281 * Use to read captured path parameters and route metadata while handling a
282 * request matched by the router.
283 *
284 * **Details**
285 *
286 * It provides the route definition and the path parameters captured by the route
287 * matcher.
288 *
289 * @stability unstable
290 * @category services
291 * @since 4.0.0
292 */
293export class RouteContext extends Context.Service<RouteContext, {
294 readonly params: Readonly<Record<string, string | undefined>>
295 readonly route: Route<unknown, unknown>
296}>()("effect/http/HttpRouter/RouteContext") {}
297
298/**
299 * Effect that returns the path parameters captured for the current matched route.
300 *
301 * @stability unstable
302 * @category getters
303 * @since 4.0.0
304 */
305export const params: Effect.Effect<
306 ReadonlyRecord<string, string | undefined>,
307 never,
308 RouteContext
309> = Effect.withFiberSucceed((fiber) =>
310 Context.getUnsafe(fiber.context as Context.Context<RouteContext>, RouteContext).params
311)
312
313/**
314 * Decodes a schema from the current request and its JSON body.
315 *
316 * **Details**
317 *
318 * The input passed to the schema includes the request method, URL, headers,
319 * cookies, path parameters, search parameters, and parsed JSON body. The effect
320 * fails if the body cannot be parsed or the schema decode fails.
321 *
322 * @stability unstable
323 * @category schemas
324 * @since 4.0.0
325 */
326export const schemaJson = <
327 A,
328 I extends Partial<{
329 readonly method: HttpMethod.HttpMethod
330 readonly url: string
331 readonly cookies: Readonly<Record<string, string | undefined>>
332 readonly headers: Readonly<Record<string, string | undefined>>
333 readonly pathParams: Readonly<Record<string, string | undefined>>
334 readonly searchParams: Readonly<Record<string, string | ReadonlyArray<string> | undefined>>
335 readonly body: any
336 }>,
337 RD
338>(
339 schema: Schema.ConstraintCodec<A, I, RD, unknown>,
340 options?: (ParseOptions & HttpIncomingMessage.JsonOptions) | undefined
341): Effect.Effect<
342 A,
343 HttpServerError.HttpServerError | Schema.SchemaError,
344 HttpServerRequest.HttpServerRequest | HttpServerRequest.ParsedSearchParams | RouteContext | RD
345> => {
346 const parse = Schema.decodeUnknownEffect(schema)
347 const parseBody = HttpIncomingMessage.schemaBodyJson(Schema.Unknown, options)
348 return Effect.contextWith(
349 (
350 context: Context.Context<
351 HttpServerRequest.HttpServerRequest | HttpServerRequest.ParsedSearchParams | RouteContext
352 >
353 ) => {
354 const request = Context.get(context, HttpServerRequest.HttpServerRequest)
355 const searchParams = Context.get(context, HttpServerRequest.ParsedSearchParams)
356 const routeContext = Context.get(context, RouteContext)
357 return Effect.flatMap(parseBody(request), (body) =>
358 parse({
359 method: request.method,
360 url: request.url,
361 headers: request.headers,
362 cookies: request.cookies,
363 pathParams: routeContext.params,
364 searchParams,
365 body
366 }, options))
367 }
368 )
369}
370
371/**
372 * Decodes a schema from the current request without reading the request body.
373 *
374 * **Details**
375 *
376 * The input passed to the schema includes the request method, URL, headers,
377 * cookies, path parameters, and search parameters.
378 *
379 * @stability unstable
380 * @category schemas
381 * @since 4.0.0
382 */
383export const schemaNoBody = <
384 A,
385 I extends Partial<{
386 readonly method: HttpMethod.HttpMethod
387 readonly url: string
388 readonly cookies: Readonly<Record<string, string | undefined>>
389 readonly headers: Readonly<Record<string, string | undefined>>
390 readonly pathParams: Readonly<Record<string, string | undefined>>
391 readonly searchParams: Readonly<Record<string, string | ReadonlyArray<string> | undefined>>
392 }>,
393 RD
394>(
395 schema: Schema.ConstraintCodec<A, I, RD, unknown>,
396 options?: ParseOptions | undefined
397): Effect.Effect<
398 A,
399 Schema.SchemaError,
400 HttpServerRequest.HttpServerRequest | HttpServerRequest.ParsedSearchParams | RouteContext | RD
401> => {
402 const parse = Schema.decodeUnknownEffect(schema)
403 return Effect.contextWith(
404 (
405 context: Context.Context<
406 HttpServerRequest.HttpServerRequest | HttpServerRequest.ParsedSearchParams | RouteContext
407 >
408 ) => {
409 const request = Context.get(context, HttpServerRequest.HttpServerRequest)
410 const searchParams = Context.get(context, HttpServerRequest.ParsedSearchParams)
411 const routeContext = Context.get(context, RouteContext)
412 return parse({
413 method: request.method,
414 url: request.url,
415 headers: request.headers,
416 cookies: request.cookies,
417 pathParams: routeContext.params,
418 searchParams
419 }, options)
420 }
421 )
422}
423
424/**
425 * Decodes a schema from the current route path parameters and search parameters.
426 *
427 * **Details**
428 *
429 * When the same key appears in both sources, the path parameter value is used.
430 *
431 * @stability unstable
432 * @category schemas
433 * @since 4.0.0
434 */
435export const schemaParams = <A, I extends Readonly<Record<string, string | ReadonlyArray<string> | undefined>>, RD>(
436 schema: Schema.ConstraintCodec<A, I, RD, unknown>,
437 options?: ParseOptions | undefined
438): Effect.Effect<A, Schema.SchemaError, HttpServerRequest.ParsedSearchParams | RouteContext | RD> => {
439 const parse = Schema.decodeUnknownEffect(schema)
440 return Effect.contextWith((context: Context.Context<HttpServerRequest.ParsedSearchParams | RouteContext>) => {
441 const searchParams = Context.get(context, HttpServerRequest.ParsedSearchParams)
442 const routeContext = Context.get(context, RouteContext)
443 return parse({ ...searchParams, ...routeContext.params }, options)
444 })
445}
446
447/**
448 * Decodes a schema from the path parameters captured for the current matched
449 * route.
450 *
451 * @stability unstable
452 * @category schemas
453 * @since 4.0.0
454 */
455export const schemaPathParams = <A, I extends Readonly<Record<string, string | undefined>>, RD>(
456 schema: Schema.ConstraintCodec<A, I, RD, unknown>,
457 options?: ParseOptions | undefined
458): Effect.Effect<A, Schema.SchemaError, RouteContext | RD> => {
459 const parse = Schema.decodeUnknownEffect(schema)
460 return Effect.flatMap(params, (_) => parse(_, options))
461}
462
463/**
464 * Creates a layer that accesses the current `HttpRouter` service and runs the
465 * supplied effect.
466 *
467 * **When to use**
468 *
469 * Use when you need to register routes or middleware with the router during layer
470 * construction.
471 *
472 * **Example** (Registering routes during layer construction)
473 *
474 * ```ts import.meta.vitest
475 * import { Effect } from "effect"
476 * import { HttpRouter, HttpServerResponse } from "effect/http"
477 *
478 * const Routes = HttpRouter.use((router) =>
479 * router.add("GET", "/health", HttpServerResponse.text("ready"))
480 * )
481 *
482 * const program = Effect.acquireUseRelease(
483 * Effect.sync(() => HttpRouter.toWebHandler(Routes, { disableLogger: true })),
484 * ({ handler }) =>
485 * Effect.gen(function*() {
486 * const response = yield* Effect.promise(() => handler(new Request("http://localhost/health")))
487 * const body = yield* Effect.promise(() => response.text())
488 * return body
489 * }),
490 * ({ dispose }) => Effect.promise(dispose)
491 * )
492 *
493 * await Effect.runPromise(program) // => "ready"
494 * ```
495 *
496 * @stability unstable
497 * @category layers
498 * @since 4.0.0
499 */
500export const use = <A, E, R>(
501 f: (router: HttpRouter) => Effect.Effect<A, E, R>
502): Layer.Layer<never, E, HttpRouter | Exclude<R, Scope.Scope>> => Layer.effectDiscard(Effect.flatMap(HttpRouter, f))
503
504/**
505 * Create a layer that adds a single route to the HTTP router.
506 *
507 * **Example** (Adding a GET route)
508 *
509 * ```ts import.meta.vitest
510 * import { Layer } from "effect"
511 * import { HttpRouter, HttpServerResponse } from "effect/http"
512 *
513 * const Route = HttpRouter.add(
514 * "GET",
515 * "/hello",
516 * HttpServerResponse.text("Hello, World!")
517 * )
518 * Layer.isLayer(Route) // => true
519 * ```
520 *
521 * @stability unstable
522 * @category layers
523 * @since 4.0.0
524 */
525export const add = <E = never, R = never>(
526 method: "*" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "QUERY",
527 path: PathInput,
528 handler:
529 | HttpServerResponse.HttpServerResponse
530 | Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
531 | ((request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>),
532 options?: {
533 readonly uninterruptible?: boolean | undefined
534 }
535): Layer.Layer<never, never, HttpRouter | Request.From<"Requires", Exclude<R, Provided>> | Request.From<"Error", E>> =>
536 use((router) => router.add(method, path, handler, options))
537
538/**
539 * Create a layer that adds multiple routes to the HTTP router.
540 *
541 * **Example** (Adding multiple routes)
542 *
543 * ```ts import.meta.vitest
544 * import { Layer } from "effect"
545 * import { HttpRouter, HttpServerResponse } from "effect/http"
546 *
547 * const Routes = HttpRouter.addAll([
548 * HttpRouter.route(
549 * "GET",
550 * "/hello",
551 * HttpServerResponse.text("Hello, World!")
552 * )
553 * ])
554 * Layer.isLayer(Routes) // => true
555 * ```
556 *
557 * @stability unstable
558 * @category layers
559 * @since 4.0.0
560 */
561export const addAll = <Routes extends ReadonlyArray<Route<any, any>>, EX = never, RX = never>(
562 routes: Routes | Effect.Effect<Routes, EX, RX>,
563 options?: {
564 readonly prefix?: string | undefined
565 }
566): Layer.Layer<
567 never,
568 EX,
569 | HttpRouter
570 | Exclude<RX, Scope.Scope>
571 | Request.From<"Requires", Exclude<Route.Context<Routes[number]>, Provided>>
572 | Request.From<"Error", Route.Error<Routes[number]>>
573> =>
574 Layer.effectDiscard(Effect.gen(function*() {
575 const toAdd = Effect.isEffect(routes) ? yield* routes : routes
576 let router = yield* HttpRouter
577 if (options?.prefix) {
578 router = router.prefixed(options.prefix)
579 }
580 yield* router.addAll(toAdd)
581 }))
582
583/**
584 * Layer that provides a newly constructed `HttpRouter`.
585 *
586 * @stability unstable
587 * @category layers
588 * @since 4.0.0
589 */
590export const layer: Layer.Layer<HttpRouter> = Layer.effect(HttpRouter)(make)
591
592/**
593 * Builds an application layer with a router and returns the router as an HTTP
594 * handler effect.
595 *
596 * **Details**
597 *
598 * The returned effect handles the current `HttpServerRequest` in the current
599 * `Scope`; route request markers are converted into the ordinary requirements of
600 * the returned handler.
601 *
602 * **Gotchas**
603 *
604 * The layer is built with a forked `MemoMap`, so calling `toHttpEffect` twice
605 * will build the same layer twice.
606 *
607 * @stability unstable
608 * @category converting
609 * @since 4.0.0
610 */
611export const toHttpEffect = <A, E, R>(
612 appLayer: Layer.Layer<A, E, R>
613): Effect.Effect<
614 Effect.Effect<
615 HttpServerResponse.HttpServerResponse,
616 Request.Only<"Error", R> | Request.Only<"GlobalError", R> | HttpServerError.HttpServerError,
617 Scope.Scope | HttpServerRequest.HttpServerRequest | Request.Only<"Requires", R> | Request.Only<"GlobalRequires", R>
618 >,
619 Request.Without<E>,
620 Exclude<Request.Without<R>, HttpRouter> | Scope.Scope
621> =>
622 Effect.contextWith((context) => {
623 const scope = Context.get(context, Scope.Scope)
624 const memoMap = Layer.CurrentMemoMap.forkOrCreate(context)
625 return Effect.map(Layer.buildWithMemoMap(Layer.provideMerge(appLayer, layer), memoMap, scope), (context) => {
626 const router = Context.get(context, HttpRouter)
627 return router.asHttpEffect()
628 })
629 }) as any
630
631const RouteTypeId = "~effect/http/HttpRouter/Route"
632
633/**
634 * Description of a registered HTTP route.
635 *
636 * **Details**
637 *
638 * A route pairs an HTTP method and path pattern with a response handler, plus
639 * metadata used for prefix handling and interruptibility.
640 *
641 * @stability unstable
642 * @category routes
643 * @since 4.0.0
644 */
645export interface Route<E = never, R = never> {
646 readonly [RouteTypeId]: typeof RouteTypeId
647 readonly method: HttpMethod.HttpMethod | "*"
648 readonly path: PathInput
649 readonly handler: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
650 readonly uninterruptible: boolean
651 readonly prefix: Option.Option<string>
652}
653
654/**
655 * Helper types for extracting the error and context types carried by `Route`
656 * values.
657 *
658 * @stability unstable
659 * @since 4.0.0
660 */
661export declare namespace Route {
662 /**
663 * Extracts the error type produced by a `Route` handler.
664 *
665 * @stability unstable
666 * @category routes
667 * @since 4.0.0
668 */
669 export type Error<R extends Route<any, any>> = R extends Route<infer E, infer _R> ? E : never
670
671 /**
672 * Extracts the context requirements of a `Route` handler.
673 *
674 * @stability unstable
675 * @category routes
676 * @since 4.0.0
677 */
678 export type Context<T extends Route<any, any>> = T extends Route<infer _E, infer R> ? R : never
679}
680
681const makeRoute = <E, R>(options: {
682 readonly method: HttpMethod.HttpMethod | "*"
683 readonly path: PathInput
684 readonly handler: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
685 readonly uninterruptible?: boolean | undefined
686 readonly prefix?: Option.Option<string> | string | undefined
687}): Route<E, Exclude<R, Provided>> => (({
688 ...options,
689 uninterruptible: options.uninterruptible ?? false,
690 prefix: typeof options.prefix === "string" ? Option.some(options.prefix) : options.prefix ?? Option.none(),
691 [RouteTypeId]: RouteTypeId
692}) as Route<E, Exclude<R, Provided>>)
693
694/**
695 * Constructs a `Route` from an HTTP method, path, and handler.
696 *
697 * **Details**
698 *
699 * The handler may be a static response, an effect that produces a response, or a
700 * function from the current request to a response effect. Set `uninterruptible` to
701 * prevent the route handler from being made interruptible while it runs.
702 *
703 * @stability unstable
704 * @category routes
705 * @since 4.0.0
706 */
707export const route = <E = never, R = never>(
708 method: "*" | HttpMethod.HttpMethod,
709 path: PathInput,
710 handler:
711 | HttpServerResponse.HttpServerResponse
712 | Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>
713 | ((request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>),
714 options?: {
715 readonly uninterruptible?: boolean | undefined
716 }
717): Route<E, Exclude<R, Provided>> =>
718 makeRoute({
719 ...options,
720 method,
721 path,
722 handler: HttpServerResponse.isHttpServerResponse(handler) ?
723 Effect.succeed(handler) :
724 Effect.isEffect(handler)
725 ? handler
726 : Effect.flatMap(HttpServerRequest.HttpServerRequest, handler),
727 uninterruptible: options?.uninterruptible ?? false
728 })
729
730/**
731 * Path pattern accepted by the router. Routes must use an absolute path
732 * beginning with `/` or the wildcard `*`.
733 *
734 * @stability unstable
735 * @category models
736 * @since 4.0.0
737 */
738export type PathInput = `/${string}` | "*"
739
740const removeTrailingSlash = (
741 path: PathInput
742): PathInput => (path.endsWith("/") ? path.slice(0, -1) : path) as any
743
744/**
745 * Adds a path prefix to a route path.
746 *
747 * **Details**
748 *
749 * Trailing slashes are removed from the prefix; `/` becomes the prefix itself and
750 * `*` becomes a wildcard route under the prefix.
751 *
752 * @stability unstable
753 * @category transforming
754 * @since 4.0.0
755 */
756export const prefixPath: {
757 (prefix: string): (self: string) => string
758 (self: string, prefix: string): string
759} = dual(2, (self: string, prefix: string) => {
760 prefix = removeTrailingSlash(prefix as PathInput)
761 if (self === "*") return `${prefix}/*`
762 else if (self === "/") return prefix
763 return prefix + self
764})
765
766/**
767 * Returns a copy of a route with its path prefixed.
768 *
769 * **Details**
770 *
771 * The prefix is also tracked on the route so that, when the route handles a
772 * request, the matched prefix can be removed from the request URL seen by the
773 * handler.
774 *
775 * @stability unstable
776 * @category routes
777 * @since 4.0.0
778 */
779export const prefixRoute: {
780 (prefix: string): <E, R>(self: Route<E, R>) => Route<E, R>
781 <E, R>(self: Route<E, R>, prefix: string): Route<E, R>
782} = dual(2, <E, R>(self: Route<E, R>, prefix: string): Route<E, R> =>
783 makeRoute({
784 ...self,
785 path: prefixPath(self.path, prefix) as PathInput,
786 prefix: Option.match(self.prefix, {
787 onNone: () => removeTrailingSlash(prefix as PathInput),
788 onSome: (existingPrefix) => prefixPath(existingPrefix, prefix) as string
789 })
790 }))
791
792/**
793 * Represents a request-level dependency, that needs to be provided by
794 * middleware.
795 *
796 * @stability unstable
797 * @category utility types
798 * @since 4.0.0
799 */
800export interface Request<Kind extends string, T> {
801 readonly _: unique symbol
802 readonly kind: Kind
803 readonly type: T
804}
805
806/**
807 * Helper types for request-level dependency markers used by router layers and
808 * middleware.
809 *
810 * @stability unstable
811 * @since 4.0.0
812 */
813export declare namespace Request {
814 /**
815 * Wraps a type in a request-level marker of the supplied kind.
816 *
817 * @stability unstable
818 * @category utility types
819 * @since 4.0.0
820 */
821 export type From<Kind extends string, R> = R extends infer T ? Request<Kind, T> : never
822
823 /**
824 * Extracts the payload types from request-level markers that have the supplied
825 * kind.
826 *
827 * @stability unstable
828 * @category utility types
829 * @since 4.0.0
830 */
831 export type Only<Kind extends string, A> = A extends Request<Kind, infer T> ? T : never
832
833 /**
834 * Removes request-level markers from a union, leaving only ordinary requirement
835 * or error types.
836 *
837 * @stability unstable
838 * @category utility types
839 * @since 4.0.0
840 */
841 export type Without<A> = A extends Request<infer _Kind, infer _> ? never : A
842}
843
844/**
845 * Services provided by the HTTP router, which are available in the
846 * request context.
847 *
848 * @stability unstable
849 * @category utility types
850 * @since 4.0.0
851 */
852export type Provided =
853 | HttpServerRequest.HttpServerRequest
854 | Scope.Scope
855 | HttpServerRequest.ParsedSearchParams
856 | RouteContext
857
858/**
859 * Services provided to global middleware.
860 *
861 * @stability unstable
862 * @category utility types
863 * @since 4.0.0
864 */
865export type GlobalProvided =
866 | HttpServerRequest.HttpServerRequest
867 | Scope.Scope
868
869const MiddlewareTypeId = "~effect/http/HttpRouter/Middleware"
870
871/**
872 * Composable descriptor for route-scoped HTTP router middleware.
873 *
874 * **Details**
875 *
876 * Its `layer` can be provided to route layers, and `combine` composes middleware
877 * while tracking provided services, handled errors, and remaining requirements at
878 * the type level.
879 *
880 * @stability unstable
881 * @category middleware
882 * @since 4.0.0
883 */
884export interface Middleware<
885 Config extends {
886 provides: any
887 handles: any
888 error: any
889 requires: any
890 layerError: any
891 layerRequires: any
892 }
893> {
894 readonly [MiddlewareTypeId]: Config
895
896 readonly layer: [Config["requires"]] extends [never] ? Layer.Layer<
897 | Request.From<"Requires", Config["provides"]>
898 | Request.From<"Error", Config["handles"]>,
899 Config["layerError"],
900 | Config["layerRequires"]
901 | Request.From<"Requires", Config["requires"]>
902 | Request.From<"Error", Config["error"]>
903 >
904 : "Need to .combine(middleware) that satisfy the missing request dependencies"
905
906 readonly combine: <
907 Config2 extends {
908 provides: any
909 handles: any
910 error: any
911 requires: any
912 layerError: any
913 layerRequires: any
914 }
915 >(other: Middleware<Config2>) => Middleware<{
916 provides: Config2["provides"] | Config["provides"]
917 handles: Config2["handles"] | Config["handles"]
918 error: Config2["error"] | Exclude<Config["error"], Config2["handles"]>
919 requires: Exclude<Config["requires"], Config2["provides"]> | Config2["requires"]
920 layerError: Config["layerError"] | Config2["layerError"]
921 layerRequires: Config["layerRequires"] | Config2["layerRequires"]
922 }>
923}
924
925/**
926 * Create a middleware layer that can be used to modify requests and responses.
927 *
928 * **Details**
929 *
930 * By default, the middleware only affects the routes that it is provided to.
931 *
932 * If you want to create a middleware that applies globally to all routes, pass
933 * the `global` option as `true`.
934 *
935 * **Example** (Applying route and global middleware)
936 *
937 * ```ts import.meta.vitest
938 * import { Effect, Layer } from "effect"
939 * import { HttpRouter, HttpServerResponse } from "effect/http"
940 *
941 * const RouteMiddleware = HttpRouter.middleware((httpEffect) =>
942 * Effect.map(httpEffect, HttpServerResponse.setHeader("x-route", "route"))
943 * ).layer
944 *
945 * const GlobalMiddleware = HttpRouter.middleware(
946 * (httpEffect) => Effect.map(httpEffect, HttpServerResponse.setHeader("x-global", "global")),
947 * { global: true }
948 * )
949 *
950 * const Routes = HttpRouter.add("GET", "/hello", HttpServerResponse.text("Hello")).pipe(
951 * Layer.provide(RouteMiddleware)
952 * )
953 * const App = Layer.mergeAll(Routes, GlobalMiddleware)
954 *
955 * const program = Effect.acquireUseRelease(
956 * Effect.sync(() => HttpRouter.toWebHandler(App, { disableLogger: true })),
957 * ({ handler }) =>
958 * Effect.gen(function*() {
959 * const response = yield* Effect.promise(() => handler(new Request("http://localhost/hello")))
960 * return [response.headers.get("x-route"), response.headers.get("x-global")]
961 * }),
962 * ({ dispose }) => Effect.promise(dispose)
963 * )
964 *
965 * await Effect.runPromise(program) // => ["route", "global"]
966 * ```
967 *
968 * @stability unstable
969 * @category middleware
970 * @since 4.0.0
971 */
972export const middleware:
973 & middleware.Make<never, never>
974 & (<
975 Config extends {
976 provides?: any
977 handles?: any
978 } = {}
979 >() => middleware.Make<
980 Config extends { provides: infer R } ? R : never,
981 Config extends { handles: infer E } ? E : never
982 >) = function() {
983 if (arguments.length === 0) {
984 return makeMiddleware as any
985 }
986 return makeMiddleware(arguments[0], arguments[1]) as any
987 }
988
989const makeMiddleware = (middleware: any, options?: {
990 readonly global?: boolean | undefined
991}) =>
992 options?.global ?
993 Layer.effectDiscard(Effect.gen(function*() {
994 const router = yield* HttpRouter
995 const fn = Effect.isEffect(middleware) ? yield* middleware : middleware
996 yield* router.addGlobalMiddleware(fn)
997 }))
998 : new MiddlewareImpl(
999 Effect.isEffect(middleware) ?
1000 Layer.effectContext(Effect.map(middleware, (fn) => Context.makeUnsafe(new Map([[fnContextKey, fn]])))) :
1001 Layer.succeedContext(Context.makeUnsafe(new Map([[fnContextKey, middleware]]))) as any
1002 )
1003
1004let middlewareId = 0
1005const fnContextKey = "effect/http/HttpRouter/MiddlewareFn"
1006
1007class MiddlewareImpl<
1008 Config extends {
1009 provides: any
1010 handles: any
1011 error: any
1012 requires: any
1013 layerError: any
1014 layerRequires: any
1015 }
1016> implements Middleware<Config> {
1017 readonly [MiddlewareTypeId]: Config = {} as any
1018
1019 readonly layerFn: Layer.Layer<never>
1020 readonly dependencies?: Layer.Layer<any, any, any> | undefined
1021
1022 constructor(
1023 layerFn: Layer.Layer<never>,
1024 dependencies?: Layer.Layer<any, any, any>
1025 ) {
1026 this.layerFn = layerFn
1027 this.dependencies = dependencies
1028 const contextKey = `effect/http/HttpRouter/Middleware-${++middlewareId}` as const
1029 this.layer = Layer.effectContext(Effect.gen({ self: this }, function*() {
1030 const context = yield* Effect.context<Scope.Scope>()
1031 const stack = [Context.getOrUndefinedUnsafe(context, fnContextKey)]
1032 if (this.dependencies) {
1033 const memoMap = yield* Layer.CurrentMemoMap
1034 const scope = Context.get(context, Scope.Scope)
1035 const depsContext = yield* Layer.buildWithMemoMap(this.dependencies, memoMap, scope)
1036 stack.push(...getMiddleware(depsContext))
1037 }
1038 return Context.makeUnsafe<never>(new Map([[contextKey, stack]]))
1039 })).pipe(Layer.provide(this.layerFn))
1040 }
1041
1042 layer: any
1043
1044 combine<
1045 Config2 extends {
1046 provides: any
1047 handles: any
1048 error: any
1049 requires: any
1050 layerError: any
1051 layerRequires: any
1052 }
1053 >(other: Middleware<Config2>): Middleware<any> {
1054 return new MiddlewareImpl(
1055 this.layerFn,
1056 this.dependencies ? Layer.provideMerge(this.dependencies, other.layer as any) : other.layer as any
1057 ) as any
1058 }
1059}
1060
1061const middlewareCache = new WeakMap<Context.Context<never>, any>()
1062const getMiddleware = (context: Context.Context<never>): Array<middleware.Fn> => {
1063 let arr = middlewareCache.get(context)
1064 if (arr) return arr
1065 const topLevel = Arr.empty<Array<middleware.Fn>>()
1066 let maxLength = 0
1067 for (const [key, value] of context.mapUnsafe) {
1068 if (key.startsWith("effect/http/HttpRouter/Middleware-")) {
1069 topLevel.push(value)
1070 if (value.length > maxLength) {
1071 maxLength = value.length
1072 }
1073 }
1074 }
1075 if (topLevel.length === 0) {
1076 arr = []
1077 } else {
1078 const middleware = new Set<middleware.Fn>()
1079 for (let i = maxLength - 1; i >= 0; i--) {
1080 for (const arr of topLevel) {
1081 if (i < arr.length) {
1082 middleware.add(arr[i])
1083 }
1084 }
1085 }
1086 arr = Arr.fromIterable(middleware).reverse()
1087 }
1088 middlewareCache.set(context, arr)
1089 return arr
1090}
1091
1092/**
1093 * Types used by the `middleware` constructor.
1094 *
1095 * @stability unstable
1096 * @since 4.0.0
1097 */
1098export declare namespace middleware {
1099 /**
1100 * Overloaded constructor type for router middleware.
1101 *
1102 * **Details**
1103 *
1104 * It builds either a route-scoped `Middleware` or, when `global` is `true`, a
1105 * layer that installs middleware for all routes. The type tracks provided
1106 * services, handled errors, middleware failures, and remaining requirements.
1107 *
1108 * @stability unstable
1109 * @category middleware
1110 * @since 4.0.0
1111 */
1112 export type Make<Provides = never, Handles = never> = {
1113 <E, R, EX, RX, const Global extends boolean = false>(
1114 middleware: Effect.Effect<
1115 (
1116 effect: Effect.Effect<
1117 HttpServerResponse.HttpServerResponse,
1118 Types.NoInfer<Handles | Types.unhandled>,
1119 Types.NoInfer<Provides>
1120 >
1121 ) =>
1122 & Effect.Effect<
1123 HttpServerResponse.HttpServerResponse,
1124 E,
1125 R
1126 >
1127 & (Types.unhandled extends E ? unknown : "You must only handle the configured errors"),
1128 EX,
1129 RX
1130 >,
1131 options?: {
1132 readonly global?: Global | undefined
1133 }
1134 ): Global extends true ? Layer.Layer<
1135 | Request.From<"Requires", Provides>
1136 | Request.From<"Error", Handles>
1137 | Request.From<"GlobalRequires", Provides>
1138 | Request.From<"GlobalError", Handles>,
1139 EX,
1140 | HttpRouter
1141 | Exclude<RX, Scope.Scope>
1142 | Request.From<"GlobalRequires", Exclude<R, GlobalProvided>>
1143 | Request.From<"GlobalError", Exclude<E, Types.unhandled>>
1144 > :
1145 Middleware<{
1146 provides: Provides
1147 handles: Handles
1148 error: Exclude<E, Types.unhandled>
1149 requires: Exclude<R, Provided>
1150 layerError: EX
1151 layerRequires: Exclude<RX, Scope.Scope>
1152 }>
1153 <E, R, const Global extends boolean = false>(
1154 middleware:
1155 & ((
1156 effect: Effect.Effect<
1157 HttpServerResponse.HttpServerResponse,
1158 Types.NoInfer<Handles | Types.unhandled>,
1159 Types.NoInfer<Provides>
1160 >
1161 ) => Effect.Effect<
1162 HttpServerResponse.HttpServerResponse,
1163 E,
1164 R
1165 >)
1166 & (Types.unhandled extends E ? unknown : "You must only handle the configured errors"),
1167 options?: {
1168 readonly global?: Global | undefined
1169 }
1170 ): Global extends true ? Layer.Layer<
1171 | Request.From<"Requires", Provides>
1172 | Request.From<"Error", Handles>
1173 | Request.From<"GlobalRequires", Provides>
1174 | Request.From<"GlobalError", Handles>,
1175 never,
1176 | HttpRouter
1177 | Request.From<"GlobalRequires", Exclude<R, GlobalProvided>>
1178 | Request.From<"GlobalError", Exclude<E, Types.unhandled>>
1179 > :
1180 Middleware<{
1181 provides: Provides
1182 handles: Handles
1183 error: Exclude<E, Types.unhandled>
1184 requires: Exclude<R, Provided>
1185 layerError: never
1186 layerRequires: never
1187 }>
1188 }
1189
1190 /**
1191 * Function that transforms an HTTP response effect into another HTTP response
1192 * effect.
1193 *
1194 * @stability unstable
1195 * @category middleware
1196 * @since 4.0.0
1197 */
1198 export type Fn = (
1199 effect: Effect.Effect<HttpServerResponse.HttpServerResponse>
1200 ) => Effect.Effect<HttpServerResponse.HttpServerResponse>
1201}
1202
1203/**
1204 * Middleware that applies CORS headers to the HTTP response.
1205 *
1206 * @stability unstable
1207 * @category layers
1208 * @since 4.0.0
1209 */
1210export const cors = (
1211 options?: {
1212 readonly allowedOrigins?: ReadonlyArray<string> | undefined
1213 readonly allowedMethods?: ReadonlyArray<string> | undefined
1214 readonly allowedHeaders?: ReadonlyArray<string> | undefined
1215 readonly exposedHeaders?: ReadonlyArray<string> | undefined
1216 readonly maxAge?: number | undefined
1217 readonly credentials?: boolean | undefined
1218 } | undefined
1219): Layer.Layer<never, never, HttpRouter> => middleware(HttpMiddleware.cors(options), { global: true })
1220
1221/**
1222 * Middleware that disables the logger for some routes.
1223 *
1224 * **Example** (Disabling route logging)
1225 *
1226 * ```ts import.meta.vitest
1227 * import { Layer } from "effect"
1228 * import { HttpRouter, HttpServerResponse } from "effect/http"
1229 *
1230 * const Route = HttpRouter.add(
1231 * "GET",
1232 * "/hello",
1233 * HttpServerResponse.text("Hello, World!")
1234 * ).pipe(
1235 * // disable the logger for this route
1236 * Layer.provide(HttpRouter.disableLogger)
1237 * )
1238 * Layer.isLayer(Route) // => true
1239 * ```
1240 *
1241 * @stability unstable
1242 * @category layers
1243 * @since 4.0.0
1244 */
1245export const disableLogger: Layer.Layer<never> = middleware(HttpMiddleware.withLoggerDisabled).layer
1246
1247/**
1248 * Provides request-level dependencies to some routes.
1249 *
1250 * @stability unstable
1251 * @category layers
1252 * @since 4.0.0
1253 */
1254export const provideRequest =
1255 <A2, E2, R2>(layer: Layer.Layer<A2, E2, R2>) =>
1256 <A, E, R>(self: Layer.Layer<A, E, R>): Layer.Layer<
1257 A,
1258 E | E2,
1259 R2 | Exclude<R, Request.From<"Requires", A2>>
1260 > =>
1261 Layer.provide(
1262 self,
1263 middleware<{ provides: A2 }>()(Effect.gen(function*() {
1264 const services = yield* Layer.build(layer as Layer.Layer<A2>)
1265 return (effect) =>
1266 Effect.provideContext(effect, services) as Effect.Effect<
1267 HttpServerResponse.HttpServerResponse,
1268 Types.unhandled
1269 >
1270 })).layer
1271 )
1272
1273/**
1274 * Runs the provided application layer as an HTTP server.
1275 *
1276 * **Details**
1277 *
1278 * Layers first built inside the app are private to this server. Provide
1279 * services that must be shared with sibling layers outside `serve`.
1280 *
1281 * **Gotchas**
1282 *
1283 * The layer is built with a forked `MemoMap`, so calling `serve` twice will
1284 * build the same layer twice.
1285 *
1286 * @stability unstable
1287 * @category layers
1288 * @since 4.0.0
1289 */
1290export const serve = <A, E, R, HE, HR = Request.Only<"Requires", R> | Request.Only<"GlobalRequires", R>>(
1291 appLayer: Layer.Layer<A, E, R>,
1292 options?: {
1293 readonly routerConfig?: Partial<FindMyWay.RouterConfig> | undefined
1294 readonly disableLogger?: boolean | undefined
1295 readonly disableListenLog?: boolean
1296 /**
1297 * Middleware to apply to the HTTP server.
1298 *
1299 * **Gotchas**
1300 *
1301 * This middleware is applied to the entire HTTP server chain, including the
1302 * sending of the response. Changes to the response are not reflected in the
1303 * final response sent to the client. Use `HttpRouter.middleware` when
1304 * middleware must modify the response.
1305 */
1306 readonly middleware?: (
1307 effect: Effect.Effect<
1308 HttpServerResponse.HttpServerResponse,
1309 Request.Only<"Error", R> | Request.Only<"GlobalError", R> | HttpServerError.HttpServerError,
1310 | Scope.Scope
1311 | HttpServerRequest.HttpServerRequest
1312 | Request.Only<"Requires", R>
1313 | Request.Only<"GlobalRequires", R>
1314 >
1315 ) => Effect.Effect<HttpServerResponse.HttpServerResponse, HE, HR>
1316 }
1317): Layer.Layer<
1318 A,
1319 Request.Without<E>,
1320 HttpServer.HttpServer | Exclude<Request.Without<R> | Exclude<HR, GlobalProvided>, HttpRouter>
1321> => {
1322 let middleware: any = options?.middleware
1323 if (options?.disableLogger !== true) {
1324 middleware = middleware ? compose(middleware, HttpMiddleware.logger) : HttpMiddleware.logger
1325 }
1326 const layerWithRouter = Layer.provideMerge(
1327 appLayer,
1328 options?.routerConfig
1329 ? Layer.provide(layer, Layer.succeed(RouterConfig)(options.routerConfig))
1330 : layer
1331 )
1332 return Effect.contextWith((context) => {
1333 const memoMap = Layer.CurrentMemoMap.forkOrCreate(context)
1334 return Layer.buildWithMemoMap(layerWithRouter, memoMap, Context.get(context, Scope.Scope))
1335 }).pipe(
1336 Effect.map((context) => {
1337 const router = Context.get(context, HttpRouter)
1338 const handler = router.asHttpEffect()
1339 return Layer.merge(
1340 middleware ? HttpServer.serve(handler, middleware) : HttpServer.serve(handler),
1341 Layer.succeedContext(context)
1342 )
1343 }),
1344 Layer.unwrap,
1345 options?.disableListenLog ? identity : HttpServer.withLogAddress
1346 ) as any
1347}
1348
1349/**
1350 * Builds a Fetch-compatible request handler from an HTTP router application
1351 * layer.
1352 *
1353 * **Details**
1354 *
1355 * The result contains a `handler` function that converts Web `Request` values to
1356 * Web `Response` values and a `dispose` function for releasing the layer
1357 * resources. The layer is built immediately, so the cost is paid when the
1358 * handler is created rather than on the first request. A layer that performs
1359 * asynchronous work while building may still be in progress when the first
1360 * request arrives, in which case that request waits for the build to finish.
1361 * If the build fails, every request rejects with the build error.
1362 *
1363 * @stability unstable
1364 * @category converting
1365 * @since 4.0.0
1366 */
1367export const toWebHandler = <
1368 A,
1369 E,
1370 R extends
1371 | HttpRouter
1372 | Request<"Requires", any>
1373 | Request<"GlobalRequires", any>
1374 | Request<"Error", any>
1375 | Request<"GlobalError", any>,
1376 HE,
1377 HR = Exclude<Request.Only<"Requires", R> | Request.Only<"GlobalRequires", R>, A>,
1378 ReqR = Exclude<HR, A | Scope.Scope | HttpServerRequest.HttpServerRequest>
1379>(
1380 appLayer: Layer.Layer<A, E, R>,
1381 options?: {
1382 readonly memoMap?: Layer.MemoMap | undefined
1383 readonly routerConfig?: Partial<FindMyWay.RouterConfig> | undefined
1384 readonly disableLogger?: boolean | undefined
1385 /**
1386 * Middleware to apply to the HTTP server.
1387 *
1388 * **Gotchas**
1389 *
1390 * This middleware is applied to the entire HTTP server chain, including the
1391 * sending of the response. Changes to the response are not reflected in the
1392 * final response sent to the client. Use `HttpRouter.middleware` when
1393 * middleware must modify the response.
1394 */
1395 readonly middleware?: (
1396 effect: Effect.Effect<
1397 HttpServerResponse.HttpServerResponse,
1398 Request.Only<"Error", R> | Request.Only<"GlobalError", R> | HttpServerError.HttpServerError,
1399 | Scope.Scope
1400 | HttpServerRequest.HttpServerRequest
1401 | Request.Only<"Requires", R>
1402 | Request.Only<"GlobalRequires", R>
1403 >
1404 ) => Effect.Effect<HttpServerResponse.HttpServerResponse, HE, HR | GlobalProvided>
1405 }
1406): {
1407 readonly handler: [ReqR] extends [never]
1408 ? ((request: globalThis.Request, context?: Context.Context<never> | undefined) => Promise<Response>)
1409 : ((
1410 request: globalThis.Request,
1411 context: Context.Context<ReqR>
1412 ) => Promise<Response>)
1413 readonly dispose: () => Promise<void>
1414} => {
1415 let middleware: any = options?.middleware
1416 if (options?.disableLogger !== true) {
1417 middleware = middleware ? compose(middleware, HttpMiddleware.logger) : HttpMiddleware.logger
1418 }
1419 const RouterLayer = options?.routerConfig
1420 ? Layer.provide(layer, Layer.succeed(RouterConfig)(options.routerConfig))
1421 : layer
1422 return HttpEffect.toWebHandlerLayerWith(
1423 Layer.provideMerge(appLayer, RouterLayer) as Layer.Layer<A | HttpRouter, E>,
1424 {
1425 toHandler: (s) => Effect.succeed(Context.get(s, HttpRouter).asHttpEffect()),
1426 middleware,
1427 memoMap: options?.memoMap
1428 }
1429 )
1430}