Keyring
ReferencePackages

@keyring/next

Next.js App Router route-handler wrapper, Node runtime.

Generated from packages/next/src/index.ts and the declarations it exports, by packages/docs/tools/generate-reference.mjs. Every signature below is printed from the source; the prose is the source's own doc comment.
npm install @keyring/next

Functions

closeKeyring

Flushes the telemetry buffer and the disk snapshot, then forgets the instance, so the next getKeyring() builds a fresh one.

export async function closeKeyring(): Promise<void>;

currentKeyring

The instance this process is using, or null if nothing built one yet.

export function currentKeyring(): Keyring<ResourceMap> | null;

getKeyring

withKeyring calls this with no arguments, so a route file that configures nothing still gets a client: secretKey, projectId and baseUrl come from KEYRING_SECRET_KEY, KEYRING_PROJECT_ID and KEYRING_BASE_URL.

export function getKeyring<R extends ResourceMap = ResourceMap>(options?: KeyringOptions<R>): Keyring<R>;

withKeyring

export const GET = withKeyring(handler) in app/api/**\/route.ts, on the Node runtime.

Everything it does is translation, exactly as report section 6.5 asks of an adapter: the decision, the three onUnavailable modes, the rate-limit modes and header spellings, the per-environment resources, the AsyncLocalStorage scope, the idempotency state machine and the telemetry ring buffer all live in @keyring/sdk.

The order is the one the other two adapters use and it is not arbitrary: verification and the rate limit happen before the body is read, so an unauthenticated caller cannot get a 5 MB payload parsed on the way to a 401, and idempotency happens after, because its fingerprint covers the body.

The returned function's second parameter is required, and that is not a style choice. next build generates a type assertion per route file and checks the exported handler against RouteContext = { params: Promise<...> } via SecondArg<typeof GET>; an optional parameter makes that SegmentData<P> | undefined, undefined does not extend RouteContext, and the build fails for every TypeScript consumer with "Type ... is not a valid type for the function's second argument". There is no opt-out and next dev does not run the validator, so the route serves correctly right up to the deploy. Next.js always passes the argument; the ?? below is for a caller who is not Next.js.

export function withKeyring<P = Params>(handler: KeyringRouteHandler<P>, options: WithKeyringOptions = {}): (request: Request, context: SegmentData<P>) => Promise<Response>;

Interfaces

KeyringOptions

Re-exported from @keyring/sdk.

export interface KeyringOptions<R extends ResourceMap = ResourceMap> {
  /**
   * A `krsk_` vendor secret key. Never a tenant's `kr_` key.
   * Defaults to `KEYRING_SECRET_KEY`.
   */
  readonly secretKey?: string;
  /** Defaults to `KEYRING_PROJECT_ID`. */
  readonly projectId?: string;
  /** Defaults to `KEYRING_BASE_URL`, then to the EU control plane. */
  readonly baseUrl?: string;
  /**
   * Report section 5.3: unset means one endpoint serves both environments and
   * routes on the key, which is the shape the docs lead with. Set it only for a
   * deployment that serves one environment and should refuse the other outright.
   */
  readonly env?: Environment;
  /** Report section 6.1, fix 1: the tenants this node serves. */
  readonly tenantIds?: readonly string[];
  readonly maxCachedKeys?: number;
  /**
   * How far the SDK may raise `maxCachedKeys` by itself before it refuses to
   * serve a project that does not fit. `DEFAULT_MAX_AUTO_CACHED_KEYS` (38,000,
   * ~31 MiB of heap on a three-scope, two-rule key; ~46 MiB on a heavier one)
   * unless set, and never below `maxCachedKeys`.
   */
  readonly maxAutoCachedKeys?: number;
  /**
   * What to do when a node that has **never served** finds the project has more
   * live keys than it can cache and no `tenantIds` narrows it.
   *
   * The default throws it, which crashes the process at boot with the actionable
   * message -- that is what "refuse to start" means, and it is deliberate. Such
   * a node can never hold a complete cache, and an incomplete cache under the
   * `stale-then-open` default admits *any* well-formed key, unverified and
   * unscoped, for the life of the process. A deploy that cannot verify keys
   * should not take traffic.
   *
   * It is **not** called on a node that is already serving when its project
   * outgrows the cache. That one keeps serving and keeps polling, reports the
   * same error through `onError` on every poll, and reports
   * `stats().policyCache.scopeTooLarge` -- the readiness predicate to drain it
   * on. Killing it instead is a fleet-wide outage of your own API one vendor
   * `PATCH` away, and stopping its poller is worse still: the node then never
   * learns another revocation.
   *
   * Set this to handle the boot case yourself -- to page, to fail a readiness
   * probe, to exit with your own code. A no-op leaves the node serving on the
   * store it has, which at boot is an empty one: every well-formed key is
   * admitted unverified until the first successful poll, and there will not be
   * one. The fail-open table in the README says what that admits.
   */
  readonly onScopeTooLarge?: (error: PolicyScopeTooLargeError) => void;
  readonly policyRefreshMs?: number;
  readonly maxStalenessMs?: number;
  readonly onUnavailable?: UnavailableMode;
  /**
   * Report section 3: what a request does when the *counter store* cannot be
   * reached, which is a different question from what it does when the *policy*
   * cannot be. `open` by default; see `limits/limiter.ts` for why, and why
   * idempotency's default is the opposite.
   */
  readonly rateLimitOnUnavailable?: RateLimitUnavailableMode;
  /** Report section 3.6: the pre-08 header triple. On, because clients read it. */
  readonly legacyRateLimitHeaders?: boolean;
  readonly rateLimitTimeoutMs?: number;
  /**
   * Report section 4. `false` disables the feature; an object configures it.
   * Enabled by default -- a mutating request with no `Idempotency-Key` costs
   * nothing, and the header is how a caller opts in.
   */
  readonly idempotency?:
    | boolean
    | Omit<IdempotencyOptions, 'baseUrl' | 'secretKey' | 'fetch' | 'onError'>;
  readonly routes?: RouteRules;
  readonly scopes?: readonly string[];
  /** Report section 5.3, level 2. Resolved per request from the key's env. */
  readonly resources?: R;
  readonly cacheDir?: string;
  readonly persist?: boolean;
  readonly nodeId?: string;
  /** Report section 6.3. Set false to send nothing at all. */
  readonly telemetry?: boolean;
  readonly bufferSize?: number;
  readonly flushIntervalMs?: number;
  readonly flushEvents?: number;
  readonly maxEventsPerSecond?: number;
  readonly handleSignals?: boolean;
  readonly lazyFill?: boolean | OnDemandFillOptions;
  readonly sdkName?: string;
  readonly fetch?: FetchLike & ShipperFetch & ControlPlaneFetch;
  readonly onWarn?: (message: string, detail?: unknown) => void;
  /** Injectable so a test can exercise the environment defaults. */
  readonly processEnv?: Record<string, string | undefined>;
  readonly onError?: (error: unknown) => void;
  /** Escape hatch for tests: a store to use instead of the polled cache. */
  readonly store?: PolicyStore;
  readonly now?: () => number;
}

