DataSource
Connects to a data source and executes downstream commands to fetch data.
DataSource<T> is the interface for running a command against a real data source.
interface DataSource {
execute(req: T): Promise<Record<string, any>[]>;
addRef(): void;
release(): Promise<void>;
}The command type is T.
- for SQL-based sources,
Tis usuallystring - for API-based sources,
Tcan be a valid request object that api accepts - for custom sources,
Tcan be any command shape your source accepts
Example:
Run a SQL query against the data source
await sqlDataSource.execute("SELECT city, revenue FROM sales");Send a request to an API endpoint with given payload
await apiDataSource.execute({
endpoint: "/query",
method: "GET",
body: { groupBy: ["city"] },
});A single DataSource instance can be shared by multiple consumers. Use addRef() and release() to manage that lifecycle. See the API Reference for details.
Responsibilities
DataSource only does three things:
- accepts a command
- runs it against the source
- returns rows
It does not decide which command to run. The caller decides that and passes the command to execute(...).
What "downstream" means here
Downstream code means code that uses the data source, usually a data model. That code prepares the command. DataSource executes it.
Example flow:
const sql = buildQueryFromState(gridState);
const rows = await dataSource.execute(sql);Built-in implementations
This package provides SQL-based implementations:
DataSource<T>
└── SqlDataSource
├── DuckDBDataSource
└── DuckDBWasmDataSourceThat means the built-in sources expect SQL strings.
| Class | What it does |
|---|---|
DataSource<T> | Base interface for executing a command and returning rows. |
SqlDataSource | Base class for SQL-backed data sources that execute SQL strings. |
DuckDBDataSource | SQL data source backed by DuckDB. |
DuckDBWasmDataSource | SQL data source backed by DuckDB WASM for browser use. |
Implementing your own DataSource
The library includes built-in API-based and browser WASM SQL-based data sources for common cases. If you need something else, implement DataSource<T> yourself or extend an existing data source.
Example: an in-memory JavaScript data source.
import { DataSource } from "grid";
interface DataSourceConfig {
maxRows: number;
}
const defaultConfig: DataSourceConfig = {
maxRows: 10 ** 6,
};
type InMemoryCommand = {
filter?: (row: Record<string, any>) => boolean;
// more operators
};
class InMemoryJsDataSource implements DataSource<InMemoryCommand> {
private rows: Record<string, any>[];
private refCount = 1;
private config: DataSourceConfig;
private constructor(rows: Record<string, any>[], config: DataSourceConfig) {
this.rows = rows;
this.config = config;
}
static create(rows: Record<string, any>[]): DataSource<InMemoryCommand> {
return new InMemoryJsDataSource(rows, defaultConfig);
}
addRef(): void {
this.refCount++;
}
async execute(command: InMemoryCommand): Promise<Record<string, any>[]> {
const rows = command.filter ? this.rows.filter(command.filter) : this.rows;
return rows.slice(0, this.config.maxRows);
}
async release(): Promise<void> {
this.refCount--;
if (this.refCount <= 0) {
this.rows = [];
}
}
}Recommendation
We recommend using DuckDBWasmDataSource to load in-memory data. It is more exhaustive and works well with complex grids out of the box with little extra effort.
Usage
DataSource<T> is an interface. You do not create it directly. Instead, create an instance of a concrete data source class and pass that instance to a data model.
Typical flow:
- User creates a data source instance.
const ds = InMemoryJsDataSource.create(rows);- User passes the
DataSourceinstance and schema to a data model.
const model = new InMemoryJsDataModel(schema, ds);- The grid calls the data model functions which in turn calls
dataSource.execute(...). - The data source returns rows.
API Reference
Generic interface for query execution with ref-counted lifecycle.
Multiple consumers (e.g. pivot grid and flat table) can share a single
DataSource instance. Use addRef / release to manage ownership.
Prop
Type
executefunctionExecute a command against the data source and return the result.
The shape of req depends on the data source implementation:
- SQL-based sources: a SQL query string (e.g.
"SELECT city, revenue FROM sales") - API-based sources: a request object describing the endpoint and payload
- Custom sources: whatever command shape your source accepts
Type
(req: T) => Promise<Record<string, any>[]>
Parameters
req -
The command to execute. Its type is determined by T.
Returns
An array of key-value records. The layout (row-major vs column-major) is up to the implementation — the data model consuming the result handles the transformation.
addReffunctionIncrement the reference count. Call this when a new consumer starts using this datasource.
Type
() => void
releasefunctionDecrement the reference count. When the count reaches zero the underlying resources are released.
Type
() => Promise<void>