Data Fetching
The query() method is the primary way to fetch data with λ Query. It handles caching, expiration, stale-while-revalidate, and request deduplication automatically.
Basic Fetching
Section titled “Basic Fetching”Pass a cache key to query(). By default, the key is used as the URL with the global fetch():
import { createQuery } from "@studiolambda/query";
const query = createQuery();
const posts = await query.query("/api/posts");const user = await query.query("/api/users/42");The returned promise resolves to the parsed JSON response.
Custom Fetcher Per Query
Section titled “Custom Fetcher Per Query”Override the fetcher for a specific query call when the default behavior doesn’t fit:
const user = await query.query("my-user", { async fetcher(key, { signal }) { if (key === "my-user") { const response = await fetch("/api/me", { signal });
if (!response.ok) { throw new Error("Unable to fetch: " + response.statusText); }
return response.json(); }
throw new Error("Unknown key"); },});Request Deduplication
Section titled “Request Deduplication”When query() triggers a fetch, the in-flight promise is stored in the resolvers cache. Any subsequent query() call for the same key while the fetch is still pending will receive the same promise — no duplicate network requests:
// These three calls result in a single fetch request.const [a, b, c] = await Promise.all([ query.query("/api/posts"), query.query("/api/posts"), query.query("/api/posts"),]);
// a === b === c (same resolved value, same promise)This is especially useful in React where multiple components may call useQuery with the same key during the same render cycle.
Fresh Fetching
Section titled “Fresh Fetching”Force a fresh fetch that bypasses the cache entirely:
const fresh = await query.query("/api/posts", { fresh: true });With fresh: true, the cache is ignored — a new request is always made, and the result replaces any cached value.
Stale-While-Revalidate
Section titled “Stale-While-Revalidate”When a cached item has expired, the behavior depends on the stale option:
With stale: true (default)
Section titled “With stale: true (default)”The expired value is returned immediately, and a background refetch starts. When the refetch completes, a resolved event fires so subscribers can update:
const query = createQuery({ expiration: () => 1000, stale: true });
const first = await query.query("/api/posts"); // Fresh fetch.await delay(1500); // Wait for expiration.const second = await query.query("/api/posts"); // Returns stale data instantly.
// Meanwhile, a background refetch is running.// When it completes, subscribers are notified.With stale: false
Section titled “With stale: false”The query waits for the refetch to complete before returning:
const fresh = await query.query("/api/posts", { stale: false });// Always returns fresh data, but may take longer.Aborting Requests
Section titled “Aborting Requests”Cancel in-flight requests using the abort() method. This triggers the AbortSignal passed to the fetcher:
// Start a fetch.const promise = query.query("/api/large-dataset");
// Cancel it.query.abort("/api/large-dataset");The abort() method accepts several argument forms:
// Abort a single key.query.abort("/api/posts");
// Abort a single key with a reason.query.abort("/api/posts", new Error("User cancelled"));
// Abort multiple keys.query.abort(["/api/posts", "/api/users"]);
// Abort all in-flight requests.query.abort();After aborting, the resolver is removed from the cache. The abort signal is passed to your fetcher, so make sure you use it:
const query = createQuery({ async fetcher(key, { signal }) { const response = await fetch(key, { signal }); // Abortable. return response.json(); },});Error Handling
Section titled “Error Handling”When a fetch fails (the promise rejects), the behavior depends on the removeOnError option:
removeOnError: false(default): The cached item (if any) is preserved. The error is emitted via theerrorevent.removeOnError: true: The cached item is removed on error, forcing a fresh fetch on the next query.
const query = createQuery({ removeOnError: true });
try { const data = await query.query("/api/unreliable");} catch (error) { console.error("Fetch failed:", error);}For background refetches (stale-while-revalidate), errors are silenced by default — the stale data remains in the cache and the error event fires for any subscribers listening.
Snapshots
Section titled “Snapshots”Get the current cached value for a key without triggering a fetch:
const cached = await query.snapshot("/api/posts");
if (cached) { console.log("Have cached data:", cached);} else { console.log("No data cached for this key");}snapshot() returns undefined if the key is not in the items cache.
Inspecting the Cache
Section titled “Inspecting the Cache”Current Keys
Section titled “Current Keys”const itemKeys = query.keys("items"); // Keys with cached data.const resolverKeys = query.keys("resolvers"); // Keys with in-flight requests.Expiration
Section titled “Expiration”Check when a cached item expires:
const expiresAt = query.expiration("/api/posts");
if (expiresAt) { console.log("Expires at:", expiresAt.toISOString()); console.log("Is expired:", expiresAt < new Date());}Returns undefined if the key is not in the items cache.