SegmentData

What Next.js 15 hands a route handler as its second argument. params is a promise from 15 onward; a static route gets one that resolves to {}.

export interface SegmentData<P> {
  readonly params: Promise<P>;
}

WithKeyringOptions

export interface WithKeyringOptions extends RouteRule {
  /**
   * An explicit client, for a process that runs several or for a test. Left
   * out, the wrapper uses the process-wide one from `getKeyring()`.
   */
  readonly instance?: Keyring<ResourceMap>;
  /**
   * The route *pattern* this file serves -- `'/api/orders/[id]'` -- for
   * matching `routes` rules and for the `route` column of the request log. The
   * concrete path is what a rule matches without it, which for a dynamic
   * segment is a different string on every request.
   */
  readonly route?: string;
}

Types

KeyringContext

Re-exported from @keyring/sdk.

export type KeyringContext<R extends ResourceMap = ResourceMap> =
  KeyringContextBase & ResolvedResources<R>;

KeyringRouteContext

The second argument the wrapped handler receives: Next.js's own segment data with keyring on it.

KeyringContext | null and not KeyringContext -- the same property type @keyring/express assigns and @keyring/fastify decorates, because req.keyring's shape is one row of packages/sdk/README.md's cross-language table and an adapter that spelled it differently would make the Python and Go ports two contracts. It is null on a route configured skip: true, which is a route the customer asked us not to verify; a non-nullable field would lie about it. Unlike Express's, this one has no "from here onward" caveat: the wrapper builds the object it passes, so the property is always there.

export type KeyringRouteContext<P> = SegmentData<P> & {
  readonly keyring: KeyringContext | null;
};

KeyringRouteHandler

export type KeyringRouteHandler<P> = (
  request: Request,
  context: KeyringRouteContext<P>,
) => Response | Promise<Response>;

On this page