Docs · Integrations
TanStack DB & Query + AT-1
Hydrate a reactive TanStack DB collection — or a plain TanStack Query queryFn — straight from a compressed, queryable .at1. Ship a big dataset as a small file, decompress it into a collection, then query it reactively in the browser.
How the layers stack
AT-1 one compressed, queryable .at1 (the source — pushdown, range-GET, verified)
└─ TanStack Query / DB hydrate a collection from it; useLiveQuery for reactive views
└─ TanStack Virtual render the result, millions of rows, on demand
One static file on a CDN backs a fully reactive, virtualized grid — no database server.1 · The source (full rows, any type)
One function loads rows from the .at1through AT-1's SQL endpoint — only the touched blocks are read.
// at1-source.ts — load rows from an .at1 through AT-1's SQL endpoint.
// Any column type, and only the touched blocks are read server-side.
export type Row = Record<string, unknown>;
export async function loadFromAt1(where = "1=1"): Promise<Row[]> {
const r = await fetch("https://your-at1-cloud/sql", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer <token>" },
body: JSON.stringify({ sql: `SELECT * FROM data/trades.csv WHERE ${where}` }),
});
const { columns, rows } = await r.json();
return rows.map((row: unknown[]) =>
Object.fromEntries(columns.map((c: string, i: number) => [c, row[i]])));
}2a · Just TanStack Query
If you only need fetching + caching, that queryFn is the whole integration:
// Plain TanStack Query — if you just need fetching/caching, no collection layer:
import { useQuery } from "@tanstack/react-query";
import { loadFromAt1 } from "./at1-source";
const { data, isLoading } = useQuery({
queryKey: ["trades"],
queryFn: () => loadFromAt1("price BETWEEN 4200000 AND 4300000"),
});2b · A TanStack DB collection
For the reactive store, wrap the same loader in a collection via the official query-collection options:
npm install @tanstack/react-db @tanstack/query-db-collection @tanstack/query-core
// at1-collection.ts — an AT-1-backed TanStack DB collection.
import { createCollection } from "@tanstack/react-db";
import { queryCollectionOptions } from "@tanstack/query-db-collection";
import { QueryClient } from "@tanstack/query-core";
import { loadFromAt1, type Row } from "./at1-source";
const queryClient = new QueryClient();
export const tradesCollection = createCollection(
queryCollectionOptions<Row>({
queryKey: ["trades"],
queryFn: () => loadFromAt1(), // <- hydrate the whole collection from the compressed .at1
queryClient,
getKey: (row) => row.id as number,
})
);3 · useLiveQuery — reactive views
After the one-time hydrate, filters/sorts/joins recompute incrementally on the client — no further round-trips.
// useLiveQuery — reactive, incrementally-recomputed views over the collection.
// After the one-time hydrate, filtering/sorting/joining happen client-side, no round-trips.
import { useLiveQuery } from "@tanstack/react-db";
import { tradesCollection } from "./at1-collection";
function BigTrades() {
const { data } = useLiveQuery((q) =>
q.from({ t: tradesCollection })
.where(({ t }) => t.qty > 1000)
.orderBy(({ t }) => t.ts, "asc")
);
return <div>{data.length} large trades</div>;
}No backend: hydrate from the browser WASM
For numeric columns you can skip the server entirely and decode the collection from the .at1 in the browser (the engine behind /try):
// No backend at all? For NUMERIC columns, hydrate the collection straight from the
// browser query WASM (same engine as the TanStack Virtual demo) — the file never leaves
// the client. (Text/decimal need the /sql path above; this decodes integer columns.)
async function loadFromAt1Wasm(url: string): Promise<Row[]> {
const m = await (window as any).AT1Module({ locateFile: (f: string) => `/wasm/${f}` });
m.FS.writeFile("/c.at1", new Uint8Array(await (await fetch(url)).arrayBuffer()));
const h = Number(m.ccall("at1q_open", "number", ["string"], ["/c.at1"]));
const ncols = Number(m.ccall("at1q_ncols", "number", ["number"], [h]));
const nrg = Number(m.ccall("at1q_nrowgroups", "number", ["number"], [h]));
const cols = ["id", "price", "qty", "ts"]; // names for your file
const out: Row[] = [];
for (let g = 0; g < nrg; g++) {
const cap = Number(m.ccall("at1q_rows_in_group", "number", ["number", "number"], [h, g]));
const data: number[][] = [];
for (let c = 0; c < ncols; c++) {
const buf = m._malloc(8 * cap);
const n = Number(m.ccall("at1q_decode_int", "number",
["number","number","number","number","number"], [h, g, c, buf, cap]));
const arr: number[] = [];
for (let k = 0; k < n; k++) arr.push(Number(m.getValue(buf + 8 * k, "i64")));
m._free(buf); data.push(arr);
}
for (let r = 0; r < cap; r++) out.push(Object.fromEntries(cols.map((c, i) => [c, data[i][r]])));
}
return out; // hand this to queryCollectionOptions' queryFn instead of loadFromAt1
}Compose with TanStack Virtual
Feed the live-query result into the virtualizer for the full stack — reactive query + windowed render over a compressed source:
// The full trio: AT-1 source -> TanStack DB reactive query -> TanStack Virtual render.
import { useVirtualizer } from "@tanstack/react-virtual";
function Grid() {
const { data } = useLiveQuery((q) => q.from({ t: tradesCollection }).where(({ t }) => t.qty > 1000));
const parentRef = useRef<HTMLDivElement>(null);
const rv = useVirtualizer({ count: data.length, getScrollElement: () => parentRef.current, estimateSize: () => 30 });
// ...render rv.getVirtualItems().map(vi => data[vi.index]) — see the TanStack Virtual guide
}