opengridv0.15.0

What sets it apart

How opengrid differs from a data grid written in JavaScript — one query engine in Rust, the same answer in the tab and on the server, and markup built for assistive technology.

Most data grids are a JavaScript component that draws rows and also sorts, filters and groups them. opengrid starts from the other end: it is a query engine, and the grid, the table and the pivot are what shows its answers. That one decision explains most of the differences on this page.

In short

A grid with its logic in JavaScript opengrid
Sorting, filtering, grouping In the component, on the main thread In an engine written in Rust, compiled to WebAssembly, in a worker if you like
Data too big for the browser A server mode: your backend answers, and you write the rules again The same engine inside opengrid-server, or the query split between server and tab
What “the same result” means Whatever the two implementations happen to do with NULL, text order or decimals Written down as rules and pinned by case files every path has to pass
What goes over the wire Often filter expressions or parameters your backend turns into SQL A query as a JSON document, checked against the fields and rows the token may see
Markup Usually nested divs with ARIA roles for every kind of table A native <table> where the data is read, role="grid" only where it is worked with
Framework A wrapper per framework, or one framework only Web Components, with thin components for React, Vue and Svelte
Pivot, grouping, export, tree Often part of a paid edition In the one package, under MIT or Apache-2.0

One engine instead of two implementations

A grid that sorts in JavaScript works until the data no longer fits in the browser. Then it switches to a server mode, and the sorting, filtering and grouping the component used to do have to exist a second time in your backend — with the same treatment of NULL, the same text order and the same rounding, or the reader sees rows move when the mode changes.

opengrid has one implementation. The element hands a query to a provider, and the same Rust code answers it wherever the provider points:

Focus
Two implementations of a query, or one
Two implementations of a query, or oneWhere a grid's sorting, filtering and grouping are written in JavaScript, a data set that outgrows the browser needs the same rules written a second time in the backend, and the two have to be kept in agreement by hand. In opengrid the element hands a query as JSON to one engine written in Rust. That engine is compiled to WebAssembly for the tab and natively into opengrid-server, where it can also hand parts of the query to PostgreSQL. Shared conformance cases check every path.Query logic in JavaScriptopengridGrid component → Server modeServer mode → Your backendYour backend → DatabaseGrid, table or pivot → One engine in RustOne engine in Rust → WebAssembly: compiled toOne engine in Rust → opengrid-server: compiled intoopengrid-server → PostgreSQLcompiled tocompiled intoGrid component – sorts, filters, groups in JSGrid componentsorts, filters, groups in JSServer mode – for data too big for the tabServer modefor data too big for the tabYour backend – the same rules, written againYour backendthe same rules, written againDatabaseDatabaseGrid, table or pivot – hands over a query as JSONGrid, table or pivothands over a query as JSONOne engine in Rust – one set of rulesOne engine in Rustone set of rulesWebAssembly – in the tab or a workerWebAssemblyin the tab or a workeropengrid-server – native, on your serveropengrid-servernative, on your serverPostgreSQL – or another connectorPostgreSQLor another connector

Source: crates/opengrid-engine, crates/opengrid-wasm, crates/opengrid-server, crates/opengrid-conformance

Show chart data
Data for Two implementations of a query, or one
StepKindLaneLeads to
Grid componentstartQuery logic in JavaScriptServer mode
Grid, table or pivotstartopengridOne engine in Rust
Server modestepQuery logic in JavaScriptYour backend
One engine in RuststepopengridWebAssembly (compiled to), opengrid-server (compiled into)
Your backendstepQuery logic in JavaScriptDatabase
WebAssemblystepopengrid
opengrid-serverstepopengridPostgreSQL
Databasedata storeQuery logic in JavaScript
PostgreSQLdata storeopengrid
Focus
Two implementations of a query, or one
Two implementations of a query, or oneWhere a grid's sorting, filtering and grouping are written in JavaScript, a data set that outgrows the browser needs the same rules written a second time in the backend, and the two have to be kept in agreement by hand. In opengrid the element hands a query as JSON to one engine written in Rust. That engine is compiled to WebAssembly for the tab and natively into opengrid-server, where it can also hand parts of the query to PostgreSQL. Shared conformance cases check every path.Query logic in JavaScriptopengridGrid component → Server modeServer mode → Your backendYour backend → DatabaseGrid, table or pivot → One engine in RustOne engine in Rust → WebAssembly: compiled toOne engine in Rust → opengrid-server: compiled intoopengrid-server → PostgreSQLcompiled tocompiled intoGrid component – sorts, filters, groups in JSGrid componentsorts, filters, groups in JSServer mode – for data too big for the tabServer modefor data too big for the tabYour backend – the same rules, written againYour backendthe same rules, written againDatabaseDatabaseGrid, table or pivot – hands over a query as JSONGrid, table or pivothands over a query as JSONOne engine in Rust – one set of rulesOne engine in Rustone set of rulesWebAssembly – in the tab or a workerWebAssemblyin the tab or a workeropengrid-server – native, on your serveropengrid-servernative, on your serverPostgreSQL – or another connectorPostgreSQLor another connector

