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

@entitle/express

v0.2.0

Published

Express HTTP adapter for the Entitle policy engine

Readme

@entitle/express

Express adapter for Entitle, a strongly-typed feature entitlements and policy engine for TypeScript.

It mounts an express.Router that serves the engine's operations over HTTP.

These routes can change who is entitled to what. /assign, /revoke and /override grant any policy, or any limit, to any subject. authorize is a required option on every Entitle adapter and there is no way to mount one without it: 0.0.1 served these routes with no authentication, which was an anonymous privilege-escalation API rather than a missing feature, and 0.1.0 made the hook required in order to close it. expose defaults to 'read-only', so the four mutating routes are not registered at all until you pass expose: 'all'.

Read Security in @entitle/core's README before you deploy this. It is the same text as node_modules/@entitle/core/README.md, which this package installs alongside itself.

Install

pnpm add @entitle/express @entitle/core express

Usage

createExpressRouter(engine, options) returns a Router. Mount it wherever you like; it expects express.json() upstream so req.body is already parsed.

import express from 'express'
import { createExpressRouter } from '@entitle/express'

const app = express()
app.use(express.json())

app.use(
  '/entitle',
  createExpressRouter(engine, {
    // Omit `expose` for a read-only mount: check / explain / resolve only.
    // `'all'` additionally mounts assign / revoke / override / override/remove.
    expose: 'all',
    authorize: async ({ access, request }) => {
      const session = await getSession(request.header('cookie'))
      if (!session) return { ok: false, code: 'UNAUTHENTICATED' }
      if (access === 'write' && !session.user.isAdmin) {
        return { ok: false, code: 'FORBIDDEN' }
      }
      return { ok: true, actorId: session.user.id }
    },
  }),
)

app.listen(3000)

Options

| Option | Required | Default | Meaning | | ----------- | -------- | ------------- | ----------------------------------------------------- | | authorize | yes | -- | Decides whether one request may perform one operation | | expose | no | 'read-only' | Which routes exist: the three reads, or all seven |

authorize

Called once per request, before the operation runs, with:

| Field | Type | Meaning | | ----------- | ----------------------------------- | ---------------------------------------------- | | operation | AdminOperationName | Canonical operation name, e.g. 'assign_policy' | | access | 'read' \| 'write' | Whether the operation observes or changes state | | input | unknown | The parsed request body | | request | Express Request | For cookies, headers, and whatever middleware attached |

Return { ok: true, actorId } to allow -- actorId is what the write is attributed to. Return { ok: false, code: 'UNAUTHENTICATED' } for a 401 or { ok: false, code: 'FORBIDDEN' } for a 403; a bare { ok: false } is a 403. A hook that throws or rejects denies the request: it fails closed, and the handler is never reached.

expose

Defaults to 'read-only', which mounts check, explain and resolve. The four mutating routes are not registered at all until expose: 'all', so until then they 404 rather than 403 -- they genuinely do not exist on that mount. This is the option most likely to surprise you on an upgrade, because a missing authorize is a compile error while a missing expose is a silent 404.

Routes

All four Entitle adapters serve the same POST routes at the same paths:

| Route | Access | Description | | ----------------------- | ------- | ------------------------------------ | | POST /check | read | Check a single feature for a subject | | POST /explain | read | Full evaluation trace for a feature | | POST /resolve | read | Resolve all features for a subject | | POST /assign | write | Assign a policy to a subject | | POST /revoke | write | Revoke a policy from a subject | | POST /override | write | Set a per-subject feature override | | POST /override/remove | write | Remove a per-subject override |

Every body names the subject by subjectId and subjectKind, removals included.

Responses

Every success is the same body on all four adapters, with data always present and null for the four operations that return nothing:

{
  "ok": true,
  "data": null
}

Before 0.1.1 this adapter omitted the data key, so a client written against @entitle/hono or @entitle/next and moved here broke on a data === null check. The contract, the idempotent removals, and the three failures the envelope does not cover are What a success response contains in @entitle/core's README.

Errors

Every failure is the same body on all four adapters:

{
  "ok": false,
  "error": {
    "code": "STORE_ERROR",
    "message": "Failed to assign policy",
    "correlationId": "0b9c1f8e-6f2a-4b51-9a2e-2d3f8f0f7c11"
  }
}

code decides the status; message is a constant chosen by the code path; correlationId is present exactly when something was withheld, and matches the logger.error line that carries the cause in full. A driver's error fields, a store's SQL or file path, a feature's schema and an internal exception never reach a response. The full contract is What an error response may contain in @entitle/core's README.

Serving the API open, on purpose

For a sidecar bound to loopback, a mesh that authorizes at the edge, or a test harness:

import { createExpressRouter, UNSAFE_allowUnauthenticated } from '@entitle/express'

const router = createExpressRouter(engine, {
  expose: 'all',
  authorize: UNSAFE_allowUnauthenticated,
})

It warns once per process, and the name is deliberately greppable. The deployment assumptions that have to hold first are under UNSAFE_allowUnauthenticated in @entitle/core's README.

Testing

@entitle/testing exports createAuthorizeStub, which is the hook plus a record of what the adapter asked it -- so a test can assert that a refused request never reached the operation.

Links

  • @entitle/core -- the engine, and the guidance the sections above point at: what the routes are and which of them write, expose, serving a subject its own grants, explain as information disclosure, actor attribution, UNSAFE_allowUnauthenticated and the preconditions it needs, and what an error response may contain. Also at node_modules/@entitle/core/README.md.
  • Reporting a vulnerability -- privately, to https://github.com/fponticelli, and not in a public issue. 0.0.1 is deprecated and unsupported: it served these routes with no authentication.

License

MIT