import { Env, Context } from 'hono'; type IdempotencyErrorCode = "MISSING_KEY" | "KEY_TOO_LONG" | "BODY_TOO_LARGE" | "FINGERPRINT_MISMATCH" | "CONFLICT"; interface ProblemDetail { type: string; title: string; status: number; detail: string; code: IdempotencyErrorCode; } /** Clamp HTTP status to 200-599 integer range; returns 500 for out-of-range or non-integer values. */ declare function clampHttpStatus(status: number): number; declare function problemResponse(problem: ProblemDetail, extraHeaders?: Record): Response; declare const IdempotencyErrors: { readonly missingKey: () => ProblemDetail; readonly keyTooLong: (maxLength: number) => ProblemDetail; readonly bodyTooLarge: (maxSize: number) => ProblemDetail; readonly fingerprintMismatch: () => ProblemDetail; readonly conflict: () => ProblemDetail; }; interface IdempotencyStore { /** * Get a record by key. Returns undefined if the key does not exist or has expired. * Implementations should filter out expired records transparently. */ get(key: string): Promise; /** * Attempt to lock a key by storing a record in "processing" state. * Must be atomic: if two concurrent calls race on the same key, * exactly one must return true and the other false. * Returns true if the lock was acquired, false if the key already exists. * Expired keys should be treated as non-existent (lock succeeds). */ lock(key: string, record: IdempotencyRecord): Promise; /** * Mark a record as "completed" and attach the response. * Called after the handler returns a 2xx response. * If the key does not exist, this should be a no-op. */ complete(key: string, response: StoredResponse): Promise; /** * Delete a record. Called when the handler throws or returns non-2xx. * Allows the client to retry with the same key. */ delete(key: string): Promise; /** * Physically remove expired records and return the count of deleted entries. * For stores with automatic expiration (e.g., KV with expirationTtl), * this may be a no-op returning 0. */ purge(): Promise; } declare const RECORD_STATUS_PROCESSING: "processing"; declare const RECORD_STATUS_COMPLETED: "completed"; interface IdempotencyEnv extends Env { Variables: { idempotencyKey: string | undefined; }; } interface StoredResponse { status: number; headers: Record; body: string; } interface IdempotencyRecord { key: string; fingerprint: string; status: "processing" | "completed"; response?: StoredResponse; createdAt: number; } interface IdempotencyOptions { store: IdempotencyStore; headerName?: string; fingerprint?: (c: Context) => string | Promise; required?: boolean; methods?: string[]; maxKeyLength?: number; /** * Maximum request body size in bytes. Pre-checked via Content-Length header, * then enforced against actual body byte length. * Only applies when an Idempotency-Key header is present. * Requests without the key bypass this check regardless of this setting. */ maxBodySize?: number; /** Should be a lightweight, side-effect-free predicate. Avoid reading the request body. */ skipRequest?: (c: Context) => boolean | Promise; /** Return a Response with an error status (4xx/5xx). Returning 2xx bypasses idempotency guarantees. */ onError?: (error: ProblemDetail, c: Context) => Response | Promise; cacheKeyPrefix?: string | ((c: Context) => string | Promise); /** * Called when a cached response is about to be replayed. * Errors are swallowed — hooks must not affect request processing. * `key` is the raw header value; sanitize before logging to prevent log injection. */ onCacheHit?: (key: string, c: Context) => void | Promise; /** * Called when a new request acquires the lock (before the handler runs). * Fires on each lock acquisition, including retries after prior failures. * Errors are swallowed — hooks must not affect request processing. */ onCacheMiss?: (key: string, c: Context) => void | Promise; /** * Opt out of the multi-tenant safety warning. * * When `cacheKeyPrefix` is not set and `methods` includes any state-mutating * method (POST/PATCH/PUT/DELETE), the middleware emits a one-time * `console.warn` at factory construction time. Set this to `true` to * acknowledge that the deployment is single-tenant and silence the warning. * * @default false */ dangerouslyAllowGlobalKeys?: boolean; } export { type IdempotencyOptions as I, type ProblemDetail as P, RECORD_STATUS_COMPLETED as R, type StoredResponse as S, type IdempotencyEnv as a, type IdempotencyErrorCode as b, IdempotencyErrors as c, type IdempotencyRecord as d, type IdempotencyStore as e, RECORD_STATUS_PROCESSING as f, clampHttpStatus as g, problemResponse as p };