Source: crates/opengrid-engine, crates/opengrid-wasm, crates/opengrid-server, crates/opengrid-conformance

Show chart data
Data for Two implementations of a query, or one
StepKindLaneLeads to
Grid componentstartQuery logic in JavaScriptServer mode
Grid, table or pivotstartopengridOne engine in Rust
Server modestepQuery logic in JavaScriptYour backend
One engine in RuststepopengridWebAssembly (compiled to), opengrid-server (compiled into)
Your backendstepQuery logic in JavaScriptDatabase
WebAssemblystepopengrid
opengrid-serverstepopengridPostgreSQL
Databasedata storeQuery logic in JavaScript
PostgreSQLdata storeopengrid
Focus
Two implementations of a query, or one
Two implementations of a query, or oneWhere a grid's sorting, filtering and grouping are written in JavaScript, a data set that outgrows the browser needs the same rules written a second time in the backend, and the two have to be kept in agreement by hand. In opengrid the element hands a query as JSON to one engine written in Rust. That engine is compiled to WebAssembly for the tab and natively into opengrid-server, where it can also hand parts of the query to PostgreSQL. Shared conformance cases check every path.Query logic in JavaScriptopengridGrid component → Server modeServer mode → Your backendYour backend → DatabaseGrid, table or pivot → One engine in RustOne engine in Rust → WebAssembly: compiled toOne engine in Rust → opengrid-server: compiled intoopengrid-server → PostgreSQLcompiled tocompiled intoGrid component – sorts, filters, groups in JSGrid componentsorts, filters, groups in JSServer mode – for data too big for the tabServer modefor data too big for the tabYour backend – the same rules, written againYour backendthe same rules, written againDatabaseDatabaseGrid, table or pivot – hands over a query as JSONGrid, table or pivothands over a query as JSONOne engine in Rust – one set of rulesOne engine in Rustone set of rulesWebAssembly – in the tab or a workerWebAssemblyin the tab or a workeropengrid-server – native, on your serveropengrid-servernative, on your serverPostgreSQL – or another connectorPostgreSQLor another connector

Source: crates/opengrid-engine, crates/opengrid-wasm, crates/opengrid-server, crates/opengrid-conformance

Show chart data
Data for Two implementations of a query, or one
StepKindLaneLeads to
Grid componentstartQuery logic in JavaScriptServer mode
Grid, table or pivotstartopengridOne engine in Rust
Server modestepQuery logic in JavaScriptYour backend
One engine in RuststepopengridWebAssembly (compiled to), opengrid-server (compiled into)
Your backendstepQuery logic in JavaScriptDatabase
WebAssemblystepopengrid
opengrid-serverstepopengridPostgreSQL
Databasedata storeQuery logic in JavaScript
PostgreSQLdata storeopengrid
Focus
Two implementations of a query, or one
Two implementations of a query, or oneWhere a grid's sorting, filtering and grouping are written in JavaScript, a data set that outgrows the browser needs the same rules written a second time in the backend, and the two have to be kept in agreement by hand. In opengrid the element hands a query as JSON to one engine written in Rust. That engine is compiled to WebAssembly for the tab and natively into opengrid-server, where it can also hand parts of the query to PostgreSQL. Shared conformance cases check every path.Query logic in JavaScriptopengridGrid component → Server modeServer mode → Your backendYour backend → DatabaseGrid, table or pivot → One engine in RustOne engine in Rust → WebAssembly: compiled toOne engine in Rust → opengrid-server: compiled intoopengrid-server → PostgreSQLcompiled tocompiled intoGrid component – sorts, filters, groups in JSGrid componentsorts, filters, groups in JSServer mode – for data too big for the tabServer modefor data too big for the tabYour backend – the same rules, written againYour backendthe same rules, written againDatabaseDatabaseGrid, table or pivot – hands over a query as JSONGrid, table or pivothands over a query as JSONOne engine in Rust – one set of rulesOne engine in Rustone set of rulesWebAssembly – in the tab or a workerWebAssemblyin the tab or a workeropengrid-server – native, on your serveropengrid-servernative, on your serverPostgreSQL – or another connectorPostgreSQLor another connector

