{"locator":"npm:@northbound-run/search-cloudflare","path":"@northbound-run/search-cloudflare","npm":"@northbound-run/search-cloudflare","github":"Northbound-Run/open-mercato-cloudflare-ai-search","repoPath":null,"kind":"module","type":"3rd-party","enterprise":false,"title":"Cloudflare AI Search","summary":"Cloudflare AI Search as a fulltext driver for search: hybrid BM25 and vector retrieval with no search server to run.","whenToUse":"Use when you would rather not operate Meilisearch and accept about 250 ms search latency from a managed service.","whenNotToUse":"Indexed text leaves your infrastructure: decide which fields may be sent before indexing production data.","category":"search-analytics","tags":["search","cloudflare","fulltext"],"providers":[{"name":"cloudflare-ai-search","label":"Cloudflare AI Search"}],"moduleIds":["search_cloudflare"],"requires":[{"raw":"search","resolvesTo":null},{"raw":"@open-mercato/search","resolvesTo":"npm:@open-mercato/search"}],"replaces":[],"slotConflicts":[],"renamedFrom":[],"supersededBy":null,"alternatives":[],"status":"active","listed":true,"featured":false,"license":{"spdx":"MIT","kind":"osi","mismatch":false},"version":{"latest":"0.1.1","preview":null,"publishedAt":"2026-08-08","integrity":"sha512-AyNqom+FSOVbc04lPrdWIEiJ8/QDm4l/61Gbh3GnOTNPSHwyEw/pqAAB6aoNyVudE/uG5JaFgfA/sF2p5zPWcw==","installableFrom":"2026-08-13","gitRef":"1d17054f40e339cdd089bce97f4c803dd07471d1","recommended":{"0.9":"0.1.1","0.8":null,"next":null}},"identity":{"builtFrom":"verified","claimed":false,"source":"npm"},"compat":{"builtFor":{"range":">=0.6.0 <0.7.0","kind":"bounded","lines":[]},"lines":{"0.9":{"status":"verified","step":null,"omVersion":"0.9.0","moduleVersion":"0.1.1","at":"2026-10-29T08:29:32Z","receipt":"r_20261029_71db7d0f3eca","since":"2026-10-29","precheck":null,"patches":[]}},"verifiedOn":["0.9"]},"anatomy":{"version":"0.1.1","coverage":"partial","counts":{"entities":0,"tables":0,"migrations":0,"routes":0,"publicRoutes":0,"aclFeatures":0,"eventsEmitted":0,"eventsConsumed":0,"backendPages":0,"frontendPages":0,"widgets":0,"workers":0,"cli":2,"aiTools":0,"aiAgents":0,"extends":0},"lists":{"aclFeatures":[],"eventsEmitted":[],"eventsConsumed":[],"env":["CF_AI_SEARCH_ACCOUNT_ID","CF_AI_SEARCH_API_TOKEN","CF_AI_SEARCH_INSTANCE","CF_AI_SEARCH_NAMESPACE","SEARCH_DISABLE_SINGLETON_CACHE","SEARCH_EXCLUDE_ENCRYPTED_FIELDS","CF_AI_SEARCH_MATCH_THRESHOLD","CF_AI_SEARCH_RETRIEVAL_TYPE"],"hosts":["api.cloudflare.com"],"tables":[],"publicRoutes":[],"credentials":[]},"callout":"FEW CONTRACTS","diff":{"from":"0.1.0","added":{"tables":0,"routes":0,"publicRoutes":0,"aclFeatures":0,"env":[],"hosts":[]},"removed":{"tables":0,"routes":0,"aclFeatures":0}}},"signals":{"receipt":{"0.9":"verified","0.8":"untested","next":"untested"},"fit":{"0.9":"ok","0.8":"drift","next":"drift"},"anatomy":"partial","supplyChain":{"status":"ok","reasons":[]},"hygiene":{"status":"ok","failing":[]},"maintenance":{"status":"current","lastRelease":"2026-08-08","catchUpDays":null},"declaredVsObserved":{"status":"mismatch","notes":["Built for no tested line; verified on 0.9."]}},"tier":"verified","quality":null,"agentPolicy":{"0.9":{"decision":"ask","reasons":[{"code":"THIRD_PARTY","text":"Third-party code runs with access to all tenants' data."}],"consentText":"Install @northbound-run/search-cloudflare@0.1.1"},"0.8":{"decision":"ask","reasons":[{"code":"THIRD_PARTY","text":"Third-party code runs with access to all tenants' data."},{"code":"NOT_VERIFIED","text":"Not yet tested in a new app on 0.8."}],"consentText":"Install @northbound-run/search-cloudflare@0.1.1"},"next":{"decision":"ask","reasons":[{"code":"THIRD_PARTY","text":"Third-party code runs with access to all tenants' data."},{"code":"NOT_VERIFIED","text":"Not yet tested in a new app on next."}],"consentText":"Install @northbound-run/search-cloudflare@0.1.1"}},"install":{"cli":"yarn mercato module add @northbound-run/search-cloudflare@0.1.1 --allow-third-party","cmm":"npx create-mercato-module install @northbound-run/search-cloudflare@0.1.1","needsAllowThirdParty":true,"moduleFlag":null},"stats":{"downloads30d":23,"stars":0},"sigil":{"glyph":"0000200","phase":62,"publicRoutes":false},"links":{"page":"https://modules.openmercato.com/m/@northbound-run/search-cloudflare","md":"https://modules.openmercato.com/m/@northbound-run/search-cloudflare.md","json":"https://modules.openmercato.com/m/@northbound-run/search-cloudflare.json","facts":"https://modules.openmercato.com/m/@northbound-run/search-cloudflare/v/0.1.1/facts.json","npm":"https://www.npmjs.com/package/@northbound-run/search-cloudflare","repo":"https://github.com/Northbound-Run/open-mercato-cloudflare-ai-search","docs":null},"addedAt":"2026-10-23","updatedAt":"2026-10-29T09:22:28Z","_meta":{"com.openmercato.modules/installable":{"0.9":"0.1.1","0.8":"0.1.1","next":"0.1.1"}},"facts":{"factsVersion":1,"extractor":{"engine":"@open-mercato/cli@0.9.0 extractModuleFacts","factsContractVersion":2,"catalog":"om-modules rentgen 1","durationMs":34},"package":{"locator":"npm:@northbound-run/search-cloudflare","name":"@northbound-run/search-cloudflare","version":"0.1.1","integrity":"sha512-AyNqom+FSOVbc04lPrdWIEiJ8/QDm4l/61Gbh3GnOTNPSHwyEw/pqAAB6aoNyVudE/uG5JaFgfA/sF2p5zPWcw==","source":"npm-tarball","gitRef":"1d17054f40e339cdd089bce97f4c803dd07471d1"},"gate":{"installable":true,"needsModuleFlag":false,"reason":null,"modules":[{"id":"search_cloudflare","src":true,"dist":true}]},"kind":"module","license":{"value":"MIT","kind":"osi","mismatch":false,"sources":{"packageJson":"MIT","licenseFile":"MIT","moduleInfo":"MIT","integrationTs":null}},"omPeers":{"@open-mercato/search":">=0.6.6 <0.7.0","@open-mercato/shared":">=0.6.0 <0.7.0"},"dependencies":{},"agentsMd":false,"readmeExcerpt":"# @northbound-run/search-cloudflare\n\nCloudflare AI Search as a pluggable **fulltext driver** for [Open Mercato](https://docs.openmercato.com) — hybrid BM25 + vector retrieval behind the stock `FullTextSearchDriver` contract, with no search service to operate.\n\nA peer of the built-in Meilisearch driver in `@open-mercato/search`, implementing the same interface. Drop it in and `SearchService` fuses it with the `vector` and `tokens` strategies exactly as before.\n\n---\n\n## Read this first\n\n### It replaces the search *service*, not the search *architecture*\n\nOpen Mercato's search is three strategies fused with Reciprocal Rank Fusion: `fulltext` (Meilisearch), `vector` (pgvector/Chroma/Qdrant + an embedding provider), and `tokens` (a Postgres hash index that always works). This package supplies a second `fulltext` driver. Everything else is untouched, and `tokens` remains the fallback when Cloudflare is unreachable.\n\nThe practical trade: you stop operating a Meilisearch container, and you accept ~250ms search latency instead of single-digit milliseconds.\n\n### Backend list views are NOT affected\n\nDespite what you might assume, list-view `q` filtering does not go through `SearchService` — it joins `search_tokens` directly inside `QueryEngine`. Installing this changes **Cmd+K global search, `/api/search`, and the AI assistant's MCP search tool**. Nothing else. That is what makes it low-risk and trivially reversible.\n\n### Your data goes to a managed service\n\nMeilisearch was a container you controlled. Cloudflare AI Search is managed and must hold readable text in order to embed it. This driver honours `fieldPolicy.excluded` / `fieldPolicy.hashOnly` and, with `SEARCH_EXCLUDE_ENCRYPTED_FIELDS=true`, the tenant encryption map — so encrypted fields stay searchable only via the `tokens` strategy, which hashes rather than storing plaintext.\n\n**Decide this before indexing production data.** It is a deployment decision the package cannot make for you.\n\n### Cloudflare AI Search is in open beta, and we found a data leak in it\n\nSee [Cross-tenant safety](#cross-tenant-safety). It is handled, but you should understand it before trusting the product with multi-tenant data.\n\n---\n\n## Install\n\n```bash\nyarn add @northbound-run/search-cloudflare\n```\n\nPeer dependencies: `@open-mercato/search` and `@open-mercato/shared` (0.6.x).\n\n### 1. Provision the instance\n\nCustom metadata fields must exist **before** any upload — Cloudflare silently drops undeclared fields, which would produce hits that cannot be mapped back to an Open Mercato entity.\n\n```bash\nnpx wrangler ai-search create my-instance \\\n  --type builtin \\\n  --hybrid-search \\\n  --custom-metadata entity:text \\\n  --custom-metadata org:text\n```\n\n`--hybrid-search` sets `index_method` to `{vector: true, keyword: true}`. Vector-only is the default and misses exact identifiers like `PO-10432`.\n\n### 2. API token\n\nNeeds **Account → AI Search:Edit** and **AI Search:Run**. A `wrangler login` OAuth token already carries both.\n\n### 3. Environment\n\n```bash\nCF_AI_SEARCH_ACCOUNT_ID=<account id>      # npx wrangler whoami\nCF_AI_SEARCH_API_TOKEN=<token>\nCF_AI_SEARCH_INSTANCE=my-instance\n# Optional\nCF_AI_SEARCH_NAMESPACE=                   # default namespace when unset\nCF_AI_SEARCH_RETRIEVAL_TYPE=hybrid        # vector | keyword | hybrid\nCF_AI_SEARCH_MATCH_THRESHOLD=0.3          # Cloudflare default is 0.4\n```\n\n### 4. Enable the module\n\n```ts\n// src/modules.ts\n{ id: 'search_cloudflare', from: '@northbound-run/search-cloudflare' },\n```\n\nThen `yarn generate`. That is the whole integration — no app `src/di.ts` wiring.\nThe module ships no entities, migrations, routes or UI: a `di.ts` that registers\nthe driver, and the `doctor` CLI. It declares `requires: ['search']`.\n\n**Registration has to live in a module `di.ts`, not the app's.** Open Mercato\nreaches app-level DI through `await import('@/di')` — a bundler-only path alias\nthat throws `ERR_MODULE_NOT_FOUND` under raw Node into a bare `catch {}`. So app\nDI silently never runs i","modules":[{"id":"search_cloudflare","title":"Cloudflare AI Search","description":"Cloudflare AI Search as a fulltext search driver: hybrid BM25 + vector retrieval behind the stock FullTextSearchDriver contract, with diagnostics for the instance configuration it depends on.","requires":[{"raw":"search","resolvesTo":null}],"ejectable":true,"data":{"entities":[],"migrations":{"count":0,"first":null,"last":null},"customEntities":0},"http":{"routes":[],"legacyRouteFiles":0,"authResolved":1},"security":{"aclFeatures":[],"setupDefaultRoleFeatures":false},"events":{"emits":[],"subscribes":[]},"ui":{"backendPages":[],"frontendPages":[],"widgets":0,"extensionHosts":0},"background":{"workers":[],"cli":["doctor","help"]},"ai":{"tools":[],"agents":[]},"config":{"credentials":[],"env":[{"name":"CF_AI_SEARCH_ACCOUNT_ID","src":{"file":"src/modules/search_cloudflare/cli.ts","line":72}},{"name":"CF_AI_SEARCH_API_TOKEN","src":{"file":"src/modules/search_cloudflare/cli.ts","line":73}},{"name":"CF_AI_SEARCH_INSTANCE","src":{"file":"src/modules/search_cloudflare/cli.ts","line":74}},{"name":"CF_AI_SEARCH_NAMESPACE","src":{"file":"src/modules/search_cloudflare/cli.ts","line":91}},{"name":"SEARCH_DISABLE_SINGLETON_CACHE","src":{"file":"src/modules/search_cloudflare/di.ts","line":95}},{"name":"SEARCH_EXCLUDE_ENCRYPTED_FIELDS","src":{"file":"src/modules/search_cloudflare/di.ts","line":96}},{"name":"CF_AI_SEARCH_MATCH_THRESHOLD","src":{"file":"src/lib/driver.ts","line":479}},{"name":"CF_AI_SEARCH_RETRIEVAL_TYPE","src":{"file":"src/lib/driver.ts","line":489}}]},"network":{"hosts":[{"host":"api.cloudflare.com","src":{"file":"src/lib/client.ts","line":13},"confidence":"high"}],"undeclared":[]},"extends":[],"omImports":{"@open-mercato/search":{"di":["addSearchStrategy"],"fulltext":["DocumentLookupKey","FullTextSearchDocument","FullTextSearchDriver","FullTextSearchHit","FullTextSearchQuery","IndexStats"],"lib/field-policy":["extractSearchableFields"],"strategies/fulltext.strategy":["FullTextSearchStrategy"]},"@open-mercato/shared":{"lib/di/container":["AppContainer","createRequestContainer"],"lib/http/fetchWithTimeout":["fetchWithTimeout","resolveTimeoutMs"],"modules/entities":["EntityId"],"modules/registry":["ModuleCli","ModuleInfo"],"modules/search":["SearchFieldPolicy","SearchModuleConfig","getSearchModuleConfigs"]}},"collisionKeys":{"moduleId":"search_cloudflare","tables":[],"aclFeatures":[],"eventIds":[],"backendRoutes":[]},"publicRoutes":0,"glyph":"0000200","lint":[{"code":"likely-example","severity":"info","message":"Module has no api/ and no data/ folder. Consider the example tag if this is an exercise.","anchor":"spec 4.3","src":null},{"code":"no-agents-md","severity":"info","message":"No AGENTS.md in the package.","anchor":"g3 §3","src":null},{"code":"peer-range-behind","severity":"info","message":"Built for >=0.6.6 <0.7.0 does not include Open Mercato 0.9.0.","anchor":"w4 §4.2","src":null},{"code":"ships-tests","severity":"info","message":"The tarball ships test files.","anchor":null,"src":null}]}],"coverage":{"routesAuthResolved":1,"integrationTs":"absent"},"diagnostics":[]},"receipts":[{"schema":1,"id":"r_20261029_71db7d0f3eca","cellKey":"71db7d0f3eca32853600bab70a2a5de83f047ea1f68d0173b68d608918aa7911","module":{"locator":"npm:@northbound-run/search-cloudflare","version":"0.1.1","integrity":"sha512-AyNqom+FSOVbc04lPrdWIEiJ8/QDm4l/61Gbh3GnOTNPSHwyEw/pqAAB6aoNyVudE/uG5JaFgfA/sF2p5zPWcw=="},"om":{"line":"0.9","version":"0.9.0","createApp":"create-mercato-app@0.9.0","preset":"classic"},"harness":{"version":"c349007c2b5b","node":"24.21.0","yarn":"4.17.1","runner":"ubuntu-latest","mode":"realistic","patches":[]},"baseline":{"status":"verified","receipt":"r_20261029_e5f612bd10e0"},"requiresInstalled":[],"steps":[{"name":"scaffold","status":"pass","sec":2,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"install","status":"pass","sec":60,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"add","status":"pass","sec":26,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"generate","status":"pass","sec":16,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"typecheck","status":"pass","sec":77,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"initialize","status":"pass","sec":29,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"build","status":"pass","sec":146,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"start","status":"pass","sec":4,"signature":null,"excerpt":null,"file":null,"line":null},{"name":"smoke","status":"pass","sec":2,"signature":null,"excerpt":null,"file":null,"line":null}],"result":{"status":"verified","failedStep":null,"alsoFailed":[],"classification":"pass","quarantineUntil":null},"transition":{"from":null,"to":"verified","confirmed":true},"run":{"id":40842502780,"attempt":1,"url":"https://github.com/open-mercato/modules/actions/runs/40842502780/job/123342268777","at":"2026-10-29T08:29:32Z","durationSec":429},"attestation":null}],"rules":[{"id":"LST-1","pass":true,"detail":"Descriptor is valid."},{"id":"LST-2","pass":true,"detail":"Stable version exists and is not deprecated."},{"id":"LST-3","pass":true,"detail":"src/modules and dist/modules pair found."},{"id":"LST-4","pass":true,"detail":"License: MIT."},{"id":"LST-5","pass":true,"detail":"No known malware or open critical advisory."},{"id":"LST-6","pass":true,"detail":"Name reviewed at merge."},{"id":"VER-1","pass":true,"detail":"Receipt on 0.9: verified (0.1.1)."},{"id":"VER-2","pass":true,"detail":"Fits statically on 0.9: ok."},{"id":"VER-3","pass":true,"detail":"Supply chain: ok."},{"id":"VER-4","pass":true,"detail":"No lint errors."},{"id":"VER-5","pass":true,"detail":"Tarball repository.url matches the descriptor."}],"maintainers":["northboundrun"]}
