capitalsix-data-table 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +253 -0
  2. package/package.json +62 -0
package/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # DataTable
2
+
3
+ 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, Svelte stores, etc.
4
+
5
+ ## Features
6
+
7
+ - **Typed rows** — generic over any object type `T`
8
+ - **Insert** with optional duplicate-key guard
9
+ - **Update** by predicate — via mutation function or replacement object
10
+ - **Upsert** — insert new rows or update existing ones in a single call
11
+ - **Delete** by predicate, returns deleted rows
12
+ - **Sort** in-place with a custom comparator
13
+ - **Query** all rows or filter with a predicate
14
+ - **Bulk replace** via `setRows` for full re-initialisation
15
+ - Zero runtime dependencies
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install capitalsix-data-table
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```ts
26
+ import { DataTable } from 'capitalsix-data-table';
27
+
28
+ type User = { id: number; name: string; age: number };
29
+
30
+ const table = new DataTable<User>([
31
+ { id: 1, name: 'Alice', age: 30 },
32
+ { id: 2, name: 'Bob', age: 25 },
33
+ ]);
34
+
35
+ const users = table.getRows();
36
+ // [{ id: 1, name: 'Alice', age: 30 }, { id: 2, name: 'Bob', age: 25 }]
37
+ ```
38
+
39
+ ## API
40
+
41
+ ### `constructor(initialRows?: T[])`
42
+
43
+ Creates a new table, optionally pre-populated with rows.
44
+
45
+ ```ts
46
+ const empty = new DataTable<User>();
47
+ const seeded = new DataTable<User>([{ id: 1, name: 'Alice', age: 30 }]);
48
+ ```
49
+
50
+ ---
51
+
52
+ ### `getRows(predicate?: (row: T) => boolean): T[]`
53
+
54
+ Returns all rows, or only the rows matching the predicate.
55
+
56
+ ```ts
57
+ const all = table.getRows();
58
+ const young = table.getRows(u => u.age < 30);
59
+ ```
60
+
61
+ ---
62
+
63
+ ### `setRows(rows: T[]): void`
64
+
65
+ Replaces **all** current rows with the provided array. Useful for bulk re-initialisation (e.g. after loading fresh data from an API).
66
+
67
+ ```ts
68
+ table.setRows([
69
+ { id: 10, name: 'Carol', age: 28 },
70
+ { id: 11, name: 'Dave', age: 35 },
71
+ ]);
72
+ ```
73
+
74
+ ---
75
+
76
+ ### `insertRows(rows: T[], primaryKey?: keyof T): void`
77
+
78
+ Appends rows to the table. When `primaryKey` is provided, the method throws if any incoming key already exists, preventing accidental duplicates.
79
+
80
+ ```ts
81
+ // Insert without key guard
82
+ table.insertRows([{ id: 3, name: 'Eve', age: 22 }]);
83
+
84
+ // Insert with duplicate-key guard
85
+ table.insertRows([{ id: 4, name: 'Frank', age: 40 }], 'id');
86
+
87
+ // Throws: "Duplicate primary key value found in existing rows for key: id"
88
+ table.insertRows([{ id: 1, name: 'Alice again', age: 99 }], 'id');
89
+ ```
90
+
91
+ ---
92
+
93
+ ### `updateRows(predicate, update, primaryKey?): T[]`
94
+
95
+ Updates all rows matching `predicate` and returns them (already reflecting the changes).
96
+
97
+ **Mutation-function style** — receives each matched row and mutates it directly:
98
+
99
+ ```ts
100
+ table.updateRows(
101
+ u => u.id === 1,
102
+ u => { u.age += 1; },
103
+ );
104
+ ```
105
+
106
+ **Replacement-object style** — merges the object onto each matched row while preserving the primary key:
107
+
108
+ ```ts
109
+ table.updateRows(
110
+ u => u.id === 2,
111
+ { id: 0, name: 'Bob Updated', age: 26 },
112
+ 'id', // primaryKey is required; its value is preserved from the original row
113
+ );
114
+ ```
115
+
116
+ ---
117
+
118
+ ### `upsertRows(rows: T[], primaryKey: keyof T): DataTableUpsertResult<T>`
119
+
120
+ 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.
121
+
122
+ ```ts
123
+ const result = table.upsertRows(
124
+ [
125
+ { id: 1, name: 'Alice', age: 31 }, // key exists -> updated
126
+ { id: 5, name: 'Grace', age: 27 }, // new key -> inserted
127
+ ],
128
+ 'id',
129
+ );
130
+
131
+ console.log(result.updated.length); // 1
132
+ console.log(result.inserted.length); // 1
133
+ ```
134
+
135
+ ---
136
+
137
+ ### `deleteRows(predicate: (row: T) => boolean): T[]`
138
+
139
+ Removes all rows that match the predicate and returns them.
140
+
141
+ ```ts
142
+ const removed = table.deleteRows(u => u.age < 25);
143
+ console.log(removed); // rows that were deleted
144
+ ```
145
+
146
+ ---
147
+
148
+ ### `sortRows(compareFn: (a: T, b: T) => number): void`
149
+
150
+ Sorts rows in place using the provided comparator (same contract as `Array.prototype.sort`).
151
+
152
+ ```ts
153
+ // Sort by age ascending
154
+ table.sortRows((a, b) => a.age - b.age);
155
+
156
+ // Sort by name alphabetically
157
+ table.sortRows((a, b) => a.name.localeCompare(b.name));
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Examples
163
+
164
+ ### Build a simple in-memory cache
165
+
166
+ ```ts
167
+ import { DataTable } from 'capitalsix-data-table';
168
+
169
+ type Product = { sku: string; name: string; stock: number };
170
+
171
+ const cache = new DataTable<Product>();
172
+
173
+ // Load initial catalogue
174
+ cache.setRows(await fetchProducts());
175
+
176
+ // Reduce stock for a purchased item
177
+ cache.updateRows(
178
+ p => p.sku === 'ABC-123',
179
+ p => { p.stock -= 1; },
180
+ );
181
+
182
+ // Sync with latest server data, inserting new products and updating changed ones
183
+ const { inserted, updated } = cache.upsertRows(await fetchProducts(), 'sku');
184
+ console.log(`Synced: ${inserted.length} new, ${updated.length} updated`);
185
+ ```
186
+
187
+ ---
188
+
189
+ ### Filter and sort for display
190
+
191
+ ```ts
192
+ const inStock = table.getRows(p => p.stock > 0);
193
+
194
+ // Clone before sorting if you do not want to mutate the table order.
195
+ // Use sortRows() when you *do* want the table itself sorted.
196
+ const sorted = [...inStock].sort((a, b) => a.name.localeCompare(b.name));
197
+ ```
198
+
199
+ ---
200
+
201
+ ### Use inside a React reducer
202
+
203
+ ```ts
204
+ import { DataTable } from 'capitalsix-data-table';
205
+
206
+ type State = { table: DataTable<User> };
207
+
208
+ function reducer(state: State, action: Action): State {
209
+ switch (action.type) {
210
+ case 'UPSERT_USERS':
211
+ state.table.upsertRows(action.payload, 'id');
212
+ return { ...state }; // shallow clone to trigger re-render
213
+
214
+ case 'DELETE_USER':
215
+ state.table.deleteRows(u => u.id === action.id);
216
+ return { ...state };
217
+
218
+ default:
219
+ return state;
220
+ }
221
+ }
222
+ ```
223
+
224
+ ---
225
+
226
+ ### Typed interfaces for dependency injection
227
+
228
+ `DataTable` implements two interfaces you can use for narrowing in function signatures:
229
+
230
+ ```ts
231
+ import type { IDataTable, IDataTableInitial } from 'capitalsix-data-table';
232
+
233
+ // Read/write CRUD — no bulk replace
234
+ function processUsers(table: IDataTable<User>) {
235
+ table.insertRows([{ id: 99, name: 'Test', age: 0 }], 'id');
236
+ }
237
+
238
+ // Bulk replace only — used during initialisation
239
+ function loadUsers(table: IDataTableInitial<User>, users: User[]) {
240
+ table.setRows(users);
241
+ }
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Development
247
+
248
+ ```bash
249
+ npm install
250
+ npm test
251
+ npm run typecheck
252
+ npm run build
253
+ ```
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "capitalsix-data-table",
3
+ "version": "0.1.0",
4
+ "description": "A lightweight, framework-agnostic in-memory data table with typed CRUD operations: insert, update, upsert, delete, sort, and query rows.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.cjs",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "require": "./dist/index.cjs"
19
+ }
20
+ },
21
+ "author": {
22
+ "name": "Mike Wijnen",
23
+ "email": "mike@capitalsix.nl",
24
+ "url": "https://capitalsix.nl"
25
+ },
26
+ "repository": {
27
+ "url": "https://github.com/capital-six/data-table"
28
+ },
29
+ "ignore": [
30
+ "src/",
31
+ "node_modules/",
32
+ "PUBLISH.md"
33
+ ],
34
+ "sideEffects": false,
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest",
39
+ "typecheck": "tsc --noEmit",
40
+ "prepack": "npmignore --auto"
41
+ },
42
+ "peerDependencies": {
43
+ "react": "^18.3.1",
44
+ "react-dom": "^18.3.1"
45
+ },
46
+ "devDependencies": {
47
+ "@testing-library/jest-dom": "^6.4.8",
48
+ "@testing-library/react": "^16.0.1",
49
+ "@types/node": "^22.10.2",
50
+ "@types/react": "^18.3.1",
51
+ "@types/react-dom": "^18.3.1",
52
+ "@vitejs/plugin-react": "^5.0.0",
53
+ "jsdom": "^25.0.1",
54
+ "tsup": "^8.3.5",
55
+ "typescript": "^5.7.3",
56
+ "vite": "^6.0.5",
57
+ "vitest": "^2.1.8"
58
+ },
59
+ "dependencies": {
60
+ "npmignore": "^0.3.5"
61
+ }
62
+ }