capitalsix-data-store
v0.1.2
Published
A React-first, typed in-memory data store factory for shared table state with CRUD operations and per-record async status tracking.
Readme
Data Store
Data Store is a React-first, typed in-memory store for record collections.
It creates a shared hook per table name, keeps all consumers in sync, and tracks per-record operation status (working, completed, failed) while async operations run.
Install
npm install capitalsix-data-storeWhy use it
- One shared store per table name
- Typed records with a required
id: string - Built-in CRUD operations
- Optional async lifecycle events (
onInserted,onUpdated,onDeleted) - Automatic per-record status tracking for UI feedback
Core concepts
createDataStore()returns a hook binder.- Calling the binder with the same
tableNameshares state between components. rowscontains the current records.statesis a map fromrecordId -> operation state.operationscontains async CRUD methods you call from your UI or effects.
Quick start
import { createDataStore, type DataRecord } from 'capitalsix-data-store';
import type { JSX } from 'react';
type TodoData = {
title: string;
done: boolean;
};
type TodoRecord = DataRecord<TodoData>;
const useDataStore = createDataStore();
export const TodoList = (): JSX.Element => {
const { rows, states, operations } = useDataStore<TodoRecords>('todos');
const addTodo = async (): Promise<void> => {
const newRecord: TodoRecord = {
id: crypto.randomUUID(),
title: 'New item',
done: false,
};
await operations.insertRecords([newRecord]);
};
return (
<div>
<button type="button" onClick={() => void addTodo()}>
Add
</button>
<ul>
{rows.map((row) => (
<li key={row.id}>
{row.title} - {states[row.id] ?? 'completed'}
</li>
))}
</ul>
</div>
);
};API overview
createDataStore()
Creates a binder hook for named tables.
const useDataStore = createDataStore();useDataStore(tableName, events?)
Returns:
rows: current records in the tablestates: map from record id to operation stateoperations: CRUD-like async operations
Supported operations:
setRecords(rows): Promise<void>insertRecords(rows): Promise<void>updateRecords(rows): Promise<void>upsertRecords(rows): Promise<void>deleteRecords(ids): Promise<void>
Optional event callbacks:
onInserted(rows)onUpdated(rows)onDeleted(rows)
When a callback exists, touched rows move to working until the promise resolves or rejects.
Type notes
- Every record must include
id: string. DataRecord<T>is a helper type for this shape.- Event handlers receive the affected rows and can persist to APIs.
- If an event handler throws/rejects, record states move to
failed.
Examples
Example 1: optimistic insert with async persistence
import { createDataStore, type DataRecord } from 'capitalsix-data-store';
type User = DataRecord<{ name: string }>;
const useDataStore = createDataStore();
const { operations } = useDataStore<User>('users', {
onInserted: async (rows) => {
await fetch('/api/users', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(rows),
});
},
});
const usersToInsert: User[] = [{ id: 'u-1', name: 'Alice' }];
await operations.insertRecords(usersToInsert);Example 2: merge server snapshot with upsert
import { createDataStore, type DataRecord } from 'capitalsix-data-store';
type Product = DataRecord<{ name: string; price: number }>;
const useDataStore = createDataStore();
const { operations } = useDataStore<Product>('products', {
onUpdated: async (rows) => {
await fetch('/api/products/bulk', {
method: 'PATCH',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(rows),
});
},
});
const incoming = await fetch('/api/products').then((r) => r.json() as Promise<Product[]>);
await operations.upsertRecords(incoming);Example 3: row-level loading and error badges
import { createDataStore, type DataRecord, type DataRecordState } from 'capitalsix-data-store';
import type { JSX } from 'react';
type Task = DataRecord<{ title: string }>;
const StatusBadge = ({ state }: { state?: DataRecordState }): JSX.Element => {
if (state === 'working') return <span>Saving...</span>;
if (state === 'failed') return <span>Failed</span>;
return <span>Done</span>;
};
const useDataStore = createDataStore();
const RowsView = (): JSX.Element => {
const { rows, states } = useDataStore<Task>('tasks');
const taskRows = rows as Task[];
return (
<>
{taskRows.map((row) => (
<div key={row.id}>
{row.id} <StatusBadge state={states[row.id]} />
</div>
))}
</>
);
};Development
npm install
npm test
npm run typecheck
npm run build