/** * `QueryCache` — the runtime-level query cache as a **built-in service** (ADR * 0008). Every `mount` (and the `/ssr` `renderToStream`) provides it, so a * service — not just a component — can invalidate or refetch queries: the * post-mutation reload lives next to the mutation, in the service that owns it. * * ```ts * class Todos extends Context.Service()('Todos', { * make: Effect.gen(function* () { * const cache = yield* QueryCache; * return { reload: () => cache.invalidate('todos') }; * }), * }) {} * ``` * * This module is React-free — only the Tag and the server no-op live here, so * the RSC server `mount` (which has no client store) stays client-free. The * live implementation, bound to the client store, is * `#infrastructure/react/query-cache-live.ts`. */ import * as Context from 'effect/Context'; /** What the cache can do — the shape `yield* QueryCache` resolves to. */ export interface QueryCacheApi { /** * Refetch every query carrying an `Equal`-equal tag. An active query (something * is reading it) revalidates in the background — its readers keep showing the * current value, then update; an inactive one is dropped so its next read * fetches afresh. Returns how many queries matched. */ invalidate(tag: unknown): number; /** * Refetch every query whose key is `Equal`-equal to `key` — a failed or * completed query re-runs without a key change or a remount (a retry button, * pull-to-refresh, post-mutation refresh). Returns how many queries matched. */ refetch(key: unknown): number; } /** * The service Tag. Provided automatically by `mount`/`renderToStream` when you * pass a `Layer` — your services may simply `yield* QueryCache`. An *injected* * `ManagedRuntime` is built outside effract, so it must carry the cache itself: * build with the `Layer` form if your services use it. */ export class QueryCache extends Context.Service()( '@tmonier/effract/QueryCache', ) {} /** * The RSC server path has no client store — a server render resolves each query * inline exactly once, so there is nothing to invalidate or refetch. This no-op * is the honest implementation the server `mount` provides: both methods report * zero matches. */ export const queryCacheNoop: QueryCacheApi = { invalidate: () => 0, refetch: () => 0, };