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