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

@tooee/router

v0.7.3

Published

Stack-based router for Tooee terminal apps

Readme

@tooee/router

Stack-based routing, asynchronous navigation preparation, and screen-focus helpers for Tooee terminal apps.

Creating and starting a router

Routes carry the parameter type used by application navigation. Parameter codecs validate every value that crosses the serialized boundary, and canonicalize can return a fresh canonical form before guards run.

const detailRoute = createRoute({
  id: "detail",
  component: DetailScreen,
  params: detailParamsCodec,
  canonicalize: ({ id }) => ({ id: id.trim() }),
});

const router = createRouter({
  routes: [homeRoute, detailRoute],
  initial: { routeId: "home" },
  beforeNavigate: async (navigation) => {
    await prepareApplicationFor(navigation.target, navigation.signal);
  },
});

const startup = await router.start();
if (startup.status !== "committed") {
  // Do not mount RouterProvider.
  throw new Error(`Router startup ${startup.status}`);
}

createRouter() is initially unstarted and has an empty stack. RouterProvider requires a successfully started router; it no longer accepts an initial route or performs mount-time navigation. Concurrent startup calls share one attempt, successful startup is stable, and failed or cancelled startup can be retried.

Navigation

Application code navigates with route objects, preserving the route's parameter type:

const result = await router.push(detailRoute, { id: "42" });

if (result.status === "committed") {
  // Router assignment and synchronous subscriber notification are complete.
}

push, replace, reset, pop, and navigate return Promise<NavigationResult>. Once work is submitted, these promises always resolve and never reject. Results are committed, cancelled, noop, or failed. A synchronous programmer error—such as using a foreign route object or navigating before startup—throws before a promise is returned.

Decoded commands and action results use the string-ID boundary:

await router.navigate({
  type: "replace",
  routeId: decoded.route,
  params: decoded.params,
});

Unknown serialized route IDs and invalid parameters resolve as failed and are reported to onNavigationError.

Guards and cancellation

Guards run in registration order after the complete target is resolved, decoded, and canonicalized. This includes the entry revealed by pop. A guard may cancel, rewrite the target, or return resource handles:

const removeGuard = router.addNavigationGuard(async (navigation, context) => {
  const prepared = await context.prepare(navigation.target, navigation.signal);
  return {
    target: prepared.canonicalTarget,
    beforeCommit: prepared.activate,
    abort: prepared.dispose,
  };
});

Rewritten targets are decoded and canonicalized before the next guard. beforeCommit callbacks are synchronous and run in guard order. If work does not commit, abort callbacks run once in reverse order. Each activation callback must be internally atomic; the router cannot roll back arbitrary external effects.

Navigation is serialized switch-latest: a new call aborts active preparation, only the newest queued call is retained, and stale work cannot commit. Targets resolve against the committed stack when their turn begins.

Commit and observability

The router does not change its stack or state cache until all preparation and activation succeeds. Cache invalidation happens immediately before the single stack assignment:

  • push preserves existing cache entries;
  • pop clears the removed top entry;
  • replace clears the replaced top entry, including same-route replacement;
  • reset clears all entries;
  • failed, cancelled, and no-op navigation changes no cache state.

pendingNavigation identifies the active guard pipeline. subscribeNavigation emits started after pending is set and settled after it is cleared. Stack and navigation listeners are isolated per listener; errors go to onSubscriberError without changing a committed result.

A committed navigation means router assignment and synchronous listener iteration finished. Route loaders still begin during render, and React may not yet have committed. Loaders, Outlet, titles, focus, and screen-state behavior otherwise remain render-time concerns.

Screen focus

useScreenFocus() combines two signals: the enclosing screen scope (for example, whether a containing panel is active) and whether the current route is the live leaf at its router depth. useScreenEffect() runs its effect only while that combined focus is true.

Outside both a router focus provider and an explicit screen scope, useScreenFocus() defaults to { isFocused: true }. Inside a router, route-leaf focus behavior is unchanged; an inactive enclosing panel still forces focus to false.