Keyring

NestJS

A module, a guard you wire yourself, one global interceptor, and a decorator for the context.

The file below is compiled with emitDecoratorMetadata and executed against the real @keyring/nest by the docs' test suite. It runs on the Express platform adapter here; the package's own suite runs the same table on Fastify's.

import { KeyringContext, KeyringGuard, KeyringModule } from '@keyring/nest';
@Controller('/v1/orders')
class OrdersController {
  @Get()
  list(@KeyringContext() keyring: KeyringContext | null) {
    return {
      tenant: keyring?.tenantId ?? null,
      env: keyring?.env ?? null,
      degraded: keyring?.degraded ?? null,
      orders: keyring?.orders ?? null,
    };
  }
}
@Module({
  imports: [KeyringModule.forRoot({ resources: { orders } })],
  controllers: [OrdersController],
  providers: [{ provide: APP_GUARD, useClass: KeyringGuard }],
})
class AppModule {}

What forRoot wires, and what it does not

KeyringModule.forRoot(options) provides the client and registers one global interceptor: the environment scope, the idempotency state machine and the usage event. That interceptor authorises nothing.

It does not register the guard. The guard is the authorisation decision, and a module import that quietly starts refusing requests is the wrong default for one. Wire it as APP_GUARD, as above, or with @UseGuards(KeyringGuard) per controller or handler. Doing both is harmless: the guard runs once per request whatever the wiring.

forRootAsync({ imports, inject, useFactory }) takes options from a ConfigService. The module is global by default.

@SkipKeyring()

On a handler or a controller: no verification, no context, no usage event, the same branch a skip: true rule takes in the other adapters. A health check is what it exists for.

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

KeyringModule.forRoot({
  routes: {
    'POST /v1/payments': {
      onUnavailable: 'closed',
      scopes: ['write:payments'],
    },
    '/v1/internal/*': { onUnavailable: 'stale-then-closed' },
  },
});

Where Nest differs

Verification runs after the body is parsed. The platform adapter parses before its router, and a guard is the earliest point that can read @SkipKeyring() metadata. Bound the body on the platform adapter instead: bodyParser options on NestExpressApplication, bodyLimit on FastifyAdapter.

The context starts at the guard. Nest middleware, a platform-level app.use, and another APP_GUARD registered ahead of KeyringGuard see no context. @KeyringContext() answers null there.

Compile with experimentalDecorators and emitDecoratorMetadata, as any Nest project does; no type-stripping mode emits the metadata Nest resolves constructor dependencies from.

On this page