CS-11156: cross-replica clearLocalCaches broadcast via NOTIFY#4842
Conversation
Preview deploymentsHost Test Results 1 files 1 suites 1h 46m 25s ⏱️ Results for commit 05b77ca. Realm Server Test Results 1 files ± 0 1 suites ±0 8m 36s ⏱️ + 1m 42s Results for commit 05b77ca. ± Comparison against earlier commit ececde7. |
Two completely different caches were both called "the module cache" in
this codebase:
- `Realm.#moduleCache` — in-process bytes of transpiled JS (the
prerender's input)
- `CachingDefinitionLookup`'s `modules` DB table — assembled card
definitions (the prerender's output)
Both even had a type named `ModuleCacheEntry` with different shapes. The
juxtaposition in `handle-publish-realm.ts` after #4842
(`definitionLookup.clearRealmCache(url)` next to
`realm.clearLocalCachesAndBroadcast()`) made the collision impossible
to ignore.
This commit renames the Realm-side cache to make the "transpiled JS
bytes" framing explicit at the API surface, and renames the public
cache-wipe methods so each call site self-documents which cache it
touches.
- `Realm.#moduleCache` → `Realm.#transpiledModuleCache`
- Type `ModuleCacheEntry` (in `realm.ts`, local to that file) →
`TranspiledModuleEntry`
- `Realm.clearLocalCaches()` → `Realm.clearLocalSourceCaches()`
- `Realm.clearLocalCachesAndBroadcast()` →
`Realm.clearLocalSourceCachesAndBroadcast()`
- Internal helpers renamed consistently
(`#dropModuleCacheEntry`, `#bumpModuleCacheGeneration`, the
generation maps, etc.)
Mechanical rename — no behavior change. 16/16 listener tests pass.
Tier 2 (DefinitionLookup-side renames: `ModuleCacheEntry` →
`DefinitionCacheEntry`, `clearRealmCache` → `clearRealmDefinitions`,
`clearAllModules` → `clearAllDefinitions`, etc.) is a separate
follow-up commit. Tier 3 (DB column + NOTIFY channel rename, needs a
deploy plan for rolling-update compatibility) is deliberately deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bbdeef8 to
d4a09f3
Compare
Two completely different caches were both called "the module cache" in
this codebase:
- `Realm.#moduleCache` — in-process bytes of transpiled JS (the
prerender's input)
- `CachingDefinitionLookup`'s `modules` DB table — assembled card
definitions (the prerender's output)
Both even had a type named `ModuleCacheEntry` with different shapes. The
juxtaposition in `handle-publish-realm.ts` after #4842
(`definitionLookup.clearRealmCache(url)` next to
`realm.clearLocalCachesAndBroadcast()`) made the collision impossible
to ignore.
This commit renames the Realm-side cache to make the "transpiled JS
bytes" framing explicit at the API surface, and renames the public
cache-wipe methods so each call site self-documents which cache it
touches.
- `Realm.#moduleCache` → `Realm.#transpiledModuleCache`
- Type `ModuleCacheEntry` (in `realm.ts`, local to that file) →
`TranspiledModuleEntry`
- `Realm.clearLocalCaches()` → `Realm.clearLocalSourceCaches()`
- `Realm.clearLocalCachesAndBroadcast()` →
`Realm.clearLocalSourceCachesAndBroadcast()`
- Internal helpers renamed consistently
(`#dropModuleCacheEntry`, `#bumpModuleCacheGeneration`, the
generation maps, etc.)
Mechanical rename — no behavior change. 16/16 listener tests pass.
Tier 2 (DefinitionLookup-side renames: `ModuleCacheEntry` →
`DefinitionCacheEntry`, `clearRealmCache` → `clearRealmDefinitions`,
`clearAllModules` → `clearAllDefinitions`, etc.) is a separate
follow-up commit. Tier 3 (DB column + NOTIFY channel rename, needs a
deploy plan for rolling-update compatibility) is deliberately deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65b0596 to
0288f5a
Compare
The CS-11043 publish-realm fix invalidates the publish-handling replica's #sourceCache / #moduleCache before the reindex enqueues so the reindex's prerender doesn't see pre-swap bytes. That fix is correct on one replica. On two+ replicas behind a load balancer, peers still hold pre-swap bytes in their own caches and the reindex's HTTP fan-out to peers serves stale source — back into boxel_index.isolated_html, served forever. Extends the existing per-path `realm_file_changes` NOTIFY channel with a bulk payload `<realmURL>:*` meaning "drop every cached path for this realm". Wired into publish, unpublish, and delete realm handlers; on receive, peers call `Realm.clearLocalCaches()`. * runtime-common/realm.ts: `REALM_FILE_CHANGES_WILDCARD` sentinel, standalone `notifyAllFileChanges(dbAdapter, realmURL)` emitter, and `Realm.notifyAllFileChanges()` instance form. Same fire-and-forget semantics as `Realm.#notifyFileChange`; missed NOTIFY is a bounded staleness window per §9 of the registry doc, not data corruption. * realm-file-changes-listener.ts: dispatch branches on the wildcard payload to `Realm.clearLocalCaches()`. Existing per-path parser + realm lookup reused as-is. * handle-publish-realm.ts: keeps the sync local `clearLocalCaches()` before the reindex enqueue (replica's own prerender fan-out must bypass its cache) and adds the broadcast after. Self-NOTIFY is a no-op since clearLocalCaches is idempotent. * handle-unpublish-realm.ts and handle-delete-realm.ts: broadcast after the FS removal. Defense-in-depth against the brief window before peers unmount via `NOTIFY realm_registry`. Tests in realm-file-changes-listener-test.ts: * parsePayload returns `path: '*'` for both `host:port` and port-less URLs * dispatch routes wildcard to `clearLocalCaches`, not `invalidateCache` * end-to-end through the live LISTEN client: the new emitter → Postgres NOTIFY → the listener → `clearLocalCaches` on a fake peer-side realm Stacks on #4840 (CS-11125 — per-realm advisory locks on the data plane). The lock is what makes the broadcast's "after the swap" ordering meaningful — without serialization a concurrent same-realm write could land in the staleness window. Linear: https://linear.app/cardstack/issue/CS-11156 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to the initial CS-11156 PR. The publish-realm handler had to
call two methods in sequence to fully invalidate the publishing
replica's cache plus all peers' caches:
mountedRealmForCacheClear.clearLocalCaches();
await mountedRealmForCacheClear.notifyAllFileChanges();
Every future emitter would have to remember both lines. Mirroring
`CachingDefinitionLookup.clearRealmCache(url)` — which bundles local
generation bump + DB DELETE + cross-instance NOTIFY in one method —
introduce `Realm.clearLocalCachesAndBroadcast()` that does both steps
and let the handler make one call.
Also drop `Realm.notifyAllFileChanges()`. It was a thin wrapper around
the standalone free function `notifyAllFileChanges(dbAdapter, url)` and
they were used inconsistently — publish used the method, unpublish
used the free function despite having a Realm instance in scope. The
two surfaces collapse to one clear rule:
- Need local clear AND broadcast (publish handler, realm staying up):
`realm.clearLocalCachesAndBroadcast()`.
- Need ONLY the peer broadcast (unpublish/delete handlers, realm
being torn down — local cache is about to be GC'd with the Realm
instance): `notifyAllFileChanges(dbAdapter, url)`.
`Realm.clearLocalCaches()` stays as the local-only primitive the
LISTEN handler calls on receive (no broadcast, no NOTIFY loop). The
free function `notifyAllFileChanges` is the single cross-replica emit
surface — the Realm class no longer needs to know about channel names
or payload formats.
No behavior change. All 16 realm-file-changes-listener tests still
pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The publish-realm handler had a hand-rolled `DELETE FROM modules WHERE
resolved_realm_url = $1` to drop stale error entries before the reindex
fan-out. That covers the DB rows but is strictly weaker than
`CachingDefinitionLookup.clearRealmCache(url)`, which:
1. bumps the per-realm generation counter so in-flight prerenders
on this replica that started before the DELETE see a mismatch at
persist time and discard their result instead of re-inserting a
row this invalidation just removed,
2. drops in-flight prerender promises for the realm so new callers
install their own pending against post-swap state rather than
joining a stale shared transpile,
3. runs the same DELETE, and
4. broadcasts on `module_cache_invalidated` so peer realm-server
replicas perform 1-3 on their own state.
The raw DELETE did only step 3. The reindex worker's prerender fan-out
fires immediately after this code path through HTTP into both this
realm-server and its peers, so missing steps 1, 2, and 4 was exactly
the modules-cache analog of the byte-cache staleness this PR fixes via
`clearLocalCachesAndBroadcast()`.
`clearRealmCache` already runs via the post-fullIndex completion path
in `Realm.startReindex` (realm.ts:1068), but that's at the *end* of
the reindex — too late for the prerender fan-out at the start. Running
it pre-reindex ensures the rebuild starts against a coherent cache on
every replica.
`definitionLookup` is already plumbed through `CreateRoutesArgs`; the
handler just needed to destructure it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two completely different caches were both called "the module cache" in
this codebase:
- `Realm.#moduleCache` — in-process bytes of transpiled JS (the
prerender's input)
- `CachingDefinitionLookup`'s `modules` DB table — assembled card
definitions (the prerender's output)
Both even had a type named `ModuleCacheEntry` with different shapes. The
juxtaposition in `handle-publish-realm.ts` after #4842
(`definitionLookup.clearRealmCache(url)` next to
`realm.clearLocalCachesAndBroadcast()`) made the collision impossible
to ignore.
This commit renames the Realm-side cache to make the "transpiled JS
bytes" framing explicit at the API surface, and renames the public
cache-wipe methods so each call site self-documents which cache it
touches.
- `Realm.#moduleCache` → `Realm.#transpiledModuleCache`
- Type `ModuleCacheEntry` (in `realm.ts`, local to that file) →
`TranspiledModuleEntry`
- `Realm.clearLocalCaches()` → `Realm.clearLocalSourceCaches()`
- `Realm.clearLocalCachesAndBroadcast()` →
`Realm.clearLocalSourceCachesAndBroadcast()`
- Internal helpers renamed consistently
(`#dropModuleCacheEntry`, `#bumpModuleCacheGeneration`, the
generation maps, etc.)
Mechanical rename — no behavior change. 16/16 listener tests pass.
Tier 2 (DefinitionLookup-side renames: `ModuleCacheEntry` →
`DefinitionCacheEntry`, `clearRealmCache` → `clearRealmDefinitions`,
`clearAllModules` → `clearAllDefinitions`, etc.) is a separate
follow-up commit. Tier 3 (DB column + NOTIFY channel rename, needs a
deploy plan for rolling-update compatibility) is deliberately deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`CachingDefinitionLookup` caches assembled card *definitions* (per-export
results, error entries, dependency lists). It's not a module-byte cache.
But every public type and method on it was named "module cache" or
"modules" — which collided directly with `Realm.#transpiledModuleCache`
(renamed last commit), the actual JS-bytes cache.
Public API now reads as what it does:
- `ModuleCacheEntry` → `DefinitionCacheEntry`
- `ModuleCacheEntries` → `DefinitionCacheEntries`
- `ModuleCacheEntryQuery` → `DefinitionCacheEntryQuery`
- `getModuleCacheEntry` → `getCachedDefinitions`
- `getModuleCacheEntries` → `getCachedDefinitionsBatch`
- `clearAllModules` → `clearAllDefinitions`
- `clearRealmCache` → `clearRealmDefinitions`
Plus internal-consistency renames on the notify-emitter helpers
(`notifyModuleCacheInvalidations` → `notifyDefinitionCacheInvalidations`,
etc.).
What deliberately did NOT move (Tier 3, deferred — needs a deploy
plan for rolling-update compatibility between replicas listening on
the old vs. new channel name):
- `modules` DB table name and the `MODULES_TABLE` JS constant
- `module_cache_invalidated` NOTIFY channel name and the
`MODULE_CACHE_INVALIDATED_CHANNEL` constant
- File names containing "module-cache-*"
All 16 realm-file-changes-listener tests, 21 module-cache-invalidation-
listener tests, and 9 module-cache-coordination tests pass after the
rename. `tsc` clean across runtime-common / realm-server / host.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d4a09f3 to
e995d8f
Compare
The two rename passes earlier on this branch pushed several identifiers past prettier's wrap threshold, which forced surrounding blocks to re-indent on a subsequent format pass. Fix five files that the wrap-threshold cascade touched (one test file with a ~500-line indentation shift; four runtime-common files with small adjustments). No behavior change; pure formatting. Resolves the failing Lint CI check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR extends cross-replica cache invalidation for realms by adding a bulk realm_file_changes NOTIFY payload (<realmURL>:*) so peer realm-server replicas can drop all in-process cached source/transpiled bytes after publish/unpublish/delete operations, preventing stale prerender/index artifacts in multi-replica deployments. It also renames and routes “module cache” APIs through DefinitionLookup’s coordinated invalidation surfaces so generation bumps, in-flight drops, and peer broadcasts are not bypassed.
Changes:
- Add wildcard
realm_file_changesbroadcast (<realmURL>:*) and receiver-side dispatch to clear all local source/transpiled caches for a realm. - Replace publish-time raw
DELETE FROM modules ...withdefinitionLookup.clearRealmDefinitions(...)and rename DefinitionLookup “module cache” APIs to “definition cache” APIs across runtime/index runner/tests. - Expand test coverage for wildcard payload parsing, dispatch behavior, and end-to-end NOTIFY round-trips.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/realm.ts | Adds wildcard NOTIFY support, bulk emitter, and local+broadcast helper; renames in-process module cache to #transpiledModuleCache. |
| packages/runtime-common/definition-lookup.ts | Renames/clarifies “module cache” API to “definition cache” API; keeps coordinated invalidation/broadcast semantics. |
| packages/runtime-common/index-runner.ts | Updates index runner to consume renamed DefinitionLookup cache APIs. |
| packages/runtime-common/index-runner/dependency-resolver.ts | Wires renamed batch cache-entry reader into dependency resolution. |
| packages/runtime-common/index-runner/index-backed-dependency-errors.ts | Uses renamed batch cache-entry reader for dependency error materialization. |
| packages/realm-server/lib/realm-file-changes-listener.ts | Handles wildcard payload by clearing all local source/transpiled caches for the realm. |
| packages/realm-server/handlers/handle-publish-realm.ts | Clears definition cache via DefinitionLookup and performs local+cross-replica source-cache clear before reindex enqueue. |
| packages/realm-server/handlers/handle-unpublish-realm.ts | Broadcasts bulk realm cache clear after realm removal to cover peer staleness windows. |
| packages/realm-server/handlers/handle-delete-realm.ts | Broadcasts bulk realm cache clear for source + published realms after FS removal. |
| packages/realm-server/handlers/handle-reindex.ts | Uses renamed DefinitionLookup realm-clear API before enqueueing reindex. |
| packages/realm-server/handlers/handle-full-reindex.ts | Uses renamed DefinitionLookup global-clear API before full reindex. |
| packages/realm-server/handlers/handle-post-deployment.ts | Uses renamed DefinitionLookup global-clear API post-deploy. |
| packages/realm-server/main.ts | Uses renamed DefinitionLookup global-clear API on startup. |
| packages/realm-server/lib/module-cache-invalidation-listener.ts | Updates docs/comments to renamed DefinitionLookup invalidation methods. |
| packages/realm-server/lib/module-cache-coordination.ts | Updates docs/comments to renamed persistence method. |
| packages/realm-server/tests/realm-file-changes-listener-test.ts | Adds wildcard parse/dispatch/e2e coverage (emitter → NOTIFY → listener). |
| packages/realm-server/tests/card-source-endpoints-test.ts | Updates tests to renamed clearLocalSourceCaches() API. |
| packages/realm-server/tests/publish-unpublish-realm-test.ts | Updates docstring references for renamed realm cache-clear API. |
| packages/realm-server/tests/module-cache-race-test.ts | Renames references to #transpiledModuleCache and reformats test blocks accordingly. |
| packages/realm-server/tests/module-cache-invalidation-listener-test.ts | Updates tests for renamed DefinitionLookup clear methods. |
| packages/realm-server/tests/module-cache-coordination-test.ts | Updates tests for renamed DefinitionLookup read method. |
| packages/realm-server/tests/definition-lookup-test.ts | Updates tests for renamed DefinitionLookup clear methods and related messaging. |
| packages/realm-server/tests/indexing-test.ts | Updates tests for renamed DefinitionLookup read methods. |
| packages/realm-server/tests/matches-filter-integration-test.ts | Updates DefinitionLookup stub to renamed method set. |
| packages/host/tests/unit/index-query-engine-test.ts | Updates DefinitionLookup stub to renamed method set. |
Comments suppressed due to low confidence (2)
packages/realm-server/lib/realm-file-changes-listener.ts:108
- The catch log message always says
invalidateCache failed..., but for wildcard payloads this code callsclearLocalSourceCaches()instead. Updating the log to reflect the actual operation (or making it generic) will make debugging NOTIFY handling much clearer.
try {
if (parsed.path === REALM_FILE_CHANGES_WILDCARD) {
realm.clearLocalSourceCaches();
} else {
realm.invalidateCache(parsed.path);
}
} catch (err: unknown) {
log.warn(
`invalidateCache failed for ${parsed.url} ${parsed.path}: ${String(err)}`,
);
packages/runtime-common/realm.ts:1584
- PR description says the new API is
clearLocalCachesAndBroadcast(), but the code addsclearLocalSourceCachesAndBroadcast(). Either update the PR description/API section or rename the method for consistency, since this is a new public surface that others may copy/paste.
// Drop this replica's own `#sourceCache` / `#transpiledModuleCache` AND broadcast
// the same wipe to peer replicas. Used by the publish-realm handler
// before the reindex enqueue: this replica's own prerender fan-out must
// bypass its cache (sync local clear), and peer replicas must drop
// their pre-swap bytes too (cross-instance NOTIFY). Self-receive of the
// NOTIFY is a no-op since `clearLocalSourceCaches()` is idempotent.
//
// Bundles local + broadcast in one call, mirroring
// `CachingDefinitionLookup.clearRealmDefinitions(url)` — handlers don't have
// to remember both steps. Callers that only need the peer broadcast
// (because their own Realm instance is about to be unmounted anyway —
// unpublish/delete handlers) use the standalone `notifyAllFileChanges`
// free function above instead.
async clearLocalSourceCachesAndBroadcast(): Promise<void> {
this.clearLocalSourceCaches();
await notifyAllFileChanges(this.#dbAdapter, this.url);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Three small fixes from the Copilot review: 1. Rename `#dropAllTranspiledDefinitionCacheEntries` → `#dropAllTranspiledModuleCacheEntries`. The Tier-2 rename pass accidentally crossed the disambiguation it was trying to enforce — the method clears `#transpiledModuleCache` (Realm's per-path transpiled module bytes), not anything to do with DefinitionLookup's definition cache. The renamed name matches its sibling per-path helper `#dropTranspiledModuleEntry`. 2. Rewrite the `clearLocalSourceCaches` docstring to drop the forward-looking "CS-11156 will replace the publish handler's local call here" framing — this PR is CS-11156, so the framing is now past-tense. New comment describes the actual two callers (publish handler via the broadcast wrapper, and the cross-replica listener for the wildcard payload). 3. Listener catch-log: was unconditionally `invalidateCache failed` even for the wildcard path that calls `clearLocalSourceCaches`. Pick the right verb based on which branch was taken. No behavior change beyond the listener log message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
#sourceCache/#transpiledModuleCacheonly on the replica that processes the request. On 2+ replicas, peers keep pre-swap bytes and the reindex's prerender HTTP fan-out lands stale source inboxel_index.isolated_html— served forever. Same shape on unpublish / delete during the window between the registry-row commit and the peer-side reconciler unmount.realm_file_changesNOTIFY channel with a bulk payload<realmURL>:*meaning "drop every cached path for this realm." Wired into publish, unpublish, and delete realm handlers; on receive, peers callRealm.clearLocalSourceCaches().DELETE FROM modules WHERE resolved_realm_url = $1withdefinitionLookup.clearRealmDefinitions(url)so the definitions cache invalidation gets the same cross-replica treatment (generation bump + in-flight drop + DELETE + NOTIFY) instead of just the DB delete.Linear: CS-11156.
API surface
Mirrors
CachingDefinitionLookup.clearRealmDefinitions(url)— one method bundles local invalidation + cross-instance NOTIFY so handlers don't have to remember both steps. Three entry points after this PR:realm.clearLocalSourceCachesAndBroadcast()notifyAllFileChanges(dbAdapter, url)(free function)realm.clearLocalSourceCaches()The definitions cache (separate from the in-process byte caches above) keeps its existing
definitionLookup.clearRealmDefinitions(url)entry point — this PR just stops bypassing it from the publish handler.What's in
runtime-common/realm.ts—REALM_FILE_CHANGES_WILDCARD = '*'sentinel, standalonenotifyAllFileChanges(dbAdapter, realmURL)emitter (the single cross-replica emit surface — Realm doesn't need to know about channel names or payload formats), andRealm.clearLocalSourceCachesAndBroadcast()instance method that bundlesclearLocalSourceCaches()+ the free-function emit. Same best-effort fire-and-forget shape asRealm.#notifyFileChange; missed NOTIFY is a bounded staleness window per §9 ofdocs/db-authoritative-realm-registry.md, not data corruption.realm-file-changes-listener.ts— dispatch branches onpath === '*'toRealm.clearLocalSourceCaches(). Existing regex parser + realm lookup reused as-is (the wildcard payload parses cleanly withpath = '*').handle-publish-realm.ts—DELETE FROM modules WHERE resolved_realm_url = $1withawait definitionLookup.clearRealmDefinitions(publishedRealmURL)so the definitions-cache invalidation also bumps the per-realm generation counter, drops in-flight prerender promises, and broadcasts onmodule_cache_invalidated— the modules-table analog of the byte-cache fix this PR is making. Without those extra steps an in-flight prerender that started before the DELETE could re-insert a stale row at persist time, and peer replicas would keep their cached rows + generation counters until their own next invalidation arrived.clearRealmDefinitionsalready runs via the post-fullIndex completion path but that's at the end of the reindex — too late for the prerender fan-out at the start.await mountedRealmForCacheClear.clearLocalSourceCachesAndBroadcast()) for the byte-cache wipe + cross-replica broadcast before the reindex enqueue. Self-NOTIFY is a no-op sinceclearLocalSourceCachesis idempotent.handle-unpublish-realm.tsandhandle-delete-realm.ts— call the standalonenotifyAllFileChanges(dbAdapter, url)after the FS removal. No local clear needed: the realm is about to be unmounted, so the in-process cache will be garbage-collected with theRealminstance. Defense-in-depth against the brief window before peers unmount viaNOTIFY realm_registry. (Per-filedeleteAllin unpublish already emits per-path NOTIFYs; this is the catch-all for the registry-commit-to-unmount window.)Naming pass
Two follow-up commits in this branch did targeted renames to disambiguate which cache layer each identifier refers to:
#moduleCache→#transpiledModuleCacheonRealm(the byte cache), andclearRealmCache→clearRealmDefinitionsonCachingDefinitionLookup(the definitions cache). Both methods named here were also touched. The renames removed a long-standing "two different things both called modules cache" hazard.Tests
packages/realm-server/tests/realm-file-changes-listener-test.ts(12 existing pass; 4 new):parsePayloadround-trips<realmURL>:*topath: '*'for both port-bearing and port-less URLs.clearLocalSourceCaches()exactly once and neverinvalidateCache().notifyAllFileChangesemitter → Postgres NOTIFY → listener →clearLocalSourceCacheson a fake peer-side Realm.Composition with CS-11119
This PR handles the byte-cache side of cross-replica invalidation: write-event triggered (publish/unpublish/delete), drops
#sourceCache+#transpiledModuleCacheon peers viarealm_file_changes:*wildcard.The orthogonal read-side-cache story (
#inFlightSearch+#cachedRealmInfo) is closed by CS-11119 / PR #4862 on a separaterealm_index_updatedchannel that fires at SWAP time rather than write time. The two PRs touch adjacent but non-overlapping surfaces and can land in either order.Compatibility
clearLocalSourceCaches()insideclearLocalSourceCachesAndBroadcast()still runs; the NOTIFY is a no-op when no other replicas are LISTENing.clearRealmDefinitionswas already in use in the post-fullIndex completion path; calling it pre-reindex too is purely additive.notifyis a no-op there.Test plan
realm-file-changes-listener-test.ts(16/16) including 4 new wildcard teststsconpackages/runtime-common+packages/realm-server— no new errors🤖 Generated with Claude Code