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.
Honest scope. AT-1 is a read-optimized, verified-lossless source, and TanStack DB is built around live, optimistic mutations + sync. The strong fit today is read / query collections— large reference or analytics datasets delivered as a tiny compressed file and queried reactively. AT-1's appendable-table codec can back a writable collection, but we don't pitch it as a high-frequency optimistic-write sync backend yet.

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
}