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

@keyring-dev/nest

v0.1.1

Published

NestJS module, guard and decorators for Keyring. Works on the Express and the Fastify platform adapters.

Readme

@keyring-dev/nest

NestJS module, guard and decorators for Keyring. Works on the Express and the Fastify platform adapters. MIT.

# .env
KEYRING_SECRET_KEY=krsk_live_...   # your control-plane key
KEYRING_PROJECT_ID=...             # the project whose policy this process caches
KEYRING_BASE_URL=https://...       # your control plane; no default
import { Controller, Get, Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { KeyringContext, KeyringGuard, KeyringModule } from '@keyring-dev/nest';

@Controller('/v1/orders')
export class OrdersController {
  @Get()
  list(@KeyringContext() keyring: KeyringContext | null) {
    return {
      tenant: keyring?.tenantId,
      env: keyring?.env,
      orders: keyring?.orders,
    };
  }
}

@Module({
  imports: [KeyringModule.forRoot({ resources: { orders } })],
  controllers: [OrdersController],
  providers: [{ provide: APP_GUARD, useClass: KeyringGuard }],
})
export class AppModule {}
curl -H "Authorization: Bearer kr_test_..." http://localhost:3000/v1/orders

That is a controller and a module; bootstrap it as you would any other — NestFactory.create(AppModule) and app.listen, with import 'reflect-metadata' first.

This block is compiled the way a customer compiles it — tsc against the published dist/, with no project references and no path mapping — and that check pins the context's exact type. A runnable version of it is built and executed against a stub control plane in the source repository's own test suite.

@KeyringContext() gives you KeyringContext | null, the same shape req.keyring has in @keyring-dev/express and @keyring-dev/fastify — one row of @keyring-dev/sdk's cross-language contract table, so the ?. is not defensive style. It is null on a route carrying @SkipKeyring() and on any route KeyringGuard did not run on; a request the guard refused never reaches your handler at all. The same value is on request.keyring for anything that reads the raw request.

Everything the guard does is in @keyring-dev/sdk — read its README for the three onUnavailable modes, the three ergonomic levels, rate limiting, idempotency and the telemetry buffer. This package is translation, and both platform adapters go through the same one.

What forRoot wires, and what it deliberately does not

KeyringModule.forRoot(options) provides the client, exports it as KEYRING_INSTANCE and KeyringGuard, and registers one global interceptor: the environment scope (currentEnv() inside your handler and anything it starts), the idempotency state machine, and the usage event. That interceptor authorizes nothing, and on a request no guard verified it returns immediately.

It does not register the guard for you. The guard is the authorization decision, and a module import that quietly starts refusing requests is the wrong default for one. Wire it the way a Nest application wires every other guard:

providers: [{ provide: APP_GUARD, useClass: KeyringGuard }]; // globally
@UseGuards(KeyringGuard) // or per controller, or per handler

Doing both is harmless: the guard runs once per request whatever the wiring. Nest dedupes neither a global guard nor a controller one, and two runs would charge the rate limit twice and write two usage events — on exactly the routes a customer was careful enough to guard twice.

forRootAsync({ imports, inject, useFactory }) is there for options that come from a ConfigService. The module is global: true by default so a feature module can @UseGuards(KeyringGuard) without importing it; pass global: false if you would rather import it explicitly. app.close() closes the client the module built — a client you passed in as instance is yours to close.

@SkipKeyring()

On a handler or a controller: no verification, no context, no usage event — the same branch routes: { 'GET /healthz': { skip: true } } takes in the other two adapters, because it goes through the same Keyring.handle call rather than around it. A health check is the case it exists for; it does not make the guard optional anywhere else.

Per-route onUnavailable, scopes and maxStalenessMs are not decorators here: they are the routes table on the client, keyed by the controller's own route pattern.

KeyringModule.forRoot({
  routes: {
    'POST /v1/payments': { onUnavailable: 'closed' }, // money: refuse rather than guess
    '/v1/internal/*': { onUnavailable: 'stale-then-closed' },
  },
});

One table rather than two spellings, and keyring.stats().unmatchedRoutes names a rule that never matched — which is what catches a typo that would otherwise leave a money route on the fail-open default.

Where it differs from the other two adapters

Verification runs after the body is parsed. @keyring-dev/express and @keyring-dev/fastify both verify before any parser, so an unauthenticated caller cannot get a 5 MB payload parsed on the way to a 401. On NestJS they cannot: the platform adapter parses before its router, and a guard is the earliest point that can read the @SkipKeyring() metadata a route carries. Bound the body on the platform adapter instead — bodyParser options on NestExpressApplication, bodyLimit on FastifyAdapter.

request.keyring is set by the guard, so it starts there. Anything that runs before it sees no property at all: Nest middleware, a platform-level app.use, and — the likeliest case — another APP_GUARD registered ahead of KeyringGuard, such as your own session guard. Read it through @KeyringContext(), which answers null rather than undefined in that case.

Everything else is the same behaviour, and the package's own test suite runs the same table twice, once per platform: the denial body, the RateLimit header spellings, the three onUnavailable modes, rateLimitOnUnavailable, the idempotency state machine including a replayed 202 and a replayed 422, and one usage event per request.