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.

Outline
interface DataSource {
  execute(req: T): Promise<Record<string, any>[]>;
  addRef(): void;
  release(): Promise<void>;
}

The command type is T.

  • for SQL-based sources, T is usually string
  • for API-based sources, T can be a valid request object that api accepts
  • for custom sources, T can 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
    └── DuckDBWasmDataSource

That means the built-in sources expect SQL strings.

ClassWhat it does
DataSource<T>Base interface for executing a command and returning rows.
SqlDataSourceBase class for SQL-backed data sources that execute SQL strings.
DuckDBDataSourceSQL data source backed by DuckDB.
DuckDBWasmDataSourceSQL 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:

  1. User creates a data source instance.
const ds = InMemoryJsDataSource.create(rows);
  1. User passes the DataSource instance and schema to a data model.
const model = new InMemoryJsDataModel(schema, ds);
  1. The grid calls the data model functions which in turn calls dataSource.execute(...).
  2. 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

executefunction

Execute 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.

addReffunction

Increment the reference count. Call this when a new consumer starts using this datasource.

Type

() => void

releasefunction

Decrement the reference count. When the count reaches zero the underlying resources are released.

Type

() => Promise<void>

On this page