export interface Env { KV: KVNamespace; ADMIN_SECRET: string; RATE_LIMITER: RateLimit; RATE_LIMITER_STRICT: RateLimit; TURSO_URL: string; TURSO_AUTH_TOKEN: string; SEARCH_BACKEND_URL?: string; /// "1" re-asks Turso when the engine answers empty (limit ≤ 25 only). /// Off by default since 2026-09-04: see tursoFallbackOnEmpty. TURSO_FALLBACK_ON_EMPTY?: string; /// "1" sends the query to Turso when the engine is unreachable or errors. /// Off by default since 2026-09-04: see tursoFallbackOnUnreachable. TURSO_FALLBACK_ON_UNREACHABLE?: string; /// DID of the service's own atproto account (@typeahead.waow.tech). /// served at /.well-known/atproto-did for handle verification; the /// _lexicon DNS record points here so the service owns its lexicons. SERVICE_DID?: string; /// Emergency kill switch: when set (any non-empty value), the search /// handler bypasses everything (ingester, Turso, cache) and proxies /// directly to Bluesky's public typeahead API. Flip via: /// bunx wrangler secret put EMERGENCY_PROXY_BSKY /// Turn off with: /// bunx wrangler secret delete EMERGENCY_PROXY_BSKY EMERGENCY_PROXY_BSKY?: string; /// Discord-compatible webhook. When set, the hourly cron posts here on a /// freshness STATE CHANGE (ok -> stale, stale -> ok). Set via: /// bunx wrangler secret put ALERT_WEBHOOK_URL /// Independent of Logfire on purpose: the 48h wedge on 2026-07-28 went /// unnoticed partly because the search service exported no telemetry at all. ALERT_WEBHOOK_URL?: string; } /** KV `stats_data`, written hourly by materializeStats */ export interface StatsKvData { metrics: { hour: number; searches: number; total_ms: number; cache_hits: number; cache_ms: number }[]; snapshots: { hour: number; total: number; with_handles: number; with_avatars: number; hidden: number }[]; deltas: { bucket: number; actors_delta: number; handles_delta: number; avatars_delta: number }[]; traffic_series?: { hour: number; domain: string; hits: number }[]; traffic_series_5m?: { bucket: number; domain: string; hits: number }[]; traffic_daily?: { day: number; domain: string; hits: number }[]; /** apps that have ever sent a search: traffic_sources rows minus probes, * tunnels, private IPs, and the unknown/homepage attribution buckets */ traffic_sources_total?: number; materialized_at: number; } /** KV `request_data`, written hourly by materializeRequests */ export interface RequestKvData { series?: { hour: number; domain: string; endpoint: string; status: number; hits: number }[]; series_5m?: { bucket: number; domain: string; endpoint: string; status: number; hits: number }[]; daily?: { day: number; domain: string; endpoint: string; status: number; hits: number }[]; materialized_at: number; } export interface ActorRow { did: string; handle: string; display_name: string; avatar_url: string; labels: string; created_at: string; associated: string; pds: string; } /** getProfiles row: ActorRow plus the Turso-only profile-card columns. */ export interface ProfileRow extends ActorRow { description: string; banner_url: string; followers_count: number; follows_count: number; posts_count: number; profile_checked_at: number; updated_at: number; } export interface IngestEvent { did: string; handle?: string; display_name?: string; avatar_cid?: string; description?: string; banner_cid?: string; /** resolved alongside handle by the ingester's slingshot pool — carrying it * here is what retired the enrichment cron's per-DID pds backfill. */ pds?: string; } export interface SlingshotResponse { did: string; handle: string; pds: string; } export const CORS_HEADERS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Client", }; /** microcosm.blue etiquette: identify the project + contact on api requests */ export const MICROCOSM_UA = "typeahead (zzstoatzz.io)"; export const SLINGSHOT_URL = "https://slingshot.microcosm.blue/xrpc/blue.microcosm.identity.resolveMiniDoc"; export const BSKY_TYPEAHEAD_URL = "https://public.api.bsky.app/xrpc/app.bsky.actor.searchActorsTypeahead"; export const BSKY_GET_PROFILES_URL = "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfiles"; /** The mark: a lowercase t on the dark panel, with the accent block cursor * the wordmark carries on every page. Served at /favicon.svg and rasterized * for /apple-touch-icon.png; crawlers and home-screen installs cannot read a * data: URI, which is why it is a route and not an inline href. */ export const FAVICON_SVG = ``; export const FAVICON = ` `;