# Matcher

Composes ordered route cases into an Fx that follows the current Navigation path.

## Signatures

```ts
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>>;
}
```

```ts
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

```ts
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/.

```ts
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)
```

```ts
import * as Router from "@typed/router"

const application = Router.match(Router.Slash, "Queue")
  .match(Router.Parse("/issues/:issueId"), "Issue")
  .redirectTo("/")
```
