# Bridge Netlify site: set **Package directory** to `deploy/bridge` in the Netlify UI; # leave **Base directory** empty so it defaults to the repo root. Per Netlify monorepo # docs, `functions` and `publish` below are relative to the base directory (repo root), # not to this folder — do not use `../..` here (that escapes the repo and fails deploy). [build] command = "npm install && node scripts/netlify-redirects.mjs && cd hub/gateway && npm ci && cd ../bridge && npm ci" functions = "netlify/functions" publish = "public" [build.environment] # Netlify’s build tooling can error on Node 22 (e.g. @netlify/build internal TypeError). # Gateway site uses root netlify.toml with NODE_VERSION=20; bridge must pin the same. NODE_VERSION = "20" USE_BRIDGE_FUNCTION = "true" # Bridge index/search loads sqlite-vec + better-sqlite3 native code; must not be esbuild-bundled # (sqlite-vec getLoadablePath uses import.meta.url — bundling yields Invalid URL input '.'). [functions] node_bundler = "esbuild" external_node_modules = [ "better-sqlite3", "sqlite-vec", "sqlite-vec-linux-x64", "sqlite-vec-linux-arm64", "sqlite-vec-darwin-x64", "sqlite-vec-darwin-arm64", "sqlite-vec-windows-x64" ] # Back up now (/api/v1/vault/sync) can exceed the default ~10s limit (export + N proposal GETs + GitHub API). # Align with gateway proxy budget (root netlify.toml [functions."gateway"] timeout = 60). # Raised 26 → 60 (Netlify sync-function platform max per docs.netlify.com/build/functions/overview) # so POST /api/v1/index has headroom under DeepInfra embedding latency. See # `hub/bridge/index-timing.mjs` for per-step instrumentation that lets us identify # which sub-step dominates before choosing parallelize/async as the next step. # exceljs: see root netlify.toml [functions."bridge"] (large bundle; keep external for the bridge function). [functions."bridge"] timeout = 60 external_node_modules = ["exceljs"] # Background function: receives kickoff from the sync `bridge` function when the # auto-routing preflight in `hub/bridge/server.mjs POST /api/v1/index` decides a # re-index won't fit in 60 s (large vault, dim migration, first-time index, etc.). # The `-background` filename suffix is what makes Netlify treat this as a # background function (15-min platform max, returns 202 to caller within ~50 ms). # See `netlify/functions/bridge-index-background.mjs` and # `lib/bridge-index-preflight-estimate.mjs` for the routing math. # external_node_modules: same as the sync bridge function (sqlite-vec native code, # exceljs is large) — both functions import the same Express app and need the # same modules left external. [functions."bridge-index-background"] external_node_modules = ["exceljs"] # Send all requests to the bridge function with path preserved (/:splat). # # Why no explicit passthrough for /.netlify/functions/*: Netlify's redirect # engine REJECTS user-defined rules whose `from` starts with `/.netlify/...` # (validated at deploy time with "Invalid /.netlify path in redirect source"). # This rejection is intentional — per # https://docs.netlify.com/routing/redirects/redirect-options/#shadowing the # `/.netlify/...` namespace is automatically excluded from user redirects, # INCLUDING catch-all rules with `force = true`. Direct calls to # `/.netlify/functions/` always reach their respective function # regardless of what this catch-all says. # # The May 2026 PR #205 hotfix initially attempted to add an explicit # passthrough here; it was rejected by Netlify validation and removed. The # defense-in-depth guard now lives in `lib/bridge-index-kickoff-response.mjs` # instead — that helper asserts the kickoff fetch actually got HTTP 202 (the # only valid response from a Netlify background fn) so a future routing # misconfiguration fails loudly instead of silently returning false success # to the browser. [[redirects]] from = "/*" to = "/.netlify/functions/bridge/:splat" status = 200 force = true