capitalsix-data-table
v0.1.3
Published
A lightweight, framework-agnostic in-memory data table with typed CRUD operations: insert, update, upsert, delete, sort, and query rows.
Downloads
570
Readme
DataTable
A lightweight, framework-agnostic in-memory data table with fully typed CRUD operations. Use it anywhere you need to manage a collection of objects: plain TypeScript, Node.js, React state handlers, etc.
Features
- Typed rows — generic over any object type
T - Insert with optional duplicate-key guard
- Update by predicate — via mutation function, replacement object, or keyed update array
- Upsert — insert new rows or update existing ones in a single call
- Delete by predicate, returns deleted rows
- Sort in-place with a custom comparator
- Query all rows or filter with a predicate
- Bulk replace via
setRowsfor full re-initialisation - Zero runtime dependencies
Install
npm install capitalsix-data-tableQuick Start
import { DataTable } from 'capitalsix-data-table';
type User = { id: number; name: string; age: number };
const table = new DataTable<User>([
{ id: 1, name: 'Alice', age: 30 },
{ id: 2, name: 'Bob', age: 25 },
]);
const users = table.getRows();
// [{ id: 1, name: 'Alice', age: 30 }, { id: 2, name: 'Bob', age: 25 }]API
constructor(initialRows?: T[])
Creates a new table, optionally pre-populated with rows.
const empty = new DataTable<User>();
const seeded = new DataTable<User>([{ id: 1, name: 'Alice', age: 30 }]);getRows(predicate?: (row: T) => boolean): T[]
Returns all rows, or only the rows matching the predicate.
const all = table.getRows();
const young = table.getRows(u => u.age < 30);setRows(rows: T[]): void
Replaces all current rows with the provided array. Useful for bulk re-initialisation (e.g. after loading fresh data from an API).
table.setRows([
{ id: 10, name: 'Carol', age: 28 },
{ id: 11, name: 'Dave', age: 35 },
]);insertRows(rows: T[], primaryKey?: keyof T): void
Appends rows to the table. When primaryKey is provided, the method throws if any incoming key already exists, preventing accidental duplicates.
// Insert without key guard
table.insertRows([{ id: 3, name: 'Eve', age: 22 }]);
// Insert with duplicate-key guard
table.insertRows([{ id: 4, name: 'Frank', age: 40 }], 'id');
// Throws: "Duplicate primary key value found in existing rows for key: id"
table.insertRows([{ id: 1, name: 'Alice again', age: 99 }], 'id');updateRows(predicate, update, primaryKey?): T[]
Updates all rows matching predicate and returns them (already reflecting the changes).
When update is an object or array, primaryKey is required.
Mutation-function style — receives each matched row and mutates it directly:
table.updateRows(
u => u.id === 1,
u => { u.age += 1; },
);Replacement-object style — merges the object onto each matched row while preserving the primary key:
table.updateRows(
u => u.id === 2,
{ id: 0, name: 'Bob Updated', age: 26 },
'id', // primaryKey is required; its value is preserved from the original row
);Keyed-array style — for each matched row, finds the update item with the same primaryKey and merges it:
table.updateRows(
u => u.id >= 2,
[
{ id: 2, name: 'Bob Updated', age: 26 },
{ id: 3, name: 'Carol Updated', age: 29 },
],
'id',
);
// matched rows without a corresponding key in the update array are left unchangedupsertRows(rows: T[], primaryKey: keyof T): DataTableUpsertResult<T>
Inserts rows whose key does not yet exist; updates rows whose key already exists. Returns an object { inserted, updated } so callers can react to each outcome.
const result = table.upsertRows(
[
{ id: 1, name: 'Alice', age: 31 }, // key exists -> updated
{ id: 5, name: 'Grace', age: 27 }, // new key -> inserted
],
'id',
);
console.log(result.updated.length); // 1
console.log(result.inserted.length); // 1deleteRows(predicate: (row: T) => boolean): T[]
Removes all rows that match the predicate and returns them.
const removed = table.deleteRows(u => u.age < 25);
console.log(removed); // rows that were deletedsortRows(compareFn: (a: T, b: T) => number): void
Sorts rows in place using the provided comparator (same contract as Array.prototype.sort).
// Sort by age ascending
table.sortRows((a, b) => a.age - b.age);
// Sort by name alphabetically
table.sortRows((a, b) => a.name.localeCompare(b.name));Examples
Build a simple in-memory cache
import { DataTable } from 'capitalsix-data-table';
type Product = { sku: string; name: string; stock: number };
const cache = new DataTable<Product>();
// Load initial catalogue
cache.setRows(await fetchProducts());
// Reduce stock for a purchased item
cache.updateRows(
p => p.sku === 'ABC-123',
p => { p.stock -= 1; },
);
// Sync with latest server data, inserting new products and updating changed ones
const { inserted, updated } = cache.upsertRows(await fetchProducts(), 'sku');
console.log(`Synced: ${inserted.length} new, ${updated.length} updated`);Filter and sort for display
const inStock = table.getRows(p => p.stock > 0);
// Clone before sorting if you do not want to mutate the table order.
// Use sortRows() when you *do* want the table itself sorted.
const sorted = [...inStock].sort((a, b) => a.name.localeCompare(b.name));Use inside a React reducer
import { DataTable } from 'capitalsix-data-table';
type State = { table: DataTable<User> };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'UPSERT_USERS':
state.table.upsertRows(action.payload, 'id');
return { ...state }; // shallow clone to trigger re-render
case 'DELETE_USER':
state.table.deleteRows(u => u.id === action.id);
return { ...state };
default:
return state;
}
}Typed interfaces for dependency injection
DataTable implements two interfaces you can use for narrowing in function signatures:
import type { IDataTable, IDataTableInitial } from 'capitalsix-data-table';
// Read/write CRUD — no bulk replace
function processUsers(table: IDataTable<User>) {
table.insertRows([{ id: 99, name: 'Test', age: 0 }], 'id');
}
// Bulk replace only — used during initialisation
function loadUsers(table: IDataTableInitial<User>, users: User[]) {
table.setRows(users);
}Development
npm install
npm test
npm run typecheck
npm run build