GET /xrpc/app.bsky.actor.searchActorsTypeahead typeahead.waow.tech
15
fork

Configure Feed

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

reuse libsql client across requests, fix cleanup script table scan

- lazy singleton for turso client in worker isolate — eliminates
per-request endpoint discovery round-trip (~6s → ~900ms on cache miss)
- cleanup script: scan by rowid instead of WHERE handle='' to avoid
turso timeout on full table scan, filter client-side

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+46 -11
+11 -6
scripts/cleanup-dead-actors.py
··· 127 127 if args.start_rowid: print(f" resuming from rowid {args.start_rowid}") 128 128 129 129 while True: 130 - remaining = args.limit - checked if args.limit else PAGE_SIZE 131 - if args.limit and remaining <= 0: 130 + if args.limit and checked >= args.limit: 132 131 break 133 - page_size = min(PAGE_SIZE, remaining) if args.limit else PAGE_SIZE 134 132 135 133 rows = turso_query( 136 - "SELECT rowid, did, display_name FROM actors WHERE handle = '' AND rowid > ?1 ORDER BY rowid ASC LIMIT ?2", 137 - [{"type": "integer", "value": str(last_rowid)}, {"type": "integer", "value": str(page_size)}], 134 + "SELECT rowid, did, handle, display_name FROM actors WHERE rowid > ?1 ORDER BY rowid ASC LIMIT ?2", 135 + [{"type": "integer", "value": str(last_rowid)}, {"type": "integer", "value": str(PAGE_SIZE)}], 138 136 turso_url, turso_token, 139 137 ) 140 138 if not rows: 141 139 break 142 140 141 + last_rowid = int(rows[-1]["rowid"]) 142 + 143 + # only check actors with empty handles — skip the rest client-side 144 + empty = [r for r in rows if r.get("handle", "") == ""] 145 + if not empty: 146 + continue 147 + rows = empty 148 + 143 149 dids = [r["did"] for r in rows] 144 150 did_to_row = {r["did"]: r for r in rows} 145 - last_rowid = int(rows[-1]["rowid"]) 146 151 147 152 batches = [dids[i:i+25] for i in range(0, len(dids), 25)] 148 153 to_delete = []
+32 -2
src/db.ts
··· 49 49 }; 50 50 } 51 51 52 - export function createDb(env: Env): TursoDB { 53 - return tursoDb(createClient({ url: env.TURSO_URL, authToken: env.TURSO_AUTH_TOKEN })); 52 + let cachedKey: string | undefined; 53 + let cachedClient: Client | undefined; 54 + let cachedDb: TursoDB | undefined; 55 + let initPromise: Promise<TursoDB> | undefined; 56 + 57 + /** lazy singleton — reuses the client across requests within a worker isolate */ 58 + export function getDb(env: Env): Promise<TursoDB> { 59 + const key = `${env.TURSO_URL}::${env.TURSO_AUTH_TOKEN}`; 60 + 61 + if (cachedDb && cachedKey === key) return Promise.resolve(cachedDb); 62 + if (initPromise && cachedKey === key) return initPromise; 63 + 64 + cachedKey = key; 65 + initPromise = (async () => { 66 + const client = createClient({ 67 + url: env.TURSO_URL, 68 + authToken: env.TURSO_AUTH_TOKEN, 69 + }); 70 + const db = tursoDb(client); 71 + cachedClient = client; 72 + cachedDb = db; 73 + return db; 74 + })().catch((err) => { 75 + if (cachedKey === key) { 76 + cachedClient = undefined; 77 + cachedDb = undefined; 78 + initPromise = undefined; 79 + } 80 + throw err; 81 + }); 82 + 83 + return initPromise; 54 84 }
+3 -3
src/index.ts
··· 1 1 import type { Env } from "./types"; 2 2 import { CORS_HEADERS } from "./types"; 3 - import { createDb } from "./db"; 3 + import { getDb } from "./db"; 4 4 import { clientIP, json, html } from "./utils"; 5 5 import { recordSnapshot } from "./enrichment"; 6 6 import { enrichActors } from "./enrichment"; ··· 14 14 15 15 export default { 16 16 async scheduled(_event: ScheduledEvent, env: Env, _ctx: ExecutionContext): Promise<void> { 17 - const db = createDb(env); 17 + const db = await getDb(env); 18 18 await recordSnapshot(db); 19 19 await refreshModeration(db, env); 20 20 await enrichActors(db, env); ··· 40 40 return new Response(null, { status: 302, headers: { Location: "/" } }); 41 41 } 42 42 43 - const db = createDb(env); 43 + const db = await getDb(env); 44 44 45 45 if (pathname === "/stats" && request.method === "GET") { 46 46 return handleStats(request, db, ctx);