Import
import { Matcher } from "@typed/router";Signatures
export interface Matcher<A, E = never, R = never> extends Fx.Fx<A, E | RouteNotFound | RouteDecodeError | RouteGuardError, R | Router | Scope.Scope>, Pipeable {
readonly cases: ReadonlyArray<MatchAst>;
match<Rt extends Route.Any, B, E2 = never, R2 = never>(route: Rt, handler: (params: RefSubject.RefSubject<Route.Type<Rt>>) => MatchHandlerReturnValue<B, E2, R2>): Matcher<A | B, E | E2, R | R2 | Scope.Scope>;
match<Rt extends Route.Any, B, E2 = never, R2 = never>(route: Rt, handler: Fx.Fx<B, E2, R2> | Effect.Effect<B, E2, R2> | Stream.Stream<B, E2, R2>): Matcher<A | B, E | E2, R | R2 | Scope.Scope>;
match<Rt extends Route.Any, B, E2 = never, R2 = never, D extends ReadonlyArray<AnyDependency> | undefined = undefined, LB = B, LE2 = never, LR2 = never, C extends CatchHandler<any, any, any, any> | undefined = undefined>(route: Rt, options: MatchHandlerOptions<Route.Type<Rt>, B, E2, R2, D, LB, LE2, LR2, C>): Matcher<A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["a"], E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["e"], R | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["r"] | Scope.Scope>;
match<Rt extends Route.Any, const B>(route: Rt, handler: B): Matcher<A | B, E, R | Scope.Scope>;
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B, E2 = never, R2 = never>(route: Rt, guard: G, handler: (params: RefSubject.RefSubject<GuardOutput<G>>) => MatchHandlerReturnValue<B, E2, R2>): Matcher<A | B, E | E2 | GuardError<G>, R | R2 | GuardServices<G> | Scope.Scope>;
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B, E2 = never, R2 = never>(route: Rt, guard: G, handler: Fx.Fx<B, E2, R2> | Effect.Effect<B, E2, R2> | Stream.Stream<B, E2, R2>): Matcher<A | B, E | E2 | GuardError<G>, R | R2 | GuardServices<G> | Scope.Scope>;
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B, E2 = never, R2 = never, D extends ReadonlyArray<AnyDependency> | undefined = undefined, LB = B, LE2 = never, LR2 = never, C extends CatchHandler<any, any, any, any> | undefined = undefined>(route: Rt, guard: G, options: MatchHandlerOptions<GuardOutput<G>, B, E2, R2, D, LB, LE2, LR2, C>): Matcher<A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["a"], E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["e"], R | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["r"] | Scope.Scope>;
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B>(route: Rt, guard: G, handler: B): Matcher<A | B, E | GuardError<G>, R | GuardServices<G> | Scope.Scope>;
match<Rt extends Route.Any, B, E2 = never, R2 = never, D extends ReadonlyArray<AnyDependency> | undefined = undefined, LB = B, LE2 = never, LR2 = never, C extends CatchHandler<any, any, any, any> | undefined = undefined>(options: MatchOptions<Rt, B, E2, R2, D, LB, LE2, LR2, C>): Matcher<A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["a"], E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["e"], R | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["r"] | Scope.Scope>;
readonly prefix: <Rt extends Route.Any>(route: Rt) => Matcher<A, E, R>;
readonly provide: <Layers extends readonly [
AnyLayer,
...AnyLayer[]
]>(...layers: Layers) => Matcher<A, E | LayerError<Layers[number]>, Exclude<R, LayerSuccess<Layers[number]>> | LayerServices<Layers[number]>>;
readonly provideService: <Id, S>(tag: Context.Service<Id, S>, service: S) => Matcher<A, E, Exclude<R, Id>>;
readonly provideContext: <R2>(services: Context.Context<R2>) => Matcher<A, E, Exclude<R, R2>>;
readonly catchCause: <B, E2, R2>(f: CatchHandler<E, B, E2, R2>) => Matcher<A | B, E2, R | R2>;
readonly catch: <B, E2, R2>(f: (e: E) => Fx.Fx<B, E2, R2>) => Matcher<A | B, E2, R | R2>;
readonly catchTag: <const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, B, E2, R2>(tag: K, f: (e: ExtractTag<NoInfer<E>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>) => Fx.Fx<B, E2, R2>) => Matcher<A | B, E2 | ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>, R | R2>;
readonly redirectTo: (path: string) => Fx.Fx<A, Exclude<E, RouteNotFound> | RouteDecodeError | RouteGuardError, R | Router | Scope.Scope>;
readonly layout: <B, E2, R2>(layout: Layout<any, A, E, R, B, E2, R2>) => Matcher<B, E | E2, R | R2>;
readonly merge: <const Others extends ReadonlyArray<Matcher.Any>>(...others: Others) => Matcher<A | Matcher.MergeSuccess<Others>, E | Matcher.MergeError<Others>, R | Matcher.MergeServices<Others>>;
}export declare namespace Matcher {
type Any = Matcher<any, any, any> | Matcher<any, never, any> | Matcher<any, any, never> | Matcher<any, never, never>;
type Success<T> = [
T
] extends [
Matcher<infer A, infer _E, infer _R>
] ? A : never;
type Error<T> = [
T
] extends [
Matcher<infer _A, infer E, infer _R>
] ? E : never;
type Services<T> = [
T
] extends [
Matcher<infer _A, infer _E, infer R>
] ? R : never;
type MergeSuccess<Matchers extends ReadonlyArray<Matcher.Any>> = Success<Matchers[number]>;
type MergeError<Matchers extends ReadonlyArray<Matcher.Any>> = Error<Matchers[number]>;
type MergeServices<Matchers extends ReadonlyArray<Matcher.Any>> = Services<Matchers[number]>;
}Why
A Matcher is both a declarative route table and Fx<A, E, R>. Route, guard, dependency, layout,
and catch composition therefore stays in Effect’s typed success, error, and service channels.
Ownership and lifetime
Building a Matcher is pure. Running it requires Router and Scope: it subscribes to CurrentPath, switches selected handlers when the route changes, reuses the same handler for parameter-only updates, and finalizes route/layout/layer scopes when selection changes or the consumer interrupts. Matching is case-insensitive and ignores trailing slashes. Distinct registered matcher paths use structural precedence (literal, constrained parameter, parameter, then wildcard), not a global first-declared rule. Registration order matters among compiled entries that share the same matcher path: their schemas and guards fall through in that order.
Property: cases
The immutable ordered Match AST compiled when this Matcher is run.
Property: cases: Why
Keeping cases visible preserves registration order for entries that compile to the same matcher path. Distinct matcher paths are still selected by structural path-shape precedence.
Property: cases: Ownership and lifetime
The array is retained by the Matcher value and acquires no runtime resources.
Property: catch
Handles the first typed failure in this Matcher’s cause.
Property: catch: Why
It provides an ergonomic typed-error boundary while retaining non-failure causes unchanged.
Property: catch: Ownership and lifetime
The replacement Fx is owned by the selected route Scope; defects and interruption are rethrown.
Property: catchCause
Handles complete Effect causes from this Matcher with a reactive Cause RefSubject.
Property: catchCause: Why
Defects, interruption, and typed failures remain distinguishable instead of collapsing to one error value.
Property: catchCause: Ownership and lifetime
The catch Fx is mounted in the same selected route Scope and is interrupted when the route changes.
Property: catchTag
Handles selected tagged failures while retaining unmatched failures in the error channel.
Property: catchTag: Why
Tagged recovery narrows only the handled variants instead of erasing the entire error union.
Property: catchTag: Ownership and lifetime
The replacement Fx shares the selected route Scope. Unmatched tags rethrow the original cause.
Property: layout
Wraps the Matcher’s selected content in a parameter-aware layout Fx.
Property: layout: Why
Layouts can compose output around nested content while observing the same reactive parameters.
Property: layout: Ownership and lifetime
Layouts are acquired inner-to-outer so each newly acquired outer layout receives the already- wrapped inner Fx as its content. The rendered nesting is therefore outer(inner(handler)). Stable layout identities receive parameter/content updates without remounting and finalize with the selected route Scope.
Method: match
Appends one route case with an optional guard, dependencies, layout, and local error boundary.
Method: match: Why
Nine overload families accept direct values, Effect, Stream, Fx, parameter functions, guarded
variants, and options without erasing their errors or service requirements. After path lookup,
candidates registered under the same matcher path are decoded and guarded in declaration order:
the first decoded guard Some wins; decode failure, guard None, or guard failure falls through.
Distinct path shapes are prioritized structurally, not solely by this call’s position.
Method: match: Ownership and lifetime
Registration is pure and returns a new Matcher. When run, candidate layers are prepared before the guard; rejected candidates roll them back. The selected handler, layout, and dependencies are owned by its route Scope and are interrupted on replacement.
Property: merge
Merge this matcher with one or more others. Combined matcher matches all routes; each matcher’s layouts/provide apply only to its own routes.
Property: merge: Why
Independent route tables can be assembled without moving their local providers or layouts to a global boundary.
Property: merge: Ownership and lifetime
Merge is pure and concatenates cases: this Matcher’s cases precede each supplied Matcher’s cases. That order governs decode/guard fallthrough only when entries share a matcher path; the path router chooses between distinct shapes according to its own specificity rules.
Property: prefix
Prefixes every case in this Matcher with another Route.
Property: prefix: Why
Nested applications can reuse a route table beneath a structural mount path.
Property: prefix: Ownership and lifetime
Composition is pure. Prefix services and schemas participate when the returned Matcher runs.
Property: provide
Provides one or more Effect Layers to this Matcher’s cases.
Property: provide: Why
Route-local services remain explicit in the Matcher type instead of leaking into the whole app.
Property: provide: Ownership and lifetime
Layers are acquired only for candidate evaluation, committed for the selected route, shared via Effect’s memo map where possible, and finalized when no selected case retains them.
Property: provideContext
Provides an existing Effect Context to this Matcher’s cases.
Property: provideContext: Why
Multiple already-constructed services can satisfy route requirements without rebuilding Layers.
Property: provideContext: Ownership and lifetime
The Context is retained by the Matcher; resource lifetime remains owned by whoever constructed it.
Property: provideService
Provides one concrete Effect service to this Matcher’s cases.
Property: provideService: Why
It is the concise constant-service form of provideContext.
Property: provideService: Ownership and lifetime
The supplied service is retained by the Matcher value; no acquisition or finalizer is added.
Property: redirectTo
Finishes route configuration with one redirect-and-retry on RouteNotFound.
Use this terminal method after adding routes, providers, layouts, and recovery. It returns an Fx rather than another Matcher because the redirect wraps the running route selection. Matched-handler, decoding, and guard failures retain their error channels and do not redirect. Construction starts no navigation; the consuming Scope owns execution and the single retry.
Examples
import * as Router from "@typed/router"
const app = Router.match(Router.Parse("/"), "home")
.match(Router.Parse("/users/:id"), (params) => params)
Matcher dependency and lifetime behavior follows Effect v4 Layer and Scope semantics: https://effect.website/docs/requirements-management/layers/ and https://effect.website/docs/resource-management/scope/.
import * as Router from "@typed/router"
import * as Effect from "effect/Effect"
const users = Router.match(Router.Parse("/users/:id"), (params) =>
Effect.map(params, ({ id }) => `user:${id}`)
)Guarded candidate
```ts
import * as Router from "@typed/router"
import type { Guard } from "@typed/guard"
import * as Effect from "effect/Effect"
type Params = { readonly id: string }
const accepted: Guard<Params, Params> = (params) => Effect.succeedSome(params)
const users = Router.match(Router.Parse("/users/:id"), accepted, (params) => params)
```import * as Router from "@typed/router"
const application = Router.match(Router.Slash, "Queue")
.match(Router.Parse("/issues/:issueId"), "Issue")
.redirectTo("/")Members
Matcher.AnyA Matcher with intentionally widened success, error, and service channels.
Matcher.ErrorExtracts a Matcher’s declared error type.
Matcher.MergeErrorUnions the errors of a Matcher tuple.
Matcher.MergeServicesUnions the required services of a Matcher tuple.
Matcher.MergeSuccessUnions the success outputs of a Matcher tuple.
Matcher.ServicesExtracts a Matcher’s required Effect services.
Matcher.SuccessExtracts a Matcher’s success output type.
Other public imports
These import paths expose the same declaration. Each page retains its own public name and signature.
Source
packages/router/src/Matcher.ts:399packages/router/src/Matcher.ts:764packages/router/src/Matcher.ts:417packages/router/src/Matcher.ts:668packages/router/src/Matcher.ts:653packages/router/src/Matcher.ts:683packages/router/src/Matcher.ts:736packages/router/src/Matcher.ts:460packages/router/src/Matcher.ts:466packages/router/src/Matcher.ts:472packages/router/src/Matcher.ts:492packages/router/src/Matcher.ts:495packages/router/src/Matcher.ts:508packages/router/src/Matcher.ts:521packages/router/src/Matcher.ts:545packages/router/src/Matcher.ts:552packages/router/src/Matcher.ts:755packages/router/src/Matcher.ts:583packages/router/src/Matcher.ts:599packages/router/src/Matcher.ts:638packages/router/src/Matcher.ts:620packages/router/src/Matcher.ts:714