Skip to content

realm-server: extend job-scoped search cache to _federated-search-prerendered#4869

Closed
habdelra wants to merge 1 commit into
mainfrom
extend-job-scoped-search-cache-to-prerendered-endpoint
Closed

realm-server: extend job-scoped search cache to _federated-search-prerendered#4869
habdelra wants to merge 1 commit into
mainfrom
extend-job-scoped-search-cache-to-prerendered-endpoint

Conversation

@habdelra
Copy link
Copy Markdown
Contributor

Summary

The per-job JobScopedSearchCache already covers _federated-search but _federated-search-prerendered calls searchPrerenderedRealms directly and bypasses it. Realms that drive their cards through PrerenderedSearchResource therefore re-run the same SQL+HTML lookups on every cohort render during a reindex, while realms that drive cards through store.search ride the cache. This PR makes the two endpoints symmetric: the prerendered handler wraps its live SQL call in the same cache instance, gated on the same indexer-traffic headers.

Trigger condition

The cache is consulted on _federated-search-prerendered only when both of the following hold:

  • x-boxel-job-id is present and well-formed (only the indexer worker stamps this; user / API callers never carry it), AND
  • x-boxel-consuming-realm is present and well-formed (the host's render route sets it only during prerender).

Any request without either header falls through to a live populate, exactly like today. Both gates re-use the existing sanitizePrerenderJobId / sanitizeConsumingRealmHeader helpers — no new sanitisers, no new headers.

Cache-key extension

buildInnerKey's signature (realms, query, opts) is unchanged. The prerendered endpoint's response varies with three additional request fields:

  • prerenderedHtmlFormat (embedded | fitted | atom | head)
  • cardUrls (optional filter)
  • renderType (optional codeRef)

All three are folded into opts before the call, so the sortKeysDeep-canonicalised key already segregates entries that differ on any of them. A test asserts that flipping prerenderedHtmlFormat or adding cardUrls under an otherwise-identical (jobId, realms, query) tuple fires a fresh populate.

CachedEntry.result is now stored as unknown (typed at getOrPopulate's call site via a generic), so the same instance can hold both LinkableCollectionDocument and PrerenderedCardCollectionDocument results without one endpoint serving the other's shape — collisions are structurally impossible because the inner key segregates by every parameter that affects the response.

Host plumbing

PrerenderedSearchResource.fetchPrerenderedCards now attaches the same three prerender-context headers (x-boxel-during-prerender, x-boxel-consuming-realm, x-boxel-job-id) that store.ts already attaches to _federated-search. The three header-helper functions move from store.ts to a shared host/app/lib/prerender-fetch-headers.ts module so both fetch sites read the page globals identically. Live (non-prerender) SPA fetches see no values for any of those globals and continue to send no headers.

Shared cache instance

The same searchCache constructed at routes.ts:118 is now passed to both handleSearch({ searchCache }) and handleSearchPrerendered({ searchCache }). One cache, shared.

Test plan

  • CI: three new realm-server tests in server-endpoints/search-prerendered-test.ts cover the hit/miss-by-jobId path, the htmlFormat / cardUrls cache-key extension, and the no-headers bypass.
  • CI: existing JobScopedSearchCache unit tests and _federated-search cache test continue to pass under the now-generic getOrPopulate<T>.
  • Local bench on a realm whose cards drive through PrerenderedSearchResource (e.g. a searchPrerendered-heavy realm during a reindex): confirm fewer SQL-level searchPrerendered populates land per job.

🤖 Generated with Claude Code

…rendered

Wraps `searchPrerenderedRealms` in the `_federated-search-prerendered`
handler with the same `JobScopedSearchCache` instance already used by
`_federated-search`, gated on the same `x-boxel-job-id` +
`x-boxel-consuming-realm` indexer-traffic headers. User-facing API
callers continue to bypass the cache and observe live state.

The prerendered handler's request shape carries htmlFormat / cardUrls
/ renderType which materially change the response body. These are
passed through `opts` so the cache's sortKeysDeep-canonicalised inner
key segregates entries that differ on any of them; the cache class
stays signature-stable (`getOrPopulate(jobId, realms, query, opts,
populate)`), and stores results opaquely so both endpoints' document
types share the same store.

Host: PrerenderedSearchResource stamps the consuming-realm and job-id
headers on its outbound `_federated-search-prerendered` request, the
same pattern store.ts uses for `_federated-search`. The three header
helpers (during-prerender / consuming-realm / job-id) move from
store.ts to a shared `lib/prerender-fetch-headers.ts` module so both
fetch sites read the globals identically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@habdelra
Copy link
Copy Markdown
Contributor Author

Superseded by #4870 (same scope, plus the per-job hit/miss instrumentation folded in).

@habdelra habdelra closed this May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant