Configuration
λ Query is highly configurable. Options can be set at the instance level (applies to all queries) or per-query (overrides instance defaults for a single call).
Creating an Instance
Section titled “Creating an Instance”import { createQuery } from "@studiolambda/query";
const query = createQuery({ expiration: () => 5000, stale: true,});Instance Options
Section titled “Instance Options”These options apply globally to all queries made with the instance:
| Option | Type | Default | Description |
|---|---|---|---|
expiration | (item: T) => number | () => 2000 | Function returning the cache TTL in milliseconds. Receives the resolved item, allowing dynamic expiration based on data. |
fetcher | (key: string, opts: { signal: AbortSignal }) => Promise<T> | fetch wrapper | The function that performs the actual data fetching. Receives the cache key and an object with an AbortSignal. |
stale | boolean | true | Whether to return stale (expired) data immediately while revalidating in the background. |
removeOnError | boolean | false | Whether to remove the cached item if a background refetch fails. |
fresh | boolean | false | Whether to always bypass the cache and fetch fresh data. |
Advanced Instance Options
Section titled “Advanced Instance Options”These configure the underlying infrastructure:
| Option | Type | Default | Description |
|---|---|---|---|
itemsCache | Cache<ItemsCacheItem> | new Map() | Custom cache implementation for resolved items. Must implement get, set, delete, keys. |
resolversCache | Cache<ResolversCacheItem> | new Map() | Custom cache implementation for in-flight resolvers. |
events | EventTarget | new EventTarget() | Custom event system for cache lifecycle events. |
broadcast | BroadcastChannel | undefined | Cross-tab broadcast channel. When set, mutations, hydrations, and cache invalidations are synchronized across browser tabs. |
Default Fetcher
Section titled “Default Fetcher”If you don’t provide a custom fetcher, λ Query uses the global fetch() with the cache key as the URL:
// The default fetcher does approximately this:async function defaultFetcher(key, { signal }) { const response = await fetch(key, { signal });
if (!response.ok) { throw new Error("Unable to fetch the data: " + response.statusText); }
return response.json();}This means your cache keys should be valid URLs when using the default fetcher. The signal parameter is an AbortSignal that is triggered when the query is aborted.
Custom Fetcher
Section titled “Custom Fetcher”Provide a custom fetcher to control how data is loaded:
const query = createQuery({ async fetcher(key, { signal }) { const response = await fetch(`https://api.example.com${key}`, { signal, headers: { Authorization: `Bearer ${getToken()}`, }, });
if (!response.ok) { throw new Error(`API error: ${response.status}`); }
return response.json(); },});Per-Query Fetcher
Section titled “Per-Query Fetcher”You can override the fetcher for a specific query call:
const user = await query.query("current-user", { async fetcher(key, { signal }) { const response = await fetch("/api/me", { signal }); return response.json(); },});This is useful when a specific resource needs different fetching logic (e.g., a GraphQL query, a local storage read, or a WebSocket message).
Expiration
Section titled “Expiration”The expiration function determines how long a cached item is considered fresh. It receives the resolved item, so you can set dynamic expiration based on the data:
const query = createQuery({ expiration(item) { // Cache user profiles for 30 seconds. // Cache everything else for 5 seconds. if (item?.type === "user") return 30_000; return 5_000; },});The default expiration is 2 seconds (() => 2000).
After an item expires:
- With
stale: true(default): the expired value is returned immediately, and a background refetch starts. - With
stale: false: the query blocks until the refetch completes.
Staleness
Section titled “Staleness”The stale option controls the SWR behavior:
// Always wait for fresh data (no stale returns).const query = createQuery({ stale: false });
// Return stale data while revalidating (default).const query = createQuery({ stale: true });You can also override per-query:
// This specific query should always return fresh data.const data = await query.query("/api/critical-data", { stale: false });Lazy Reconfiguration
Section titled “Lazy Reconfiguration”You can reconfigure an instance at runtime using the configure() method. Only the provided options are updated — everything else keeps its current value:
const query = createQuery({ stale: true });
// Later, change the expiration without affecting other options.query.configure({ expiration: () => 10_000,});Cache Interface
Section titled “Cache Interface”If you need a custom cache implementation (e.g., backed by IndexedDB or a size-limited LRU), implement the Cache interface:
interface Cache<T> { get(key: string): T | undefined; set(key: string, value: T): void; delete(key: string): void; keys(): IterableIterator<string>;}A standard Map satisfies this interface, which is why it’s used as the default. Pass custom caches at construction time:
const query = createQuery({ itemsCache: new LRUCache(100), // Your custom LRU implementation. resolversCache: new Map(),});