Revalidation & subscriptions
The client’s cache is not a passive store — it actively keeps GET data fresh. There are four mechanisms, all built on the same tag index and Observable subscription protocol.
1. Tag-based revalidation after mutations
Section titled “1. Tag-based revalidation after mutations”Toapi servers attach cache tags to GET responses via the X-TAPI-Tags header (declared server-side with cache: { tags: [...] }). The client records which URLs carry which tags in a tag index.
Every mutation response (POST/PUT/PATCH/DELETE) may also carry an X-TAPI-Tags header listing the tags it invalidated. When such a response arrives, the client looks up every cached URL that shares any of those tags and revalidates them. Cached entries with active subscribers are re-fetched and their subscribers notified with the fresh value.
// This POST responds with, say, tag "users".// Any cached GET tagged "users" is revalidated automatically.await client.users.post({ name: "Alice" });Awaiting revalidation: .revalidated
Section titled “Awaiting revalidation: .revalidated”The promise returned by a mutation is augmented with a .revalidated property:
type Revalidating = { revalidated: Promise<void>;};.revalidated settles once the tag-based revalidation triggered by that mutation’s response has completed. Await it when you need dependent GETs to be up to date before proceeding:
const result = client.users.post({ name: "Alice" });await result; // the created userawait result.revalidated; // all matching GET caches are now fresh2. Imperative revalidation: .revalidate()
Section titled “2. Imperative revalidation: .revalidate()”Call .revalidate(query?) on any route to force a re-fetch of its GET cache entry. It resolves once the fresh response has been applied:
await client.users.revalidate();await client.users.get({ active: true }); // now served from fresh cacheIf a request for that URL is already in flight, revalidate queues a follow-up revalidation rather than issuing a redundant parallel request.
3. Time-based revalidation
Section titled “3. Time-based revalidation”When the server sends an X-TAPI-Expires-At header, the client schedules a background revalidation for that time (plus a small random jitter bounded by maxOverdueTTL, to avoid stampedes) — but only while the entry has active subscribers. Entries without subscribers are simply dropped after minTTL. Both minTTL and maxOverdueTTL are configurable via createFetchClient options.
4. Server-pushed invalidation
Section titled “4. Server-pushed invalidation”The client can receive invalidations pushed by the server, so open views stay current even when the change originates elsewhere (another user, a background job). This happens over a long-lived connection to the invalidations route:
INVALIDATIONS_ROUTE— re-exported from@toapi/client, equal to"/__tapi/invalidations". By default the client connects toapiUrl + INVALIDATIONS_ROUTE.- The stream is newline-delimited; each line is a space-separated list of tags to revalidate.
- The client reconnects automatically with exponential backoff on network errors.
- In browsers with a controlling service worker, invalidations are instead delivered via
postMessage(event typeTAPI_INVALIDATE_TAGS), and the direct stream is not opened.
Configure or disable this with the invalidationsUrl option:
// custom endpointcreateFetchClient<typeof api.routes>(apiUrl, { invalidationsUrl: "https://example.com/api/__tapi/invalidations",});
// disable server-push entirelycreateFetchClient<typeof api.routes>(apiUrl, { invalidationsUrl: false });Subscriptions tie it together
Section titled “Subscriptions tie it together”All revalidation ultimately surfaces through subscribers. Awaiting a .get() gives you a one-shot value; subscribing keeps the entry alive and delivers every subsequent value:
const result = client.todos.get();const unsubscribe = result.subscribe((next) => { next.then((todos) => render(todos));});
// A mutation elsewhere, a server push, or a timed revalidation// will now re-run this callback with fresh data.
unsubscribe(); // stop listening; entry is cleared after minTTLSee Observable for the subscription API in detail.