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.