Source: crates/opengrid-engine, crates/opengrid-wasm, crates/opengrid-server, crates/opengrid-conformance

Show chart data
Data for Two implementations of a query, or one
StepKindLaneLeads to
Grid componentstartQuery logic in JavaScriptServer mode
Grid, table or pivotstartopengridOne engine in Rust
Server modestepQuery logic in JavaScriptYour backend
One engine in RuststepopengridWebAssembly (compiled to), opengrid-server (compiled into)
Your backendstepQuery logic in JavaScriptDatabase
WebAssemblystepopengrid
opengrid-serverstepopengridPostgreSQL
Databasedata storeQuery logic in JavaScript
PostgreSQLdata storeopengrid

Between the two sits a planner: it asks the source what it can answer, sends that part to the server and finishes the rest in the tab. Moving a data set from the browser to a server is then a change of provider, not a second implementation — see Where queries run.

“The same answer” is written down

Whether NULL sorts first, whether 0.1 + 0.2 is 0.3, whether ä composed equals ä decomposed: every engine decides these somewhere, and most grids leave the decision to JavaScript’s defaults on one side and the database’s on the other. opengrid states them as numbered rules and pins each with case files — a query and the expected answer, as JSON.

54 query conformance cases, by the rule they pin
54 query conformance cases, by the rule they pinEach case is a JSON file with a query and the expected answer. The same files run against the native engine, the WebAssembly build in a browser, the hybrid path and PostgreSQL.02468CasesRuleS1 NULL in comparisons – Cases: 77S1 NULL in comparisonsS2 in lists – Cases: 22S2 in listsS3 NULL ordering – Cases: 33S3 NULL orderingS4 Binary collation – Cases: 22S4 Binary collationS5 Case-sensitive text – Cases: 22S5 Case-sensitive textS6 Multi-column sort, paging – Cases: 44S6 Multi-column sort, pagingS7 NaN and -0.0 – Cases: 44S7 NaN and -0.0S8 Exact decimals – Cases: 33S8 Exact decimalsS9 Dates and timestamps – Cases: 44S9 Dates and timestampsS10 NULL as a group – Cases: 22S10 NULL as a groupS11 Aggregates and NULL – Cases: 66S11 Aggregates and NULLS12 Aggregate result types – Cases: 44S12 Aggregate result typesS13 Unicode normalisation – Cases: 33S13 Unicode normalisationS14 Empty string is a value – Cases: 33S14 Empty string is a valueS15 Derived date parts – Cases: 55S15 Derived date parts

Source: crates/opengrid-conformance/cases/, counted when this site was built

Show chart data
Data for 54 query conformance cases, by the rule they pin
CategoryCases
S1 NULL in comparisons7
S2 in lists2
S3 NULL ordering3
S4 Binary collation2
S5 Case-sensitive text2
S6 Multi-column sort, paging4
S7 NaN and -0.04
S8 Exact decimals3
S9 Dates and timestamps4
S10 NULL as a group2
S11 Aggregates and NULL6
S12 Aggregate result types4
S13 Unicode normalisation3
S14 Empty string is a value3
S15 Derived date parts5
54 query conformance cases, by the rule they pin
54 query conformance cases, by the rule they pinEach case is a JSON file with a query and the expected answer. The same files run against the native engine, the WebAssembly build in a browser, the hybrid path and PostgreSQL.02468CasesRuleS1 NULL in comparisons – Cases: 77S1 NULL in comparisonsS2 in lists – Cases: 22S2 in listsS3 NULL ordering – Cases: 33S3 NULL orderingS4 Binary collation – Cases: 22S4 Binary collationS5 Case-sensitive text – Cases: 22S5 Case-sensitive textS6 Multi-column sort, paging – Cases: 44S6 Multi-column sort, pagingS7 NaN and -0.0 – Cases: 44S7 NaN and -0.0S8 Exact decimals – Cases: 33S8 Exact decimalsS9 Dates and timestamps – Cases: 44S9 Dates and timestampsS10 NULL as a group – Cases: 22S10 NULL as a groupS11 Aggregates and NULL – Cases: 66S11 Aggregates and NULLS12 Aggregate result types – Cases: 44S12 Aggregate result typesS13 Unicode normalisation – Cases: 33S13 Unicode normalisationS14 Empty string is a value – Cases: 33S14 Empty string is a valueS15 Derived date parts – Cases: 55S15 Derived date parts

