npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@effect-stack/router

v0.3.0

Published

Effect-native, renderer-independent routing for the web

Readme

@effect-stack/router

Renderer-independent routing with Schema-validated URLs, scoped Effect loaders, and Atom observations. Targets Effect v4 RC; install a version compatible with the package's peer range.

Install and define routes

pnpm add @effect-stack/router effect@rc
import { BrowserHistory, Route, Router } from "@effect-stack/router"
import { Schema } from "effect"

const home = Route.make({ id: "home", path: "/", params: {}, search: {} })
const project = Route.make({
  id: "project",
  path: "/projects/:id",
  params: { id: Schema.FiniteFromString },
  search: { tab: Schema.optionalKey(Schema.Literals(["overview", "activity"])) },
  hash: Schema.Literals(["", "details"])
})

export const router = Router.make({ routes: [home, project], layer: BrowserHistory.layer })

Use MemoryHistory.layer("/initial") in tests and non-browser hosts. For views, choose a React, Solid, or Vue adapter, or integrate the core directly.

Navigation API

  • router.href(route, input) returns a typed Result containing the encoded URL or RouteEncodeError.
  • router.execute(command) runs push, replace, refresh, back, forward, or go with an AtomRegistry.AtomRegistry service. Push/replace/refresh await their transition; traversal acknowledges the host request.
  • router.retry rebuilds failed initialization or refreshes a healthy runtime.
  • state, navigation, branch, completed, and routeAtoms(route) expose read-only Atom observations.

See navigation contracts for cancellation, snapshots, resource lifetime, and recovery.

Matching and trees

Routes support exact static segments and required named parameters. Trailing and repeated slashes are significant.

  • Static segments outrank dynamic ones; equal-ranking flat patterns keep declaration order.
  • Malformed percent-encoding cannot match a static segment. Matched dynamic parameters with invalid encoding or Schema values produce RouteDecodeError; an unmatched URL produces RouteNotFound.
  • Flat routers reject duplicate IDs and exact path templates. Not-found branches retain a covering match when available.
  • Repeated search fields preserve ordered values. Empty arrays are unrepresentable; singleton arrays are rejected when the Schema also accepts a scalar, since the URL would be ambiguous.
  • Invalid route definitions throw RouteDefinitionError; matching and URL encoding return typed Result failures.

Use RouteTree.root, RouteTree.make, and addChildren for nested, index, and pathless routes, then Router.fromTree. Trees inherit URL schemas, reject ambiguous templates, preserve ancestor chains, and rank indexes ahead of ancestors sharing the same URL. Builders support .pipe(...) and RouteTree.isNode.

RouteTree.Destination<typeof tree> describes typed destinations with required params, search, and hash inputs. An index route's requirements cannot be bypassed by targeting its ancestor at the same URL.

For tooling, RouteTree.compile(tree) exposes routes, plan(location), and target(destination). target selects the endpoint and fills omitted empty inputs; pass its { route, input } to Route.href for encoding. It throws for unknown destinations. RouteTree.flatten provides the validated preorder route list. Definitions are immutable: use addChildren to create a new tree rather than mutate cached trees or route arrays.

Code, data, and services

lazy: () => Effect.tryPromise(() => import("./page.js")) loads code into module. loader receives decoded { params, search, hash, location } and returns loaderData:

import { MemoryHistory, Route, Router } from "@effect-stack/router"
import { Context, Effect, Layer, Schema } from "effect"

class Projects extends Context.Service<
  Projects,
  {
    readonly get: (id: number) => Effect.Effect<{ readonly id: number; readonly title: string }>
  }
>()("Projects") {}

const project = Route.make({
  id: "project",
  path: "/projects/:id",
  params: { id: Schema.FiniteFromString },
  search: {},
  loader: ({ params }) => Projects.use((projects) => projects.get(params.id))
})

const router = Router.make({
  routes: [project],
  layer: Layer.merge(
    MemoryHistory.layer("/projects/42"),
    Layer.succeed(Projects, { get: (id) => Effect.succeed({ id, title: `Project ${id}` }) })
  )
})

The router Layer must supply all code/data loader services. Loader scopes close before results are published; long-lived resources and remote caching belong to application services or Effect Atom.

Architecture