vastgrid/core and vastgrid/server
vastgrid/core is the grid without React: the store that holds its state, the client of the messages and the
transports that carry them to an engine. It runs in browsers, Web Workers and servers. vastgrid/server reads
the first rows of a table on a server, for server rendering. React code imports the same names from vastgrid,
which re-exports vastgrid/core; workers and servers import from vastgrid/core.
§4 of the React specification is
the source of this page.
The store
Section titled “The store”import { createGridStore, httpTransport } from "vastgrid/core";
const store = createGridStore({ transport: httpTransport("/api", { events: "/api/events" }), table: "orders.csv" });const disconnect = store.connect();createGridStore(options) gives a GridStore (also exported as a class, new GridStore(options)). DataGrid
makes one when it is not given one.
GridStoreOptions
Section titled “GridStoreOptions”| Option | Type | Default | Description |
|---|---|---|---|
transport |
Transport |
(required) | Where the engine is. |
table |
string |
(required) | The ref of the table to open (§8.2). |
open |
OpenOptions |
The read options of open: sheet, headerRow, keepEmptyRows, keepEmptyColumns. |
|
document |
GridDocument |
the empty document | The document to start with (§5.1). |
session |
GridSession |
the empty session | The session to start with (§5.2). |
initial |
InitialView |
Rows read on a server (readInitialView), shown before the first read. |
|
cacheBytes |
number |
CACHE_BYTES (64 MiB) |
Bytes of rows to keep. |
Lifecycle
Section titled “Lifecycle”- Making a store has no side effects: it opens nothing, starts no timer and subscribes to nothing.
connect()opens the table (open), follows the engine’s events and reads the view of the document’s plan. The function it returns undoes all of it and closes the table. A store connects again after it disconnects, as React’s Strict Mode does in development.- A disconnected store keeps its last snapshot and reads nothing.
Methods
Section titled “Methods”| Method | Description |
|---|---|
getSnapshot() |
The state now, a GridSnapshot. The same object until the state changes. |
subscribe(listener) |
Calls listener when the snapshot changes. Returns the function that stops. |
on(event, listener) |
Follows "document" and "session" changes (reported also when they are controlled) and "error"s. Returns the function that stops. |
connect() |
Opens the table and starts reading. Returns the function that disconnects. |
dispatch(command, options?) |
Applies a command (Commands). Resolves to a CommandOutcome and never rejects. The same function for the life of the store. |
dispatchSync(command, options?) |
Applies a command at once, as a copy event needs. A command that needs rows that are not read fails with not_loaded, and its requests are sent. |
control(part, controlled) |
Makes "document" or "session" controlled: commands report what they make of it, and only setDocument or setSession show it. |
setDocument(document), setSession(session) |
Shows a document or a session given by the host: a controlled prop, a saved view, an agent. |
cell(row, column) |
The value of a cell of the view on screen; undefined while it is not read. |
isCut(row, column) |
Whether the engine cut a cell’s text to its limit (§8.4). |
fullValue(row, column, signal?) |
The whole value of a cell, also when its text was cut, and its column: { value, column }. |
rowKey(row), rowId(row) |
The key (§5.3) and the row ID of a row of the view on screen. |
getRowsVersion(), subscribeRows(listener) |
A number that changes when rows of the view on screen arrive or leave, and the way to follow it. Rows are not in snapshots. |
setViewport({ top, rows, columns }) |
Tells the store what is on screen, so that it reads those rows and a page around them. |
ensure(start, end, columns) |
Reads rows [start, end) of columns if they are not read. |
summarize(ranges, columns, signal?) |
Counts and sums cells of ranges of rows: { count, numericCount, sum, min, max, average }. |
values(column, { search?, limit? }?, signal?) |
A column’s values with how many rows have each, the most common first, at most limit (1,000): { values: { value, count }[], truncated }. |
aggregate(measures, signal?) |
Measures (§4.5) over the rows of the document’s plan: { values, columns }. Fails with invalid_plan for a plan that groups rows. |
find(text, { from, direction?, caseSensitive? }, signal?) |
The next cell after from (before it with direction −1) whose text contains text, in the displayed columns, wrapping around: { row, column } or null. |
retryWrite() |
Sends the last write that failed (failedWrite) again; after an edit_conflict, over what the cells hold now. |
discardWrite() |
Forgets the failed write: its cells show what the engine holds. |
reload() |
Reads the table’s file as it is now, for a file that changed on disk while it had written cells (fileChanged). |
exportUrl(target) |
Where the file of an export without a target is downloaded, from the transport, or null. |
downloadExport(target) |
Downloads that file through the transport (Transport.downloadExport); false when it has none. |
exportName(format) |
A file name for an export answered as data: the table’s name with the format’s extension. |
Snapshots
Section titled “Snapshots”interface GridSnapshot { document: GridDocument; // §5.1 session: GridSession; // §5.2 table: TableInfo | null; // null until the table is open view: ViewInfo | null; // the view on screen displayed: DisplayedColumn[]; // the columns on screen, in order pending: { plan: Plan } | null; // a plan whose view is read to replace the one on screen status: "idle" | "opening" | "ready" | "error"; error: GridError | null; failedWrite: FailedWrite | null; // the last write that failed totals: { values: Record<string, JsonValue>; columns: Record<string, Column> } | null; // the totals row}- A snapshot never changes. A command keeps every member of the document and the session that it does not change,
so selectors stay cheap: moving the active cell gives a new session with the same
document,checkedandhistory. TableInfois the answer ofopen:table(the handle),revision,status(loading,ready,error),progress,rowCount(null while unknown),readableRows,columns,capabilities(Engines), and when they applyformat,byteSize,notes,sheets,sheet,errorandfileChanged.ViewInfois a view’shash,status(building,ready),rowCount,rowCountExact,readableRows,columnsandedits.FailedWriteholds thecommand, theupdates, theerrorandretry:"retry","overwrite"(after anedit_conflict) or null.
Dispatching
Section titled “Dispatching”const outcome = await store.dispatch({ command: "sort.set", keys: [{ column: "total", direction: "desc" }] });if (!outcome.ok) console.warn(outcome.error.code, outcome.error.message);- The snapshot changes at once and subscribers are told, so that a key press shows in the same frame.
- The requests that the command gives are sent. The core’s own commands (§6.7) are applied as their answers arrive.
- When the plan changed, the store reads the new view while the old one stays on screen with
pendingset, then shows the new rows in one step. A newer plan cancels the reads of an older one. - A command that fails with
not_loadedreads the rows it names and is applied again.
| Type | Definition |
|---|---|
DispatchOptions |
{ clipboard?: { text?: string; html?: string } }: what edit.paste pastes. |
CommandOutcome |
{ ok: true; output?: unknown } or { ok: false; error: CommandError | GridError }. A command that fails changes nothing. |
copy gives { tsv, html, markdown } as its output, and edit.paste gives { cutRows, cutColumns }
(CommandOutputs of vastgrid-spec). Failures that belong to no command (a read that fails, a table that cannot
be read) go to snapshot.error and to on("error").
Errors
Section titled “Errors”GridError is an Error with a code, and when they apply a reason, a path (a JSON Pointer), details and
the op of the request. A command that cannot be applied fails with a CommandError of vastgrid-spec instead,
whose code is invalid_command (Commands).
code |
When | HTTP |
|---|---|---|
invalid_request |
a message that does not follow §8, with path and reason |
400 |
invalid_plan |
a plan that does not check (§4.7), with path and reason |
400 |
table_not_found |
the table is closed or unknown | 404 |
not_ready |
the table is still loading and the request needs all of it | 409 |
revision_changed |
the table changed since the request’s revision | 409 |
edit_conflict |
a cell no longer holds the value a write expected | 409 |
file_changed |
the file changed on disk since it was read | 409 |
read_only |
a write to a cell that cannot be written | 403 |
not_allowed |
a ref or target outside what the host allows |
403 |
invalid_value |
a value that does not fit its column, or one a block cannot hold | 422 |
too_many_columns |
a pivot with too many columns (§4.6) | 422 |
save_unsupported |
the engine cannot save this table | 422 |
load_failed |
the table could not be read | 422 |
export_too_large |
a result that does not fit the format (Excel: 1,048,575 rows, 16,384 columns) | 413 |
cancelled |
the request was cancelled | 499 |
internal |
anything else | 500 |
§8.7 is the list; ERROR_STATUS of
vastgrid-spec maps the codes to the HTTP statuses.
Transports
Section titled “Transports”A transport carries the messages of §8 to an engine. Blocks of rows travel as bytes (the structured encoding) or, over HTTP, attached after the JSON.
| Transport | Engine |
|---|---|
localTransport(engine) |
In the same thread, such as the memory engine of vastgrid-js. |
workerTransport(worker) |
In a Web Worker where serveEngine(engine, self) answers, or behind anything that carries messages as a worker does (WorkerLike: a MessagePort, a VS Code webview, a notebook widget). Buffers are transferred. Its close() stops listening. |
httpTransport(url, options?) |
On a server, such as createHttpHandler of vastgrid-duckdb. Each request is a POST, and rows and export come back with their blocks attached. Aborting a request aborts its HTTP request. |
serverFunctionTransport(action) |
Behind a "use server" function that passes each request to an engine. The function must check the request (checkRequest of vastgrid-spec). |
import { serveEngine } from "vastgrid/core";import { memoryEngine } from "vastgrid-js";
serveEngine(memoryEngine({ tables: { people: { columns, rows } } }), self);import { workerTransport } from "vastgrid/core";
const transport = workerTransport(new Worker(new URL("./worker.ts", import.meta.url), { type: "module" }));HttpTransportOptions
Section titled “HttpTransportOptions”| Option | Type | Default | Description |
|---|---|---|---|
fetch |
typeof fetch |
the global fetch |
The function that sends requests. |
headers |
Record<string, string> |
Headers sent with every request, such as an authorization. | |
events |
string |
The URL of the Server-Sent Events that carry the engine’s events. Without it, the grid does not hear of writes made by others. Requests wait until the stream is open (or fails), so that no event of a table the grid opens is missed. | |
exports |
string |
<url>/exports/ |
The URL under which exports without a target are downloaded. |
Transport and Engine
Section titled “Transport and Engine”A host with a channel of its own implements Transport: the MCP App sends each request as a call of its tool.
interface Transport { send(request: RequestMessage, options: { signal?: AbortSignal }): Promise<unknown>; subscribe?(listener: (event: unknown) => void): () => void; cancels?: "message" | "signal"; // how a request is cancelled: a `cancel` message (default) or the signal exportUrl?(target: string): string; downloadExport?(target: string): Promise<void>; // where a link to exportUrl cannot download, as in an MCP App}
interface Engine { handle(message: unknown): Promise<unknown>; subscribe?(listener: (event: unknown) => void): () => void;}
interface WorkerLike { postMessage(message: unknown, transfer?: Transferable[]): void; addEventListener(type: "message", listener: (event: MessageEvent) => void): void; removeEventListener(type: "message", listener: (event: MessageEvent) => void): void; start?(): void;}RequestMessage is { v: 2, id, op, … } (§8.1). Engines describes the two that come with
vastgrid, and Your own engine how to write one.
GridClient and RequestIds
Section titled “GridClient and RequestIds”new GridClient(transport)sends requests with ids of its own and checks every response and event (checkResponse,checkEvent):request(op, members, signal?)resolves to the result or rejects with aGridError, and sendscancelwhen the signal aborts. The store uses one; a host rarely needs it.new RequestIds(engine).client()gives each of several clients of one engine aHandlerwhose request ids are unique in the engine, and gives the answers their own ids back, so that one client’scancelnever stops another’s request. The MCP App and the VS Code extension serve several grids from one engine this way.
Constants
Section titled “Constants”| Name | Value | Meaning |
|---|---|---|
BLOCK_ROWS |
256 |
Rows in a block that the store reads and keeps. |
CACHE_BYTES |
64 * 1024 * 1024 |
The bytes of rows a store keeps by default (cacheBytes). |
vastgrid/server
Section titled “vastgrid/server”readInitialView(transport, options) opens a table, reads the view of the document’s plan and its first rows, and
closes the table. Its result is an InitialView: { table, plan, rows }, with the block of rows as a
Uint8Array, which Server Components pass to client components. Given to DataGrid as initial, or to the store, the first frame draws
these rows; given as a promise, the page streams and the grid suspends until it resolves. It imports no React.
| Option | Type | Default | Description |
|---|---|---|---|
table |
string |
(required) | The ref of the table to open. |
open |
OpenOptions |
The read options of open. |
|
document |
GridDocument |
the empty document | The document the grid starts with; its plan is the view read. |
rows |
number |
100 |
The rows to read. |
signal |
AbortSignal |
Cancels the reads. |
// app/orders/page.tsx, a Server Componentimport { httpTransport } from "vastgrid/core";import { readInitialView } from "vastgrid/server";import { OrdersGrid } from "./orders-grid"; // a client component that renders <DataGrid initial={initial} … />
export default function Page() { const initial = readInitialView(httpTransport("http://localhost:8080/api"), { table: "orders.csv" }); return <OrdersGrid initial={initial} />;}See Files on a server and §7 of the React specification.
A store without React
Section titled “A store without React”The store is the whole grid but the screen: commands, selection, undo, writes and exports all work in it. The MCP App keeps each grid as such a store on its server, applies the agent’s commands to it, and reads the state back for the agent:
import { createGridStore, GridError, type GridSnapshot, type GridStore, localTransport } from "vastgrid/core";import { DuckDBEngine } from "vastgrid-duckdb";
const engine = await DuckDBEngine.create({ allowedDirectories: ["/srv/data"], tempDirectory: "/tmp/vastgrid" });const store = createGridStore({ transport: localTransport(engine), table: "orders.csv" });const disconnect = store.connect();
// Waits until the table is open and the view of the document is on screen.function settled(store: GridStore): Promise<GridSnapshot> { return new Promise((resolve, reject) => { const check = () => { const snapshot = store.getSnapshot(); if (snapshot.status === "error") { reject(snapshot.error ?? new GridError("load_failed", snapshot.table?.error ?? "The table could not be read.")); } else if (snapshot.status === "ready" && snapshot.view && !snapshot.pending) { resolve(snapshot); } else { return; } stop(); }; const stop = store.subscribe(check); check(); });}
await settled(store);await store.dispatch({ command: "sort.set", keys: [{ column: "total", direction: "desc" }] });await settled(store);store.ensure(0, 20, ["id", "total"]);Rows arrive after ensure; subscribeRows tells when, and cell(row, column) reads them. disconnect() closes
the table; engine.dispose() closes the engine.