/** * Session stores (the **store mode** backend) - a pluggable `SessionStore` plus an in-memory * implementation (dev / single-instance, prod-guarded). The legacy Cloudflare Workers KV adapter is * retained for migration, but is not safe for session revocation. The session *manager* owns the * clock and authoritatively enforces expiry, so the store stays clock-free and trivially testable. */ /** A persisted session - its data plus an absolute expiry (ms epoch). */ export interface SessionRecord { readonly data: Record; /** Absolute expiry (ms epoch). The manager treats `expiresAt <= now` as no session. */ readonly expiresAt: number; } /** * Pluggable session backend (store mode). Reads must observe completed writes and deletes, including * revocation markers, across all instances. Eventual consistency cannot provide logout guarantees. * **Production needs a shared/durable store** so sessions hold across instances - {@link MemorySessionStore} * prod-guards against the per-instance footgun. Logout and rotation also write a revocation marker, an * empty record under `.revoked`, and rotation one under `.rotated`, so a store must accept those * keys like any other and keep each until its `expiresAt`. A dropped marker lets a request that loaded * the session before the logout write it back, unless the store implements `replace`; it also lets a * rotated generation outlive a logout made with an earlier generation's cookie. */ export interface SessionStore { /** Read consistency. Omission asserts strong consistency for existing custom adapters. */ readonly consistency?: "strong" | "eventual"; /** The record for `id`, or `undefined` on a miss. */ get(id: string): Promise; /** Store (or overwrite) the record for `id`. */ set(id: string, record: SessionRecord): Promise; /** Drop `id` (logout / regeneration). A no-op if absent. */ delete(id: string): Promise; /** * Overwrite `id` only while it still holds a record, resolving whether it wrote. The check and the * write must be one atomic step that also sets the new expiry, such as Redis * `SET key value XX PXAT ` (a bare `XX` drops the key's TTL) or a SQL * `UPDATE ... WHERE id = ?` that reports the rows it changed. Optional: when present, a loaded session renews through it instead * of `set`, so a logout that lands between the renewal's checks and its write wins even after the * logout's revocation marker is gone. */ replace?(id: string, record: SessionRecord): Promise; /** * Move the record at `previousId` to `nextId` only while it exists, is unrevoked and was not rotated * already, resolving whether it moved. One atomic step writes `marker` under `.rotated`, * deletes `previousId` and stores `record` at `nextId`; it refuses when `.revoked` or the * revocation marker of the original ID (the part before `~`) exists. Optional: without it, rotation * makes those writes one at a time and checks the revocation afterwards. */ rotate?(previousId: string, nextId: string, record: SessionRecord, marker: SessionRecord): Promise; } /** The suffix of a revocation marker's key: `.revoked`. */ export declare const REVOKED_SUFFIX = ".revoked"; export declare const ROTATED_SUFFIX = ".rotated"; export declare const sessionFamily: (id: string) => string; export declare const isSessionMarker: (id: string) => boolean; /** A generic store retains logout markers separately: rotating must never overwrite a racing logout. */ export declare function rotateExisting(store: SessionStore, previousId: string, nextId: string, record: SessionRecord, marker: SessionRecord): Promise; export interface MemorySessionStoreOptions { /** Allow the in-memory store in production. Off by default - per-instance sessions don't hold across * instances and are lost on restart. */ readonly allowInProduction?: boolean; /** Hard cap on stored sessions; the oldest-inserted is evicted past it (default 10_000). Revocation * markers are capped by it separately, so new sessions never evict one. */ readonly max?: number; } /** * In-process session store. Refuses to run in production unless explicitly allowed (mirrors the ISR * `MemoryCacheStore` + the rate-limit `MemoryStore` - a per-instance store is unsafe across instances). * Bounded: oldest-inserted entries evict past `max`, sessions and revocation markers each on their own. * Because a marker can be evicted, it implements `replace`, so a request that loaded the session before * a logout cannot restore it once the marker is gone. */ export declare class MemorySessionStore implements SessionStore { readonly consistency = "strong"; private readonly sessions; private readonly families; private readonly markers; private readonly max; constructor(options?: MemorySessionStoreOptions); private mapFor; get(id: string): Promise; set(id: string, record: SessionRecord): Promise; delete(id: string): Promise; replace(id: string, record: SessionRecord): Promise; rotate(previousId: string, nextId: string, record: SessionRecord, marker: SessionRecord): Promise; private write; private removeSession; } /** * Minimal structural shape of a Cloudflare Workers **KV namespace** binding - just the three methods * {@link KVSessionStore} uses. Structural (no `@cloudflare/workers-types` dependency) so any KV-like * binding satisfies it and tests can pass an in-memory double. */ export interface KVNamespaceLike { get(key: string): Promise; /** `expiration` is an absolute unix-seconds timestamp (Cloudflare KV auto-evicts past it). */ put(key: string, value: string, options?: { readonly expiration?: number; }): Promise; delete(key: string): Promise; } /** * Legacy Cloudflare Workers KV persistence, retained for reading and migrating existing records. * `createSessions` rejects this adapter: cached records and missing revocation markers can resurrect * a logged-out session. Use a strongly consistent {@link SessionStore} for authenticated sessions. * Records serialize to JSON; the entry's KV `expiration` is set from the record's `expiresAt` so KV * auto-evicts the session (a GC backstop - the manager still authoritatively checks expiry on read). * Every read is validated, so a corrupt/skewed entry reads as no session. * @deprecated Workers KV is eventually consistent and cannot enforce session revocation. */ export declare class KVSessionStore implements SessionStore { readonly consistency = "eventual"; private readonly kv; private readonly now; constructor(kv: KVNamespaceLike, options?: { readonly now?: () => number; }); get(id: string): Promise; set(id: string, record: SessionRecord): Promise; delete(id: string): Promise; } //# sourceMappingURL=store.d.ts.map