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

condoms

v1.0.2

Published

TypeSafe route protection

Downloads

5

Readme

Condoms

TypeSafe route protection for your web applications.

Installation

npm install condoms
# or
yarn add condoms
# or
pnpm add condoms

Usage

Condoms provides a simple, type-safe way to protect routes in your web application.

import { createCondom } from "condoms";

// Create a context provider function
const getContext = async () => {
  return {
    session: await getCurrentSession(), // Your session management
    routes: {
      login: "/login",
      dashboard: "/dashboard",
    },
    methods: {
      // Pass in one or more method(s)
      redirect: (route) => {
        // Your redirect implementation
        window.location.href = route;
      },
    },
  };
};

// Create your protection function with a default condom
const protect = createCondom(getContext, {
  check: (ctx) => !ctx.session.user, // Return true if redirect is needed
  redirectTo: (ctx) => ctx.routes.login,
  method: (ctx) => ctx.methods.redirect(ctx.route), // Define how the check-redirect is handled
});

// Use it in your route handlers
async function dashboardRouteHandler() {
  // Default callback is used when arguments are not provided
  // This will redirect to login if user is not authenticated
  const redirect = await protect();
  if (redirect) return; // User was redirected

  // Continue with protected route logic
  renderDashboard();
}

// You can also provide custom checks
async function adminRouteHandler() {
  const redirect = await protect(({ defaultCheck }) => [
    defaultCheck, // First check authentication
    {
      // Then check admin permissions
      check: (ctx) => !ctx.session.user?.isAdmin,
      redirectTo: (ctx) => ctx.routes.dashboard,
      method: (ctx) => ctx.methods.redirect(ctx.route),
    },
  ]);

  if (redirect) return; // User was redirected

  // Continue with admin route logic
  renderAdminPanel();
}

Next.js (App Router) example:

// ~/auth/utils.ts
import { createCondom } from "condoms";
import { verifySession } from "~/auth/server";
import { forbidden, redirect } from "next/navigation";
import { REDIRECT_URLS } from "~/constants/urls";

export const protect = await createCondom(
  async () => ({
    session: await verifySession(),
    methods: {
      redirect,
      forbidden,
    },
    routes: REDIRECT_URLS,
  }),
  {
    // Default condom
    check: ({ session }) => !session?.user,
    redirectTo: ({ routes }) => routes.unauthed,
    method: ({ methods, route }) => methods.redirect(route),
  }
);

Use the your protection in a layout:

// layout.tsx
import { protect } from "~/auth/utils/routes";

export default async function SetupLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  await protect(({ defaultCheck }) => [
    defaultCheck,
    {
      check: ({ session }) => !!session?.user.hasDefaults,
      redirectTo: ({ routes }) => routes.authed,
      method: ({ methods, route }) => methods.redirect(route),
    },
  ]);

  return <>{children}</>;
}

API

createCondom(getContext, defaultCheck)

Creates a route protection function.

  • getContext: A function that returns a promise resolving to a context object with session, methods, and routes. The context object can have any shape you want, as long as it satisfies the minimum requirements.
  • defaultCheck: The default check to run if no custom checks are provided. Type of defaultCheck is derived from provided context.

protect([checksGenerator])

The function returned by createCondom.

  • checksGenerator (optional): A function that receives the default check and returns an array of checks to run in sequence.

Type Reference

Context Object

The context object can have any shape you want, as long as it includes the minimum required properties:

type ProtectRouteContext<
  TSession = any, // Your session type
  TMethods extends Record<string, Function> = any, // Your methods type
  TRoutes extends Record<string, string> = any // Your routes type
> = {
  session: TSession; // Your session object (can be any shape)
  methods: TMethods; // Methods for handling redirects/responses
  routes: TRoutes; // Routes for redirection targets
};

Check Object

Both the default check and custom checks should conform to this structure:

type DefaultCheck<TContext extends ProtectRouteContext = any> = {
  // Return true if redirect is needed
  check: (ctx: TContext) => Promise<boolean> | boolean;

  // Return the route to redirect to
  redirectTo: (ctx: TContext) => string;

  // Define how the check-redirect is handled
  method: (ctx: TContext & { route: string }) => any;
};

ChecksGenerator Function

The function passed to protect() should have this signature:

type ChecksGenerator<T extends DefaultCheck> = (params: {
  defaultCheck: T;
}) => Array<T>;

License

ISC