your personal website on atproto - mirror blento.app
at bump-text-padding 98 lines 2.9 kB view raw
1import type { ActorIdentifier, Did } from '@atcute/lexicons'; 2import { isDid } from '@atcute/lexicons/syntax'; 3import type { KVNamespace } from '@cloudflare/workers-types'; 4 5/** TTL in seconds for each cache namespace */ 6const NAMESPACE_TTL = { 7 blento: 60 * 60 * 24, // 24 hours 8 identity: 60 * 60 * 24 * 7, // 7 days 9 github: 60 * 60 * 12, // 12 hours 10 'gh-contrib': 60 * 60 * 12, // 12 hours 11 lastfm: 60 * 60, // 1 hour (default, overridable per-put) 12 npmx: 60 * 60 * 12, // 12 hours 13 meta: 0 // no auto-expiry 14} as const; 15 16export type CacheNamespace = keyof typeof NAMESPACE_TTL; 17 18export class CacheService { 19 constructor(private kv: KVNamespace) {} 20 21 // === Generic namespaced operations === 22 23 async get(namespace: CacheNamespace, key: string): Promise<string | null> { 24 return this.kv.get(`${namespace}:${key}`); 25 } 26 27 async put( 28 namespace: CacheNamespace, 29 key: string, 30 value: string, 31 ttlSeconds?: number 32 ): Promise<void> { 33 const ttl = ttlSeconds ?? NAMESPACE_TTL[namespace] ?? 0; 34 await this.kv.put(`${namespace}:${key}`, value, ttl > 0 ? { expirationTtl: ttl } : undefined); 35 } 36 37 async delete(namespace: CacheNamespace, key: string): Promise<void> { 38 await this.kv.delete(`${namespace}:${key}`); 39 } 40 41 async list(namespace: CacheNamespace): Promise<string[]> { 42 const prefix = `${namespace}:`; 43 const result = await this.kv.list({ prefix }); 44 return result.keys.map((k) => k.name.slice(prefix.length)); 45 } 46 47 // === JSON convenience === 48 49 async getJSON<T = unknown>(namespace: CacheNamespace, key: string): Promise<T | null> { 50 const raw = await this.get(namespace, key); 51 if (!raw) return null; 52 return JSON.parse(raw) as T; 53 } 54 55 async putJSON( 56 namespace: CacheNamespace, 57 key: string, 58 value: unknown, 59 ttlSeconds?: number 60 ): Promise<void> { 61 await this.put(namespace, key, JSON.stringify(value), ttlSeconds); 62 } 63 64 // === blento data (keyed by DID, with handle↔did resolution) === 65 async getBlento(identifier: ActorIdentifier): Promise<string | null> { 66 const did = await this.resolveDid(identifier); 67 if (!did) return null; 68 return this.get('blento', did); 69 } 70 71 async putBlento(did: string, handle: string, data: string): Promise<void> { 72 await Promise.all([ 73 this.put('blento', did, data), 74 this.put('identity', `h:${handle}`, did), 75 this.put('identity', `d:${did}`, handle) 76 ]); 77 } 78 79 async listBlentos(): Promise<string[]> { 80 return this.list('blento'); 81 } 82 83 // === Identity resolution === 84 async resolveDid(identifier: ActorIdentifier): Promise<string | null> { 85 if (isDid(identifier)) return identifier; 86 return this.get('identity', `h:${identifier}`); 87 } 88 89 async resolveHandle(did: Did): Promise<string | null> { 90 return this.get('identity', `d:${did}`); 91 } 92} 93 94export function createCache(platform?: App.Platform): CacheService | undefined { 95 const kv = platform?.env?.USER_DATA_CACHE; 96 if (!kv) return undefined; 97 return new CacheService(kv); 98}