atproto user agency toolkit for individuals and groups
1/**
2 * DID cache using in-memory Map with TTL.
3 * Replaces Cloudflare Workers Cache API from Cirrus.
4 */
5
6import { defs, type DidDocument } from "@atcute/identity";
7
8export interface CacheResult {
9 did: string;
10 doc: DidDocument;
11 updatedAt: number;
12 stale: boolean;
13 expired: boolean;
14}
15
16export interface DidCache {
17 cacheDid(
18 did: string,
19 doc: DidDocument,
20 prevResult?: CacheResult,
21 ): Promise<void>;
22 checkCache(did: string): Promise<CacheResult | null>;
23 refreshCache(
24 did: string,
25 getDoc: () => Promise<DidDocument | null>,
26 prevResult?: CacheResult,
27 ): Promise<void>;
28 clearEntry(did: string): Promise<void>;
29 clear(): Promise<void>;
30}
31
32const STALE_TTL = 60 * 60 * 1000; // 1 hour
33const MAX_TTL = 24 * 60 * 60 * 1000; // 24 hours
34
35interface CacheEntry {
36 doc: DidDocument;
37 cachedAt: number;
38}
39
40export class InMemoryDidCache implements DidCache {
41 private cache = new Map<string, CacheEntry>();
42
43 async cacheDid(did: string, doc: DidDocument): Promise<void> {
44 this.cache.set(did, { doc, cachedAt: Date.now() });
45 }
46
47 async checkCache(did: string): Promise<CacheResult | null> {
48 const entry = this.cache.get(did);
49 if (!entry) return null;
50
51 const now = Date.now();
52 const age = now - entry.cachedAt;
53
54 // Validate document
55 const parsed = defs.didDocument.try(entry.doc);
56 if (!parsed.ok || parsed.value.id !== did) {
57 this.cache.delete(did);
58 return null;
59 }
60
61 return {
62 did,
63 doc: parsed.value,
64 updatedAt: entry.cachedAt,
65 stale: age > STALE_TTL,
66 expired: age > MAX_TTL,
67 };
68 }
69
70 async refreshCache(
71 did: string,
72 getDoc: () => Promise<DidDocument | null>,
73 ): Promise<void> {
74 // Background refresh (fire and forget)
75 getDoc().then((doc) => {
76 if (doc) {
77 this.cacheDid(did, doc);
78 }
79 });
80 }
81
82 async clearEntry(did: string): Promise<void> {
83 this.cache.delete(did);
84 }
85
86 async clear(): Promise<void> {
87 this.cache.clear();
88 }
89}