{"version":3,"sources":["../../src/stores/durable-objects.ts"],"sourcesContent":["import type { DPoPNonceStore } from \"./types.js\";\n\nconst DEFAULT_KEY_PREFIX = \"dpop:jti:\";\nconst DEFAULT_TTL_MS = 5 * 60_000; // 5 minutes — matches default jtiTtl\n\n/** Minimal DurableObjectStorage subset (avoids @cloudflare/workers-types dependency). */\nexport interface DurableObjectStorageLike {\n\tget<T>(key: string): Promise<T | undefined>;\n\tput<T>(key: string, value: T, options?: { allowUnconfirmed?: boolean }): Promise<void>;\n\tdelete(key: string): Promise<boolean>;\n\tlist<T>(options: { prefix: string }): Promise<Map<string, T>>;\n}\n\ninterface JtiEntry {\n\texpiresAt: number;\n}\n\nexport interface DurableObjectStoreOptions {\n\t/** Durable Object storage instance (from `this.ctx.storage` inside a DO class). */\n\tstorage: DurableObjectStorageLike;\n\t/** Key prefix to namespace replay-cache entries (default: \"dpop:jti:\"). */\n\tkeyPrefix?: string;\n\t/**\n\t * Fallback TTL in milliseconds applied when `expiresAt` is in the past or missing\n\t * (default: 300000 = 5 min). Normal callers always pass a future `expiresAt`.\n\t */\n\tdefaultTtl?: number;\n}\n\n/**\n * Durable Object storage-backed replay cache. DO storage has no native TTL, so each entry\n * stores its own `expiresAt` and is lazy-deleted on read. The DO single-writer guarantee\n * makes the read-then-write pattern atomic without explicit locking — concurrent requests\n * to the same Object are serialized by the runtime.\n *\n * `purge()` lists entries under `keyPrefix` and deletes those past their `expiresAt`,\n * returning the count removed. Run from a scheduled handler or alarm to bound storage.\n */\nexport function durableObjectStore(options: DurableObjectStoreOptions): DPoPNonceStore {\n\tconst { storage, keyPrefix = DEFAULT_KEY_PREFIX, defaultTtl = DEFAULT_TTL_MS } = options;\n\n\treturn {\n\t\tasync check(jti, expiresAt) {\n\t\t\tconst now = Date.now();\n\t\t\tconst key = `${keyPrefix}${jti}`;\n\t\t\tconst existing = await storage.get<JtiEntry>(key);\n\t\t\tif (existing !== undefined && existing.expiresAt > now) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst effectiveExpiresAt = expiresAt > now ? expiresAt : now + defaultTtl;\n\t\t\tawait storage.put<JtiEntry>(key, { expiresAt: effectiveExpiresAt });\n\t\t\treturn true;\n\t\t},\n\n\t\tasync purge() {\n\t\t\tconst now = Date.now();\n\t\t\tconst entries = await storage.list<JtiEntry>({ prefix: keyPrefix });\n\t\t\tlet count = 0;\n\t\t\tfor (const [key, entry] of entries) {\n\t\t\t\tif (entry.expiresAt <= now) {\n\t\t\t\t\tawait storage.delete(key);\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn count;\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,IAAM,qBAAqB;AAC3B,IAAM,iBAAiB,IAAI;AAmCpB,SAAS,mBAAmB,SAAoD;AACtF,QAAM,EAAE,SAAS,YAAY,oBAAoB,aAAa,eAAe,IAAI;AAEjF,SAAO;AAAA,IACN,MAAM,MAAM,KAAK,WAAW;AAC3B,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,MAAM,GAAG,SAAS,GAAG,GAAG;AAC9B,YAAM,WAAW,MAAM,QAAQ,IAAc,GAAG;AAChD,UAAI,aAAa,UAAa,SAAS,YAAY,KAAK;AACvD,eAAO;AAAA,MACR;AACA,YAAM,qBAAqB,YAAY,MAAM,YAAY,MAAM;AAC/D,YAAM,QAAQ,IAAc,KAAK,EAAE,WAAW,mBAAmB,CAAC;AAClE,aAAO;AAAA,IACR;AAAA,IAEA,MAAM,QAAQ;AACb,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,UAAU,MAAM,QAAQ,KAAe,EAAE,QAAQ,UAAU,CAAC;AAClE,UAAI,QAAQ;AACZ,iBAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AACnC,YAAI,MAAM,aAAa,KAAK;AAC3B,gBAAM,QAAQ,OAAO,GAAG;AACxB;AAAA,QACD;AAAA,MACD;AACA,aAAO;AAAA,IACR;AAAA,EACD;AACD;","names":[]}