import type { MiddlewareHandler } from "hono"; import { type DbFactory, type VoyantBindings, type VoyantDb, type VoyantVariables } from "../types.js"; /** * Twenty-four hours, in milliseconds. Default TTL for stored idempotency * keys. Tunable per middleware instance. */ export declare const DEFAULT_IDEMPOTENCY_TTL_MS: number; export interface IdempotencyKeyOptions { /** * Namespacing label so unrelated endpoints can safely accept overlapping * client keys. Defaults to the request method + pathname when omitted. */ scope?: string; /** * How long stored responses are replayable. Defaults to * {@link DEFAULT_IDEMPOTENCY_TTL_MS}. */ ttlMs?: number; /** * Whether the header is required. When `true`, requests without the header * are rejected with 400. Defaults to `false` so existing clients keep * working through a deprecation window. */ required?: boolean; /** * Query parameters that affect the response contract and should be included * in the idempotency fingerprint alongside the request body. Reusing the * same key with different fingerprinted query values returns 409 instead of * replaying a response captured under different request semantics. */ fingerprintSearchParams?: readonly string[]; /** * Optional callback that, given the response body that's about to be * stored, returns a `referenceId` (typically the booking id). Used for * operational queries to correlate replays with the underlying entity. */ extractReferenceId?: (body: unknown) => string | null | undefined; /** * Whether successful JSON responses are replayable. Disable for * endpoints whose response carries session-bound bearer secrets. */ replayResponses?: boolean; /** * Include the caller/session/IP in the `(scope, key)` namespace. * Enabled by default so client-chosen keys cannot replay another * caller's response on anonymous or shared public surfaces. */ scopeWithCaller?: boolean; /** Maximum request body bytes the middleware may buffer. Defaults to 10 MiB. */ maxBodyBytes?: number; } interface ContextWithIdempotency { idempotencyKey?: string; idempotencyReplayed?: boolean; } declare module "hono" { interface ContextVariableMap extends ContextWithIdempotency { } } /** * Idempotency-Key middleware. * * On request: * - reads the `Idempotency-Key` header * - if absent and `required` is false, passes through * - if absent and `required` is true, returns 400 * - looks up `(scope, key)` in `idempotency_keys`: * - hit + same body hash → returns the stored response (replay) * - hit + different body hash → returns 409 (conflict) * - miss → runs the handler, then stores the response on the way out * * The handler's response body is captured by cloning the response. The * `Idempotency-Key` and a `Idempotency-Replayed: true` header are echoed * on replay so callers can detect replays in client-side logging. * * The middleware reads the `db` instance off the request context (set by * the `db` middleware that ships with `createApp`) — caller is responsible * for ordering this middleware after `db`. */ export declare function idempotencyKey(options?: IdempotencyKeyOptions): MiddlewareHandler<{ Bindings: TBindings; Variables: TVariables; }>; /** * Sweep expired idempotency rows. Call from a daily cron. * * If `dbFactory` returns a `DisposableDb` (e.g. a per-call Neon * WebSocket Pool), the sweep awaits `dispose()` before returning so * the connection closes cleanly inside the cron handler. */ export declare function purgeExpiredIdempotencyKeys(dbFactory: DbFactory, env: TBindings): Promise<{ removed: number; }>; export {};