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

@judo/guards

v0.1.1

Published

Navigation guards for JUDO UI Runtime

Readme

@judo/guards

Navigation guards for JUDO UI Runtime

Purpose

Provides two runtime guard mechanisms:

  1. Unsaved Changes Guard — prevents navigation away from dirty forms, with browser beforeunload support
  2. Session Timeout Guard — monitors user inactivity, shows a warning dialog before session expiry, and forces logout on timeout

Architecture Layer

Layer 4 (Features) — consumed by app-shell and core for navigation protection.

Dependencies

  • @judo/model-api — model type definitions (peer)
  • @judo/feedback — dialog service for confirmation dialogs (peer)
  • @mui/material ^7 — MUI components for session timeout dialog (peer)
  • react ^19 — React (peer)
  • react-router ^7 — routing (peer)

File Structure

src/
├── index.ts                              # Barrel re-exports
├── unsaved-changes/
│   ├── unsaved-changes-guard.ts          # Interface + class
│   ├── use-unsaved-changes-guard.ts      # React hook
│   ├── unsaved-changes-guard.test.ts
│   └── use-unsaved-changes-guard.test.tsx
└── session-timeout/
    ├── session-timeout-guard.ts          # Interface + config + class
    ├── session-timeout-dialog.tsx         # React component
    ├── session-timeout-guard.test.tsx
    └── session-timeout-dialog.test.tsx

Exports Summary

Unsaved Changes Guard

| Export | Kind | Description | | ---------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | UnsavedChangesGuard | interface | Interface for managing unsaved-changes warnings across multiple forms/pages. | | UnsavedChangesGuardImpl | class | Concrete implementation. Stores dirty-checkers in a Map. Uses DialogService for confirmation. | | useUnsavedChangesGuard(guard, isDirty) | hook | Registers the component with the guard when dirty. Attaches browser beforeunload handler. Auto-cleans up. |

UnsavedChangesGuard methods:

| Method | Returns | Description | | ----------------------- | ------------------ | ---------------------------------------------------------------------------- | | hasUnsavedChanges() | boolean | Iterates all registered dirty-checkers; returns true if any is dirty. | | register(id, isDirty) | () => void | Registers a form/page by ID with a dirty callback. Returns cleanup function. | | unregister(id) | void | Removes a previously registered dirty-checker. | | confirmNavigation() | Promise<boolean> | If dirty, shows a confirm dialog; resolves true if user chooses to leave. |

Session Timeout Guard

| Export | Kind | Description | | --------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | | SessionTimeoutGuard | interface | Interface for monitoring user inactivity and triggering timeout flows. | | SessionTimeoutConfig | interface | Configuration: timeoutMs? (default: 30 min), warningMs? (default: 5 min), onTimeout?, onWarning?. All fields optional. | | SessionTimeoutGuardImpl | class | Implementation with timer cascade and DOM activity listeners. | | SessionTimeoutDialog | component | MUI Dialog showing either "Session Expiring" (with continue/logout) or "Session Expired" (with login). | | SessionTimeoutDialogProps | interface | Props: guard, onLogout. |

SessionTimeoutGuard methods:

| Method | Returns | Description | | --------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------- | | start() | void | Begins monitoring; attaches DOM listeners (mousedown, keydown, scroll, touchstart) and schedules timers. | | stop() | void | Clears timers, detaches listeners, resets state, notifies subscribers. | | resetActivity() | void | Resets inactivity timer (no-op if already timed out). | | isTimedOut() | boolean | Returns current timed-out state. | | subscribe(listener) | () => void | Subscribes to (isWarning, isTimedOut) state changes; returns unsubscribe function. |

Key Patterns

  • Interface + Impl class: Both guards define an interface and a separate *Impl class, enabling DI and testability
  • Manual pub/sub: SessionTimeoutGuardImpl uses a Set<listener> with subscribe returning unsubscribe — compatible with useSyncExternalStore
  • useSyncExternalStore: SessionTimeoutDialog bridges the imperative guard to React's concurrent-safe external store API
  • useId for registration: useUnsavedChangesGuard uses React 18+ useId for a stable, unique registration key
  • Browser beforeunload: The unsaved-changes hook traps browser-level navigation (tab close, refresh)
  • Timer cascade: Session timeout uses a two-phase timer: scheduleWarning() fires first (at timeoutMs - warningMs), then chains to scheduleTimeout() which fires after the remaining warningMs window. On timeout, activity listeners are detached.
  • No React context/provider: Guards are plain classes passed as props or hook arguments — no provider in this package
  • Dependency on @judo/feedback: UnsavedChangesGuardImpl takes a DialogService for confirmation dialogs