Source: crates/opengrid-conformance/cases/, counted when this site was built

Show chart data
Data for 54 query conformance cases, by the rule they pin
CategoryCases
S1 NULL in comparisons7
S2 in lists2
S3 NULL ordering3
S4 Binary collation2
S5 Case-sensitive text2
S6 Multi-column sort, paging4
S7 NaN and -0.04
S8 Exact decimals3
S9 Dates and timestamps4
S10 NULL as a group2
S11 Aggregates and NULL6
S12 Aggregate result types4
S13 Unicode normalisation3
S14 Empty string is a value3
S15 Derived date parts5

The same files run against the native engine, the WebAssembly build in a headless browser, the hybrid path and PostgreSQL. A path that disagrees fails its test. Query semantics lists the rules.

Rust, and only one layer that knows a browser

The engine is Rust, not TypeScript, for three reasons that show up in use:

  • It runs outside the browser unchanged. The crates below the elements pull neither web-sys nor js-sys, so the code that answers a query in the tab is the code inside the server.
  • Types the data needs. 64-bit integers stay integers, decimals are exact, timestamps keep microseconds. In JavaScript all three pass through a float unless every step takes care.
  • Work off the main thread. The engine is one WebAssembly module that runs in a worker as it runs on the page; the live demo queries 100,000 rows that way.
Focus
The layers of opengrid, from the query model up
The layers of opengrid, from the query model upAt the bottom a query model: typed columns, a query as a JSON document and columnar results. On it the engine in Rust, which filters, sorts, groups and builds pivots and trees, with a planner that splits a query between a server and the tab. The engine is built for three targets: WebAssembly in the tab, native code inside opengrid-server, and SQL for PostgreSQL and SQLite. Only the top layer, the three Web Components, knows about a browser.Query model: No expression strings and no SQL: a query is data.Query modelTyped columnsQuery as JSONColumnar resultsNo expression strings and no SQL: aquery is data.Engine in Rust: Knows no browser: these crates pull neither web-sys nor js-sys.Engine in RustFilter, sort, groupPivotTreePlannerExportKnows no browser: these cratespull neither web-sys nor js-sys.Where it runsWhere it runsWebAssembly in the tabNative in opengrid-serverSQL for PostgreSQL and SQLiteWeb Components: The only layer that touches the DOM.Web Componentsopengrid-tableopengrid-gridopengrid-pivotThe only layer that touches theDOM.
The layers of opengrid, from the query model upAt the bottom a query model: typed columns, a query as a JSON document and columnar results. On it the engine in Rust, which filters, sorts, groups and builds pivots and trees, with a planner that splits a query between a server and the tab. The engine is built for three targets: WebAssembly in the tab, native code inside opengrid-server, and SQL for PostgreSQL and SQLite. Only the top layer, the three Web Components, knows about a browser.Query model: No expression strings and no SQL: a query is data.Query modelNo expression strings and no SQL: a query is data.Typed columnsQuery as JSONColumnar resultsEngine in Rust: Knows no browser: these crates pull neither web-sys nor js-sys.Engine in RustKnows no browser: these crates pull neither web-sysnor js-sys.Filter, sort, groupPivotTreePlannerExportWhere it runsWhere it runsWebAssembly in the tabNative in opengrid-serverSQL for PostgreSQL and SQLiteWeb Components: The only layer that touches the DOM.Web ComponentsThe only layer that touches the DOM.opengrid-tableopengrid-gridopengrid-pivot

