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

@maroonedsoftware/policies

v0.4.0

Published

A collection of policies for ServerKit.

Readme

@maroonedsoftware/policies

A small, DI-friendly policy framework for ServerKit. Encode allow/deny rules as named, injectable Policy classes; resolve them at call sites through a typed PolicyService.

Installation

pnpm add @maroonedsoftware/policies

Features

  • Policy base class — implement a single evaluate method and return allow(), deny(reason, details?, internalDetails?), or denyStepUp(reason, requirement)
  • Typed PolicyResult — discriminated union with isPolicyResultAllowed / isPolicyResultDenied guards; denial results carry details (rendered to clients) and internalDetails (log-only)
  • Named registry — register each policy under a stable name (e.g. 'email.allowed') so callers depend on the name and PolicyService, not on concrete classes
  • Type-safe call sites — declare a Policies map ({ <name>: <ContextShape> }) and BasePolicyService.check/assert enforce the right context per name at compile time
  • Per-evaluation envelope — subclass BasePolicyService to attach request-scoped state (current time, session, request id, …) without each policy reaching for it
  • Fluent step-up denialsdenyStepUp(reason, { within, acceptableMethods, … }) bundles a StepUpRequirement into the response under kind: 'step_up_required'

Concepts

  • A policy is a single rule that takes a context and returns PolicyResult.
  • The registry (PolicyRegistryMap) maps a stable string name to the DI identifier of the policy class.
  • The policy service (PolicyService) is the abstract handle call sites depend on; BasePolicyService is the default implementation that pulls each policy from the DI container and supplies it with a fresh envelope.
  • An envelope (PolicyEnvelope) is the per-evaluation context shared across all policies — at minimum now: DateTime. Subclass to add session, request id, etc.

Usage

Define a policy

import { Injectable } from 'injectkit';
import { Policy, PolicyResult, PolicyEnvelope } from '@maroonedsoftware/policies';

interface EmailAllowedContext {
  value: string;
}

@Injectable()
class EmailAllowedPolicy extends Policy<EmailAllowedContext> {
  async evaluate(context: EmailAllowedContext, _envelope: PolicyEnvelope): Promise<PolicyResult> {
    if (!context.value.includes('@')) return this.deny('invalid_format');
    if (context.value.endsWith('@disposable.com')) return this.deny('deny_list');
    return this.allow();
  }
}

Wire up a PolicyService in your app

import { BasePolicyService, PolicyEnvelope, PolicyRegistryMap, PolicyService } from '@maroonedsoftware/policies';
import { Injectable } from 'injectkit';
import { DateTime } from 'luxon';

type AppPolicies = {
  'email.allowed': { value: string };
  'phone.allowed': { value: string };
};

@Injectable()
class AppPolicyService extends BasePolicyService<AppPolicies> {
  protected async buildEnvelope(): Promise<PolicyEnvelope> {
    return { now: DateTime.utc() };
  }
}

// At bootstrap:
registry.register(EmailAllowedPolicy).useClass(EmailAllowedPolicy).asSingleton();
registry.register(PolicyRegistryMap).useFactory(() => {
  const map = new PolicyRegistryMap();
  map.set('email.allowed', EmailAllowedPolicy);
  return map;
});
registry.register(PolicyService).useClass(AppPolicyService).asSingleton();

Evaluate at call sites

const policyService = container.get(PolicyService);

// `check` returns the discriminated result — branch on `allowed`.
const result = await policyService.check('email.allowed', { value: '[email protected]' });
if (!result.allowed) {
  throw httpError(400).withDetails({ value: result.reason });
}

// `assert` throws HTTP 403 on deny. The denial result is split across the thrown error:
// - `result.details` (client-facing) → `HttpError.details`, rendered to the response body.
// - `result.internalDetails` (operator/log-only) → `HttpError.internalDetails`, merged with
//   framework context (`policyName`, `reason`, `kind: 'policy_violation'`), never on the wire.
// - `result.headers` → `HttpError.withHeaders`, attached to the HTTP response.
await policyService.assert('email.allowed', { value: '[email protected]' });

Step-up denials

When a policy needs proof of recent re-authentication, return a step-up denial. The bundled StepUpRequirement is serialised under details.stepUp so clients can drive the user through a re-auth challenge before retrying:

return this.denyStepUp('recent_auth_required', {
  within: Duration.fromObject({ minutes: 5 }),
  acceptableMethods: ['fido', 'authenticator'],
});

Response headers on deny

deny(...) and denyStepUp(...) return a PolicyDenialBuilder with a fluent .withHeaders(headers) setter. BasePolicyService.assert forwards them to HttpError.withHeaders so the response carries them. Use for WWW-Authenticate on auth/MFA policies, Retry-After on rate-limit policies, etc.:

return this.deny('mfa_required').withHeaders({ 'WWW-Authenticate': 'Bearer error="mfa_required"' });

return this.denyStepUp('aal2_required', { within: Duration.fromObject({ minutes: 15 }) }).withHeaders({
  'WWW-Authenticate': 'Bearer error="aal2_required"',
});

API

Policy<Context, Envelope>

Abstract base class. Subclass and implement evaluate(context, envelope): Promise<PolicyResult>.

| Helper | Returns | Description | | ------------------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | allow() | { allowed: true } | Allow the request | | deny(reason, details?, internalDetails?) | PolicyDenialBuilder (implements PolicyResultDenied) | Deny with a machine-readable reason. details is rendered to the HTTP response by assert; internalDetails lands in the thrown error's internalDetails (logs only, never on the wire). Chain .withHeaders({...}) to attach HTTP headers (e.g. WWW-Authenticate). | | denyStepUp(reason, requirement) | PolicyDenialBuilder | Deny and attach a StepUpRequirement clients can use to drive a re-auth challenge. Chain .withHeaders({...}) to attach response headers. |

PolicyService

Abstract DI handle. Implementations supply a per-evaluation envelope.

| Method | Returns | Description | | ---------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------- | | check(policyName, context) | Promise<PolicyResult> | Resolve the registered policy and return its result. Throws when policyName is not registered. | | assert(policyName, context) | Promise<void> | Same as check, but throws HTTP 403 on deny. result.details is surfaced under HttpError.details; result.internalDetails is merged with framework context (policyName, reason, kind: 'policy_violation') under HttpError.internalDetails; result.headers is forwarded to HttpError.withHeaders. |

BasePolicyService<TPolicies, TEnvelope>

Default PolicyService. Subclass and implement buildEnvelope(): Promise<TEnvelope>. The TPolicies type parameter ties policy names to their context shape, giving call sites compile-time type safety.

PolicyRegistryMap

Map<string, Identifier<Policy>>. Populate at bootstrap to bind each policy name to its DI identifier.

Types

| Type | Shape | | --------------------- | ------------------------------------------------------------------------------------------------------ | | PolicyResultAllowed | { allowed: true } | | PolicyResultDenied | { allowed: false; reason: string; details?: Record<string, unknown>; internalDetails?: Record<string, unknown>; headers?: Record<string, string> } | | PolicyResult | PolicyResultAllowed \| PolicyResultDenied | | PolicyEnvelope | { now: DateTime } (extend in subclasses) | | StepUpRequirement | { within: Duration; acceptableMethods?; acceptableKinds?; excludeMethods? } |

| Guard | Description | | --------------------------- | -------------------------------------------- | | isPolicyResultAllowed(r) | Narrows r to the allowed branch. | | isPolicyResultDenied(r) | Narrows r to the denied branch. |