npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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 setRows for full re-initialisation
  • Zero runtime dependencies

Install

npm install capitalsix-data-table

Quick 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 unchanged

upsertRows(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); // 1

deleteRows(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 deleted

sortRows(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