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

@ontrails/permits

v0.2.4

Published

Scope-based authorization for Trails.

Readme

@ontrails/permits

Scope-based authorization for Trails.

The permits package owns adapter-agnostic auth resources, adapters, and helpers. Core executeTrail enforces trail permit declarations once a surface has resolved a permit into ctx.permit.

The core pattern

1. Declare permit requirements on trails

export const create = trail('gist.create', {
  permit: { scopes: ['gist:write'] },
  implementation: async (input, ctx) => {
    // executeTrail enforces scopes before execution enters the implementation
    return Result.ok(newGist);
  },
});

export const search = trail('gist.search', {
  permit: 'public',
  implementation: async (input, ctx) => {
    // No authentication required
    return Result.ok(results);
  },
});

2. Resolve a permit at the surface

export const graph = topo('my-app', gistModule);
// Surface auth verifies credentials and passes { permit } into executeTrail.

The execution pipeline reads each trail's permit field:

  • 'public' or undefined — execution passes through
  • { scopes: [...] } — execution checks that ctx.permit contains all required scopes

Permit enforcement is intrinsic to executeTrail; there is no opt-in layer to wire. The previously deprecated authLayer compatibility wrapper has been removed (TRL-475).

3. Bind an adapter at bootstrap

import { createJwtAdapter } from '@ontrails/permits/jwt';

const adapter = createJwtAdapter({
  secret: process.env.JWT_SECRET,
  issuer: 'https://auth.example.com',
  audience: 'api.example.com',
});

Auth adapters

An auth adapter authenticates requests and produces permits.

Built-in: JWT adapter

Verifies HS256-signed JWTs and extracts claims into permits:

import { createJwtAdapter } from '@ontrails/permits/jwt';

const adapter = createJwtAdapter({
  secret: 'your-hmac-secret',
  issuer: 'https://auth.example.com',
  audience: 'api.example.com',
  scopesClaim: 'scope',
  rolesClaim: 'roles',
});

@ontrails/permits/jwt is the canonical built-in JWT adapter subpath. The root package intentionally keeps convenience re-exports for interactive discovery, but generated code, docs, and adapter-specific examples should import JWT names from the subpath.

Custom adapters

Implement the AuthAdapter interface:

import type { AuthAdapter, PermitExtractionInput, Permit } from '@ontrails/permits';

const myAdapter: AuthAdapter = {
  authenticate: async (input: PermitExtractionInput) => {
    if (!input.bearerToken) return Result.ok(null);
    const permit: Permit = {
      id: 'user-42',
      scopes: ['user:read', 'user:write'],
      roles: ['admin'],
    };
    return Result.ok(permit);
  },
};

Permits and scopes

A Permit is the resolved identity and scopes from successful authentication:

interface Permit {
  readonly id: string;
  readonly scopes: readonly string[];
  readonly roles?: readonly string[];
  readonly tenantId?: string;
  readonly metadata?: Readonly<Record<string, unknown>>;
}

Access the permit in your implementation:

import { getPermit } from '@ontrails/permits';

const myTrail = trail('do.something', {
  implementation: async (_input, ctx) => {
    const permit = getPermit(ctx);
    if (!permit) return Result.err(new Error('Not authenticated'));
    return Result.ok({ userId: permit.id });
  },
});

Scopes follow the entity:action convention: user:read, gist:write, etc.

The auth.verify trail

An infrastructure trail that verifies bearer tokens and returns permits:

import { authVerify } from '@ontrails/permits';

// Returns { valid: true, permit: { id, scopes, roles } }
// or { valid: false, error: 'Token has expired', errorCode: 'expired_token' }

Testing with mock permits

Use createTestPermit() and createPermitForTrail() in tests:

import {
  createTestPermit,
  createPermitForTrail,
} from '@ontrails/permits/testing';

const permit = createTestPermit({
  id: 'user-123',
  scopes: ['gist:read', 'gist:write'],
  roles: ['editor'],
});

// Create a permit matching a trail's requirements
const trailPermit = createPermitForTrail(myTrail);
// { id: 'test-...', scopes: ['gist:write'] }

Permit governance

Use validatePermits() to check trails against governance rules:

import { validatePermits } from '@ontrails/permits';

const diagnostics = validatePermits(app.list());

Built-in rules:

  • destroyWithoutPermit — error if a destroy trail has no permit
  • writeWithoutPermit — warning if a write trail has no permit
  • scopeNamingConsistency — warning if a scope doesn't follow entity:action
  • orphanScopeDetection — warning if a scope appears in only one trail

Installation

These installation examples target Trails 0.2.1 on the normal npm release line.

bun add --exact @ontrails/[email protected] @ontrails/[email protected] zod