Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

lessen negative cache down to 10 seconds

+42 -19
+12 -7
apps/hosting-service/src/lib/cache-manager.ts
··· 20 20 value: unknown 21 21 timestamp: number 22 22 size: number 23 + ttl?: number 23 24 } 24 25 25 26 interface NamespaceStats { ··· 33 34 interface GetOrFetchOpts<T> { 34 35 /** Skip caching when predicate returns false (e.g. don't cache null). */ 35 36 cacheIf?: (value: T) => boolean 37 + /** Per-entry TTL override (ms). Can be a number or a function of the value. */ 38 + ttl?: number | ((value: T) => number | undefined) 36 39 } 37 40 38 41 export class CacheManager<NS extends string = string> { ··· 58 61 const value = await fetcher() 59 62 60 63 if (!opts?.cacheIf || opts.cacheIf(value)) { 61 - this.set(ns, key, value) 64 + const ttl = typeof opts?.ttl === 'function' ? opts.ttl(value) : opts?.ttl 65 + this.set(ns, key, value, ttl) 62 66 } 63 67 64 68 return value ··· 76 80 return undefined 77 81 } 78 82 79 - // TTL check 80 - if (cfg.ttl && Date.now() - entry.timestamp > cfg.ttl) { 83 + // TTL check (per-entry override takes precedence over namespace default) 84 + const effectiveTtl = entry.ttl ?? cfg.ttl 85 + if (effectiveTtl && Date.now() - entry.timestamp > effectiveTtl) { 81 86 map.delete(key) 82 87 st.entries = map.size 83 88 st.sizeBytes -= entry.size ··· 93 98 return entry.value as T 94 99 } 95 100 96 - set(ns: NS, key: string, value: unknown): void { 101 + set(ns: NS, key: string, value: unknown, ttl?: number): void { 97 102 const map = this.namespaces.get(ns) 98 103 const cfg = this.configs.get(ns) 99 104 const st = this.stats.get(ns) ··· 122 127 st.evictions++ 123 128 } 124 129 125 - map.set(key, { value, timestamp: Date.now(), size }) 130 + map.set(key, { value, timestamp: Date.now(), size, ttl }) 126 131 st.sizeBytes += size 127 132 st.entries = map.size 128 133 } ··· 182 187 this.cleanupTimer = setInterval(() => { 183 188 const now = Date.now() 184 189 for (const [ns, cfg] of this.configs) { 185 - if (!cfg.ttl) continue 186 190 const map = this.namespaces.get(ns)! 187 191 const st = this.stats.get(ns)! 188 192 for (const [key, entry] of map) { 189 - if (now - entry.timestamp > cfg.ttl) { 193 + const effectiveTtl = entry.ttl ?? cfg.ttl 194 + if (effectiveTtl && now - entry.timestamp > effectiveTtl) { 190 195 map.delete(key) 191 196 st.sizeBytes -= entry.size 192 197 }
+30 -12
apps/hosting-service/src/lib/db.ts
··· 11 11 // Cache-only mode: skip all DB writes and only use tiered storage 12 12 export const CACHE_ONLY = process.env.CACHE_ONLY === 'true' 13 13 14 + // Short TTL for negative / unmapped lookups so newly-mapped domains appear quickly. 15 + const NEGATIVE_TTL_MS = 10_000 16 + 14 17 export async function getWispDomain(domain: string): Promise<DomainLookup | null> { 15 18 const key = domain.toLowerCase() 16 - return cache.getOrFetch('domains', key, async () => { 17 - const result = await sql<DomainLookup[]>` 19 + return cache.getOrFetch( 20 + 'domains', 21 + key, 22 + async () => { 23 + const result = await sql<DomainLookup[]>` 18 24 SELECT did, rkey FROM domains WHERE domain = ${key} LIMIT 1 19 25 ` 20 - return result[0] || null 21 - }) 26 + return result[0] || null 27 + }, 28 + { ttl: (v) => (!v || !v.rkey ? NEGATIVE_TTL_MS : undefined) }, 29 + ) 22 30 } 23 31 24 32 export async function getCustomDomain(domain: string): Promise<CustomDomainLookup | null> { 25 33 const key = domain.toLowerCase() 26 - return cache.getOrFetch('customDomains', key, async () => { 27 - const result = await sql<CustomDomainLookup[]>` 34 + return cache.getOrFetch( 35 + 'customDomains', 36 + key, 37 + async () => { 38 + const result = await sql<CustomDomainLookup[]>` 28 39 SELECT cd.id, cd.domain, cd.did, cd.rkey, cd.verified 29 40 FROM custom_domains cd 30 41 LEFT JOIN site_cache sc ··· 38 49 cd.created_at DESC 39 50 LIMIT 1 40 51 ` 41 - return result[0] || null 42 - }) 52 + return result[0] || null 53 + }, 54 + { ttl: (v) => (!v || !v.rkey ? NEGATIVE_TTL_MS : undefined) }, 55 + ) 43 56 } 44 57 45 58 export async function getCustomDomainByHash(hash: string): Promise<CustomDomainLookup | null> { 46 - return cache.getOrFetch('customDomains', `hash:${hash}`, async () => { 47 - const result = await sql<CustomDomainLookup[]>` 59 + return cache.getOrFetch( 60 + 'customDomains', 61 + `hash:${hash}`, 62 + async () => { 63 + const result = await sql<CustomDomainLookup[]>` 48 64 SELECT id, domain, did, rkey, verified FROM custom_domains 49 65 WHERE id = ${hash} AND verified = true LIMIT 1 50 66 ` 51 - return result[0] || null 52 - }) 67 + return result[0] || null 68 + }, 69 + { ttl: (v) => (!v || !v.rkey ? NEGATIVE_TTL_MS : undefined) }, 70 + ) 53 71 } 54 72 55 73 export async function upsertSite(did: string, rkey: string, displayName?: string) {