@keyring/next
Next.js App Router route-handler wrapper, Node runtime.
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.Not publicly available yet
@keyring/next is not on the npm registry, and the repository it builds from is private today, so it installs only from a tarball someone with repository access builds. Start in 5 minutes has the build, the overrides block and how to ask for access.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>;