Source: crates/*/Cargo.toml: only opengrid-web-core and opengrid-web-components depend on web-sys and js-sys

Show chart data
Data for The layers of opengrid, from the query model up
LevelLayerItemsNotes
1Query modelTyped columns, Query as JSON, Columnar resultsNo expression strings and no SQL: a query is data.
2Engine in RustFilter, sort, group, Pivot, Tree, Planner, ExportKnows no browser: these crates pull neither web-sys nor js-sys.
3Where it runsWebAssembly in the tab, Native in opengrid-server, SQL for PostgreSQL and SQLite
4Web Componentsopengrid-table, opengrid-grid, opengrid-pivotThe only layer that touches the DOM.
Focus
The layers of opengrid, from the query model up
The layers of opengrid, from the query model upAt the bottom a query model: typed columns, a query as a JSON document and columnar results. On it the engine in Rust, which filters, sorts, groups and builds pivots and trees, with a planner that splits a query between a server and the tab. The engine is built for three targets: WebAssembly in the tab, native code inside opengrid-server, and SQL for PostgreSQL and SQLite. Only the top layer, the three Web Components, knows about a browser.Query model: No expression strings and no SQL: a query is data.Query modelTyped columnsQuery as JSONColumnar resultsNo expression strings and no SQL: aquery is data.Engine in Rust: Knows no browser: these crates pull neither web-sys nor js-sys.Engine in RustFilter, sort, groupPivotTreePlannerExportKnows no browser: these cratespull neither web-sys nor js-sys.Where it runsWhere it runsWebAssembly in the tabNative in opengrid-serverSQL for PostgreSQL and SQLiteWeb Components: The only layer that touches the DOM.Web Componentsopengrid-tableopengrid-gridopengrid-pivotThe only layer that touches theDOM.
The layers of opengrid, from the query model upAt the bottom a query model: typed columns, a query as a JSON document and columnar results. On it the engine in Rust, which filters, sorts, groups and builds pivots and trees, with a planner that splits a query between a server and the tab. The engine is built for three targets: WebAssembly in the tab, native code inside opengrid-server, and SQL for PostgreSQL and SQLite. Only the top layer, the three Web Components, knows about a browser.Query model: No expression strings and no SQL: a query is data.Query modelNo expression strings and no SQL: a query is data.Typed columnsQuery as JSONColumnar resultsEngine in Rust: Knows no browser: these crates pull neither web-sys nor js-sys.Engine in RustKnows no browser: these crates pull neither web-sysnor js-sys.Filter, sort, groupPivotTreePlannerExportWhere it runsWhere it runsWebAssembly in the tabNative in opengrid-serverSQL for PostgreSQL and SQLiteWeb Components: The only layer that touches the DOM.Web ComponentsThe only layer that touches the DOM.opengrid-tableopengrid-gridopengrid-pivot

Source: crates/*/Cargo.toml: only opengrid-web-core and opengrid-web-components depend on web-sys and js-sys

Show chart data
Data for The layers of opengrid, from the query model up
LevelLayerItemsNotes
1Query modelTyped columns, Query as JSON, Columnar resultsNo expression strings and no SQL: a query is data.
2Engine in RustFilter, sort, group, Pivot, Tree, Planner, ExportKnows no browser: these crates pull neither web-sys nor js-sys.
3Where it runsWebAssembly in the tab, Native in opengrid-server, SQL for PostgreSQL and SQLite
4Web Componentsopengrid-table, opengrid-grid, opengrid-pivotThe only layer that touches the DOM.

The server is a library, not a product: your program builds it, hands it PostgreSQL, SQLite or a connector of your own, and stays in charge of authentication (Connectors).

The browser never sends SQL

A query is a JSON document with a fixed shape, not a string a backend has to parse or interpolate. opengrid-server checks it before anything reaches the database: a token maps to a row filter that is attached to every query and cannot be switched off, and a field outside the allowed list does not exist for that client — the answer is the same error as a typo. There is nothing to inject into.

A table where a table is meant

The usual grid is one component for every case, built from divs and described to assistive technology through ARIA. opengrid has three elements because the cases differ:

  • <opengrid-table> and <opengrid-pivot> are native <table> elements: a screen reader’s table navigation, the browser’s find and ordinary copy and paste work as they do on any page.
  • <opengrid-grid> uses role="grid" because it takes over the arrow keys for selecting and editing — a promise that would be wrong for a report.

Every feature is operated from the keyboard, state changes go through one live region, and the end-to-end suite runs axe-core over the elements in each state it reaches. What that verifies and what is still open is listed, without rounding up, in Accessibility.

The same structure makes the grid readable for programs: roles, names and row counts are in the accessibility tree, and schema, query and view are JSON. The MCP adapter uses nothing but that public surface.

Everything in one package

Grouping with totals, the pivot, trees, export as CSV, JSON and (from the server) XLSX, editing and virtualization are in @casoon/opengrid and the crates beside it, under MIT or Apache-2.0. There is no second edition with the features an application ends up needing.

What you give up

This design has a price, and it is better read here than found later:

  • The engine is a WebAssembly module the page has to load. A table of fifty rows does not need it.
  • There is no expression language: a column is stored or derived from a date part, never calculated in the grid.
  • A pivot has hard limits and answers with an error over them, never with a truncated result.
  • It is pre-1.0. The API is written down and tested, and may still change.

The full list is in the Introduction.

Edit this page on GitHub · Docs for v0.15.0