Bext Query (signals)

Reactive data cache on bext signals — TanStack-shaped API (createQuery, createMutation, invalidateQueries, dehydrate/hydrate) without React. The page prefetches /api/poll server-side, dehydrates the cache into the island marker, and the island hydrates its client cache before the first createQuery call — so first paint shows real data, no loading flash.

Output

Manual query (staleTime 5s)

status: success · fetching: no · stale: no

now = 2026-05-03T10:37:51.556Z

Live query (refetchInterval 3s)

tick =

src/components/QueryDemo.tsx

"use signals";
/** @jsxImportSource @bext-stack/framework/signals */
import {
    createQueryClient, createQuery, createMutation,
    type DehydratedState,
} from "@bext-stack/framework/query";
export default function QueryDemo(props: { dehydrate?: DehydratedState }) {
    const client = createQueryClient();
    client.hydrate(props.dehydrate);
    const poll = createQuery<{ now: string }>(client, {
        queryKey: ["poll"],
        queryFn: async ({ signal }) => (await fetch("/api/poll", { signal })).json(),
        staleTime: 5_000,
    });
    return (
        <div>
            <p>status: {poll.status.value} · fetching: {poll.isFetching.value ? "yes" : "no"}</p>
            <p>now = {poll.data.value?.now ?? "—"}</p>
            <button onClick={() => poll.refetch()}>refetch</button>
        </div>
    );
}

src/app/examples/query/page.tsx

import { signalsIsland } from "@bext-stack/framework/signals";
import { createQueryClient } from "@bext-stack/framework/query";
import QueryDemo from "../../../components/QueryDemo";
export default async function Page() {
    const client = createQueryClient();
    // Server-side prefetch — populates the cache before the island renders.
    await client.prefetchQuery({
        queryKey: ["poll"],
        queryFn: async () => 
            (await fetch("http://127.0.0.1:3032/api/poll")).json(),
    });
    return signalsIsland("QueryDemo", QueryDemo, {
        dehydrate: client.dehydrate(),
    });
}