/packages/search-worker/CLAUDE.md
CLAUDE.md at /packages/search-worker/CLAUDE.md
Path: packages/
search-worker
Cloudflare Worker sub-package providing a server-side search API. Additional option for large doc bases — the primary search is the in-browser widget pages/, which loads the same search-index.json emitted by the @takazudo/ zfb plugin.
The host does NOT use MiniSearch. _search-widget.tsx runs its own word-match scoring loop; minisearch is a dependency of this package only and is not imported anywhere under src/ or pages/. What the two share is the index, not the engine.
Tech Stack
Cloudflare Workers — runtime
Cloudflare KV — rate limiting storage
MiniSearch — full-text search (this package's own dep; the host does not use it)
TypeScript — strict mode,
@cloudflare/workers-types
Commands
pnpm dev— local dev server (requireswrangler.tomlwith correctDOCS_SITE_URL)pnpm run deploy— deploy to Cloudflare Workers via Wranglerpnpm typecheck— TypeScript type checking
Architecture
src/
├── index.ts # Worker entry — routing, validation, CORS
├── cors.ts # CORS headers (exposes Retry-After)
├── hash-ip.ts # Rate-limit key derivation: HMAC-SHA-256 when the optional
│ # IP_HASH_SECRET is set, unsalted SHA-256 otherwise (#2038)
├── rate-limit.ts # Per-IP rate limiting via KV (60/min, 1000/day)
├── search.ts # MiniSearch index loader + search logic
└── types.ts # Env, request/response types Request Flow
CORS preflight →
cors.tsMethod + path check → 405/404
Hash client IP (
hash-ip.ts— HMAC-SHA-256 withIP_HASH_SECRET, else SHA-256)JSON parse + query validation → 400 (query required, max 500 chars)
Rate limit check → 429 with
Retry-AfterFetch
search-index.jsonfrom docs site (cached with 5-minute TTL) →search.tsMiniSearch search with prefix, fuzzy, and boost → results
Key Design Decisions
Additive, not replacement — the client-side widget handles most users. Worker is for API consumers and huge doc bases
Index from deployed site — fetches
${DOCS_, same data as the client-side widgetSITE_ URL}/ search- index. json 5-minute cache TTL — balances freshness with performance. Isolate recycle also clears cache
MiniSearch config —
prefix: true, fuzzy: 0.2, boost: { title: 3, description: 2 }. This is NOT the client's config: the client scores with its own word-match loop, so results can differ between the two paths by design
Configuration
DOCS_SITE_URL— base URL of the deployed docs site (set in this package's ownwrangler.toml[vars], separate from the main worker's). A Cloudflare dashboard environment-variable override shadows the file value and persists across deploys — if one was ever set, clear or replace it when redeployingRATE_LIMIT— KV namespace for rate limiting, separate from the main worker's.wrangler.tomlships a placeholder id; seeREADME.mdfor the creation runbook (deploying without replacing it gives error code 10042)RATE_LIMIT_PER_MINUTE/RATE_LIMIT_PER_DAY— configurable inwrangler.tomlIP_HASH_SECRET— optional Workers secret; seehash-ip.tsabove
Conventions
All responses include CORS headers (including error responses)
Error responses use
{ error: string }formatRate limit uses
cf-connecting-ipfor client IPQuery length capped at 500 characters
Default result limit: 20, max: 100