Skip to content

Getting Started

λ Query is a lightweight library for managing asynchronous data. It implements a stale-while-revalidate (SWR) caching strategy, where cached data is returned immediately while a background refetch ensures freshness. The core is framework-agnostic, with first-class React 19 bindings that leverage use() for Suspense integration.

λ Query has no dependencies and is packed at around 1.7KB compressed.

npm i @studiolambda/query

λ Query’s React bindings require React 19 as a peer dependency.

At its core, λ Query is a cache-backed data fetcher. Create an instance and start querying:

import { createQuery } from "@studiolambda/query";
const query = createQuery();
// Uses the key as the URL, fetches with the global fetch().
const posts = await query.query("/api/posts");
console.log(posts);

The first call to query.query("/api/posts") fetches the data and caches it. Subsequent calls within the expiration window return the cached value without a network request. After expiration, a stale value is returned immediately while a background refetch runs.

Wrap your application with QueryProvider and use the useQuery hook in your components:

import { QueryProvider, useQuery } from "@studiolambda/query/react";
import { createRoot } from "react-dom/client";
import { Suspense } from "react";
interface User {
name: string;
email: string;
}
function App() {
return (
<QueryProvider>
<Suspense fallback={<p>Loading...</p>}>
<UserCard />
</Suspense>
</QueryProvider>
);
}
function UserCard() {
const { data } = useQuery<User>("/api/user");
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}
createRoot(document.getElementById("root")!).render(<App />);

The useQuery hook suspends the component until data is available. Once resolved, data is always the fully loaded value — no null checks or loading states inside the component. React’s Suspense boundary handles the loading UI.

λ Query uses a two-cache system:

  1. Items cache — stores resolved data with an expiration timestamp. This is your “results” cache.
  2. Resolvers cache — stores in-flight fetch promises with abort controllers. This provides request deduplication — multiple query() calls for the same key while a fetch is in-flight share the same promise.

When you call query(key):

  1. If the key is not cached → fetch, cache the result, return it.
  2. If the key is cached and not expired → return the cached value.
  3. If the key is cached but expired → return the stale value immediately, trigger a background refetch. When the refetch completes, the cache updates and an event fires.
  4. If fresh: true → always refetch regardless of cache state.

This is the SWR (stale-while-revalidate) pattern. Your UI always has data to show, and it stays fresh automatically.

  • Configuration — Customize expiration, fetchers, staleness, and caches.
  • Data Fetching — The query() method in depth, aborting, and request deduplication.
  • Cache — Mutations, hydration, invalidation, and inspection.
  • Events — Subscribe to cache lifecycle events.
  • React — Components and hooks for React 19.