@oimdb/core 1.4.1 → 2.0.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.
package/README.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  Core in-memory data library providing reactive collections, intelligent indexing, and configurable event processing. This package offers the foundational building blocks for building high-performance, event-driven in-memory databases with type-safe operations and automatic change notifications.
4
4
 
5
+ ## For AI Assistants
6
+
7
+ If you are an AI assistant working on a project that uses OIMDB, read the full API reference before writing any code. It covers all packages, key concepts, code patterns, and naming conventions — the API has specific patterns that are easy to get wrong without context.
8
+
9
+ The file ships with this package. Read it at:
10
+
11
+ ```
12
+ node_modules/@oimdb/core/llms.txt
13
+ ```
14
+
15
+ Or fetch from CDN if working without local files:
16
+
17
+ ```
18
+ https://cdn.jsdelivr.net/npm/@oimdb/core/llms.txt
19
+ ```
20
+
21
+ If the project has `@oimdb/devtools` installed, also look for `debug.ts` or `*.debug.ts` files — they register the app's specific collections, indexes, and computeds by name and describe their relations. This is the fastest way to understand the specific data model you're working with.
22
+
23
+ **Cursor users:** Cursor does not index `node_modules`, so it will not find this README automatically. Add a `.cursor/rules/oimdb.mdc` file to your project:
24
+
25
+ ```
26
+ Read node_modules/@oimdb/core/llms.txt before writing any OIMDB code.
27
+ ```
28
+
5
29
  ## 🚀 Installation
6
30
 
7
31
  ```bash
@@ -14,9 +38,13 @@ This package exports all the core classes, interfaces, and types needed to build
14
38
 
15
39
  ### Core Classes
16
40
  - **OIMReactiveCollection**: Reactive entity storage with automatic change notifications
17
- - **OIMRICollection**: Reactive collection with integrated indexing capabilities
41
+ - **OIMCollectionRelations**: Helper for creating collection-bound indexes and ordered lists next to a collection
18
42
  - **OIMReactiveIndexManualSetBased**: Reactive index with Set-based storage (efficient for incremental updates)
19
43
  - **OIMReactiveIndexManualArrayBased**: Reactive index with Array-based storage (efficient for full replacements)
44
+ - **OIMReactiveCollectionIndexManualSetBased**: Collection-bound Set-based index with safe PK-oriented writes
45
+ - **OIMReactiveCollectionIndexManualArrayBased**: Collection-bound Array-based index with safe PK-oriented writes
46
+ - **OIMOrderedListCommandStream**: Slot-first ordered per-key lists with incremental commands for imperative consumers
47
+ - **OIMCollectionOrderedListCommandStream**: Collection-bound ordered lists with PK writes and slot/entity command payloads
20
48
  - **OIMEventQueue**: Configurable event processing queue with scheduler integration
21
49
  - **OIMCollection**: Base collection with CRUD operations and event emission
22
50
 
@@ -36,17 +64,18 @@ This package exports all the core classes, interfaces, and types needed to build
36
64
 
37
65
  ### Storage & Indexing
38
66
  - **OIMCollectionStoreMapDriven**: Map-based storage backend
39
- - **OIMIndexManualSetBased**: Set-based manual index (returns `Set<TPk>`)
40
- - **OIMIndexManualArrayBased**: Array-based manual index (returns `TPk[]`)
41
- - **OIMIndexStoreMapDrivenSetBased**: Set-based index storage backend
42
- - **OIMIndexStoreMapDrivenArrayBased**: Array-based index storage backend
67
+ - **OIMIndexManualSetBased**: Set-based manual index (stores slots, projects to `Set<TPk>`)
68
+ - **OIMIndexManualArrayBased**: Array-based manual index (stores slots, projects to `TPk[]`)
69
+ - **OIMIndexManualOrderedArrayBased**: Slot-first manual ordered Array-based index with `pushSlot`, `insertSlotAt`, `removeAt`, `move`, and `resetSlots`
70
+ - **OIMIndexStoreMapDrivenSetBased**: Set-based slot index storage backend
71
+ - **OIMIndexStoreMapDrivenArrayBased**: Array-based slot index storage backend
43
72
  - **OIMMap2Keys**: Two-key mapping utilities for complex indexing
44
73
 
45
74
  ### Abstract Classes & Interfaces
46
75
  - **OIMCollectionStore**: Storage backend interface
47
76
  - **OIMEventQueueScheduler**: Event processing scheduler interface
48
- - **OIMIndexSetBased**: Base Set-based index interface (returns `Set<TPk>`)
49
- - **OIMIndexArrayBased**: Base Array-based index interface (returns `TPk[]`)
77
+ - **OIMIndexSetBased**: Base Set-based index interface (slot-backed, projects to `Set<TPk>`)
78
+ - **OIMIndexArrayBased**: Base Array-based index interface (slot-backed, projects to `TPk[]`)
50
79
  - **OIMReactiveIndexSetBased**: Reactive Set-based index interface
51
80
  - **OIMReactiveIndexArrayBased**: Reactive Array-based index interface
52
81
 
@@ -57,6 +86,43 @@ This package exports all the core classes, interfaces, and types needed to build
57
86
 
58
87
  ## 🔧 Basic Usage
59
88
 
89
+ ### DX Collection Model
90
+
91
+ Use the `dx` factories when you want a concise entrypoint without changing the underlying model. The collection still owns entities, and relations still live next to it.
92
+
93
+ ```typescript
94
+ import {
95
+ createOIMCollectionContext,
96
+ OIMEventQueue,
97
+ OIMEventQueueSchedulerFactory
98
+ } from '@oimdb/core';
99
+
100
+ type User = {
101
+ id: string;
102
+ name: string;
103
+ teamId: string;
104
+ };
105
+
106
+ const queue = new OIMEventQueue({
107
+ scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
108
+ });
109
+ const users = createOIMCollectionContext<User, string>(queue, {
110
+ selectPk: user => user.id,
111
+ });
112
+
113
+ const usersByTeam = users.indexFactory.derivedSetIndex(user => user.teamId);
114
+ const teamUsers = users.select.entitiesBySetIndexKey(usersByTeam, 'team1');
115
+
116
+ users.collection.upsertMany([
117
+ { id: 'u1', name: 'Alice', teamId: 'team1' },
118
+ { id: 'u2', name: 'Bob', teamId: 'team1' },
119
+ ]);
120
+
121
+ teamUsers.watch(value => {
122
+ console.log(value); // Alice, Bob
123
+ });
124
+ ```
125
+
60
126
  ### Creating a Reactive Collection
61
127
 
62
128
  ```typescript
@@ -92,33 +158,63 @@ users.updateEventEmitter.subscribeOnKeys(['user1', 'user2'], () => {
92
158
  console.log('Users changed!');
93
159
  });
94
160
 
95
- // CRUD operations
96
- users.upsertOne({ id: 'user1', name: 'John Doe', email: 'john@example.com' });
97
- users.upsertMany([
161
+ // CRUD operations return canonical slots.
162
+ const user1Slot = users.upsertOne({
163
+ id: 'user1',
164
+ name: 'John Doe',
165
+ email: 'john@example.com'
166
+ });
167
+ const otherSlots = users.upsertMany([
98
168
  { id: 'user2', name: 'Jane Smith', email: 'jane@example.com' },
99
169
  { id: 'user3', name: 'Bob Wilson', email: 'bob@example.com' }
100
170
  ]);
101
171
 
172
+ // Updating an entity keeps the same slot object and updates slot.item.
173
+ const updatedUser1Slot = users.upsertOne({
174
+ id: 'user1',
175
+ name: 'John Smith',
176
+ email: 'john@example.com'
177
+ });
178
+ console.log(user1Slot === updatedUser1Slot); // true
179
+
102
180
  // Query operations
103
181
  const user = users.getOneByPk('user1');
104
182
  const multipleUsers = users.getManyByPks(['user1', 'user2']);
183
+ const userSlot = users.getSlotByPk('user1');
105
184
  ```
106
185
 
107
186
  ### Creating a Reactive Index
108
187
 
109
188
  OIMDB provides two types of indexes optimized for different use cases:
110
189
 
190
+ Indexes are slot-backed internally and expose PK projections through `getPksByKey` (`Set<TPk>` for SetBased, `TPk[]` for ArrayBased). Raw indexes can be used as standalone PK/slot structures. When an index belongs to a collection, prefer `OIMReactiveCollectionIndexManual*`; it binds the collection at construction time so PK writes resolve canonical collection slots without a later lifecycle setter.
191
+
192
+ For most app-level entity relations, start with `createOIMCollectionContext(...).indexFactory.derivedSetIndex(...)` or `.derivedArrayIndex(...)`. The manual indexes below are the advanced path for externally maintained memberships such as search results, permissions, or server-provided order.
193
+
111
194
  #### SetBased Indexes (for incremental updates)
112
195
 
113
196
  ```typescript
114
- import { OIMReactiveIndexManualSetBased, OIMEventQueue } from '@oimdb/core';
197
+ import {
198
+ OIMReactiveCollection,
199
+ OIMReactiveCollectionIndexManualSetBased,
200
+ OIMEventQueue
201
+ } from '@oimdb/core';
202
+
203
+ type User = { id: string; role: string };
115
204
 
116
205
  // Create Set-based reactive index for user roles
117
206
  const queue = new OIMEventQueue({
118
207
  scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
119
208
  });
120
209
 
121
- const userRoleIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
210
+ const users = new OIMReactiveCollection<User, string>(queue, {
211
+ selectPk: (user) => user.id
212
+ });
213
+ const userRoleIndex =
214
+ new OIMReactiveCollectionIndexManualSetBased<string, string, User>(
215
+ queue,
216
+ { collection: users }
217
+ );
122
218
 
123
219
  // Subscribe to specific index key changes
124
220
  userRoleIndex.updateEventEmitter.subscribeOnKey('admin', () => {
@@ -143,14 +239,27 @@ userRoleIndex.removePks('admin', ['user1']);
143
239
  #### ArrayBased Indexes (for full replacements)
144
240
 
145
241
  ```typescript
146
- import { OIMReactiveIndexManualArrayBased, OIMEventQueue } from '@oimdb/core';
242
+ import {
243
+ OIMReactiveCollection,
244
+ OIMReactiveCollectionIndexManualArrayBased,
245
+ OIMEventQueue
246
+ } from '@oimdb/core';
247
+
248
+ type Card = { id: string; deckId: string };
147
249
 
148
250
  // Create Array-based reactive index for deck cards
149
251
  const queue = new OIMEventQueue({
150
252
  scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
151
253
  });
152
254
 
153
- const cardsByDeckIndex = new OIMReactiveIndexManualArrayBased<string, string>(queue);
255
+ const cards = new OIMReactiveCollection<Card, string>(queue, {
256
+ selectPk: (card) => card.id
257
+ });
258
+ const cardsByDeckIndex =
259
+ new OIMReactiveCollectionIndexManualArrayBased<string, string, Card>(
260
+ queue,
261
+ { collection: cards }
262
+ );
154
263
 
155
264
  // Subscribe to specific index key changes
156
265
  cardsByDeckIndex.updateEventEmitter.subscribeOnKey('deck1', () => {
@@ -172,6 +281,68 @@ cardsByDeckIndex.setPks('deck1', ['card1', 'card2', 'card4']); // Full replaceme
172
281
  - **SetBased**: Use when you frequently add/remove individual items (`addPks`/`removePks` are efficient) and order doesn't matter
173
282
  - **ArrayBased**: Use when you typically replace the entire array (`setPks` is more efficient, no diff computation needed) or when you need to preserve element order/sorting
174
283
 
284
+ #### Ordered List Command Streams
285
+
286
+ Use `OIMCollectionOrderedListCommandStream` when you need an ordered list per key, PK-oriented writes, canonical collection slots, and incremental commands for an imperative renderer or external store. Use `OIMOrderedListCommandStream` directly only when you already manage slots yourself.
287
+
288
+ ```typescript
289
+ import {
290
+ OIMEventQueue,
291
+ OIMReactiveCollection,
292
+ OIMCollectionOrderedListCommandStream
293
+ } from '@oimdb/core';
294
+
295
+ type Card = { id: string; title: string };
296
+
297
+ const queue = new OIMEventQueue();
298
+ const cards = new OIMReactiveCollection<Card, string>(queue, {
299
+ selectPk: card => card.id,
300
+ });
301
+ const cardsByDeck = new OIMCollectionOrderedListCommandStream<
302
+ string,
303
+ string,
304
+ Card
305
+ >(queue, { collection: cards });
306
+
307
+ cards.upsertMany([
308
+ { id: 'card1', title: 'Intro' },
309
+ { id: 'card2', title: 'Details' },
310
+ { id: 'card3', title: 'Summary' },
311
+ ]);
312
+
313
+ cardsByDeck.commandsEventEmitter.subscribeOnKey('deck1', () => {
314
+ const commands = cardsByDeck.consumeCommands('deck1');
315
+
316
+ for (const command of commands) {
317
+ switch (command.type) {
318
+ case 'insert':
319
+ // Insert command.pk / command.slot.item at command.index
320
+ break;
321
+ case 'remove':
322
+ // Remove command.pk from command.index
323
+ break;
324
+ case 'move':
325
+ // Move command.pk from command.fromIndex to command.toIndex
326
+ break;
327
+ case 'set':
328
+ // Replace the whole list with command.pks or command.slots
329
+ break;
330
+ }
331
+ }
332
+ });
333
+
334
+ cardsByDeck.set('deck1', ['card1', 'card2']);
335
+ cardsByDeck.move('deck1', 1, 0);
336
+ cardsByDeck.push('deck1', 'card3');
337
+
338
+ queue.flush(); // Delivers one batched command notification for deck1
339
+
340
+ console.log(cardsByDeck.getPksByKey('deck1')); // ['card2', 'card1', 'card3']
341
+ console.log(cardsByDeck.getEntitiesByKey('deck1')); // [{ id: 'card2', ... }, ...]
342
+ ```
343
+
344
+ If you mutate `cardsByDeck.index` directly, the stream cannot know the exact operation and emits a `set` command so consumers can resync.
345
+
175
346
  ### Event Queue and Schedulers
176
347
 
177
348
  ```typescript
@@ -214,12 +385,13 @@ console.log('Is empty:', manualQueue.isEmpty);
214
385
 
215
386
  ## 🏗️ Advanced Usage
216
387
 
217
- ### Reactive Collection with Indexes (OIMRICollection)
388
+ ### Collections with Bound Indexes
218
389
 
219
390
  ```typescript
220
391
  import {
221
- OIMRICollection,
222
- OIMReactiveIndexManual,
392
+ OIMReactiveCollection,
393
+ OIMReactiveCollectionIndexManualSetBased,
394
+ OIMReactiveCollectionIndexManualArrayBased,
223
395
  OIMEventQueue,
224
396
  OIMEventQueueSchedulerFactory
225
397
  } from '@oimdb/core';
@@ -237,24 +409,28 @@ const queue = new OIMEventQueue({
237
409
  scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
238
410
  });
239
411
 
240
- // Create indexes (choose SetBased or ArrayBased based on your needs)
241
- const teamIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
242
- const roleIndex = new OIMReactiveIndexManualArrayBased<string, string>(queue);
243
-
244
- // Create collection with indexes
245
- const users = new OIMRICollection(queue, {
246
- collectionOpts: {
247
- selectPk: (user: User) => user.id
248
- },
249
- indexes: {
250
- byTeam: teamIndex,
251
- byRole: roleIndex
252
- }
412
+ // Collections own entities.
413
+ const users = new OIMReactiveCollection<User, string>(queue, {
414
+ selectPk: (user: User) => user.id
253
415
  });
254
416
 
417
+ // Indexes live next to collections and bind to them at construction time.
418
+ const indexes = {
419
+ usersByTeam: new OIMReactiveCollectionIndexManualSetBased<
420
+ string,
421
+ string,
422
+ User
423
+ >(queue, { collection: users }),
424
+ usersByRole: new OIMReactiveCollectionIndexManualArrayBased<
425
+ string,
426
+ string,
427
+ User
428
+ >(queue, { collection: users })
429
+ };
430
+
255
431
  // Subscribe to index changes
256
- users.indexes.byTeam.updateEventEmitter.subscribeOnKey('engineering', (pks) => {
257
- console.log('Engineering team changed:', pks);
432
+ indexes.usersByTeam.updateEventEmitter.subscribeOnKey('engineering', () => {
433
+ console.log('Engineering team changed:', indexes.usersByTeam.getPksByKey('engineering'));
258
434
  });
259
435
 
260
436
  // Add users and update indexes
@@ -264,8 +440,70 @@ users.upsertMany([
264
440
  ]);
265
441
 
266
442
  // Update indexes manually
267
- users.indexes.byTeam.setPks('engineering', ['u1', 'u2']);
268
- users.indexes.byRole.setPks('admin', ['u1']);
443
+ indexes.usersByTeam.setPks('engineering', ['u1', 'u2']);
444
+ indexes.usersByRole.setPks('admin', ['u1']);
445
+ ```
446
+
447
+ ### Collection Relations Helper
448
+
449
+ Use `createOIMCollectionRelations` when you want the same clean model with less constructor noise. It does not store indexes inside the collection; it only keeps the shared `queue + collection` binding for related structures that live next to the collection.
450
+
451
+ ```typescript
452
+ import {
453
+ createOIMCollectionRelations,
454
+ OIMReactiveCollection,
455
+ OIMEventQueue
456
+ } from '@oimdb/core';
457
+
458
+ type User = {
459
+ id: string;
460
+ name: string;
461
+ teamId: string;
462
+ };
463
+
464
+ type Card = {
465
+ id: string;
466
+ deckId: string;
467
+ position: number;
468
+ };
469
+
470
+ const queue = new OIMEventQueue();
471
+ const users = new OIMReactiveCollection<User, string>(queue, {
472
+ selectPk: user => user.id,
473
+ });
474
+ const userRelations = createOIMCollectionRelations(queue, users);
475
+ const cards = new OIMReactiveCollection<Card, string>(queue, {
476
+ selectPk: card => card.id,
477
+ });
478
+ const cardRelations = createOIMCollectionRelations(queue, cards);
479
+
480
+ // Derived indexes update themselves from collection writes.
481
+ const usersByTeam = userRelations.derivedSetIndex(user => user.teamId);
482
+ const cardsByDeck = cardRelations.derivedArrayIndex(
483
+ card => card.deckId,
484
+ { orderBy: card => card.position }
485
+ );
486
+
487
+ // Manual relations are still available when membership comes from outside.
488
+ const searchResults = userRelations.arrayBasedIndex<string>();
489
+ const orderedUsers = userRelations.orderedList<string>();
490
+
491
+ users.upsertMany([
492
+ { id: 'u1', name: 'Alice', teamId: 'team1' },
493
+ { id: 'u2', name: 'Bob', teamId: 'team1' },
494
+ ]);
495
+ cards.upsertMany([
496
+ { id: 'c1', deckId: 'deck1', position: 2 },
497
+ { id: 'c2', deckId: 'deck1', position: 1 },
498
+ ]);
499
+
500
+ searchResults.setPks('query:alice', ['u1']);
501
+ orderedUsers.set('visible', ['u1', 'u2']);
502
+ queue.flush();
503
+
504
+ console.log(usersByTeam.getEntitiesByKey('team1')); // Alice, Bob
505
+ console.log(cardsByDeck.getPksByKey('deck1')); // ['c2', 'c1']
506
+ console.log(orderedUsers.getPksByKey('visible')); // ['u1', 'u2']
269
507
  ```
270
508
 
271
509
  ### Custom Entity Updater
@@ -452,13 +690,25 @@ import {
452
690
  OIMEffect,
453
691
  OIMComputativeRuntime,
454
692
  OIMEventQueue,
455
- OIMReactiveIndexManualSetBased,
693
+ OIMReactiveCollection,
694
+ OIMReactiveCollectionIndexManualSetBased,
456
695
  OIMEffectDependencyKeyedIndex,
457
696
  } from '@oimdb/core';
458
697
 
698
+ interface User {
699
+ id: string;
700
+ name: string;
701
+ }
702
+
459
703
  const queue = new OIMEventQueue();
460
704
  const runtime = new OIMComputativeRuntime(queue);
461
- const roleIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
705
+ const users = new OIMReactiveCollection<User, string>(queue, {
706
+ selectPk: (u) => u.id,
707
+ });
708
+ const roleIndex = new OIMReactiveCollectionIndexManualSetBased<string, string, User>(
709
+ queue,
710
+ { collection: users }
711
+ );
462
712
 
463
713
  const effect = new OIMEffect(runtime, {
464
714
  deps: [new OIMEffectDependencyKeyedIndex(roleIndex, 'admin')],
@@ -468,11 +718,16 @@ const effect = new OIMEffect(runtime, {
468
718
  },
469
719
  });
470
720
 
721
+ users.upsertMany([
722
+ { id: 'user1', name: 'Alice' },
723
+ { id: 'user2', name: 'Bob' },
724
+ ]);
471
725
  roleIndex.setPks('admin', ['user1', 'user2']);
472
726
  queue.flush();
473
727
 
474
728
  effect.destroy();
475
729
  roleIndex.destroy();
730
+ users.destroy();
476
731
  queue.destroy();
477
732
  ```
478
733
 
@@ -570,7 +825,7 @@ import {
570
825
  OIMEventQueue,
571
826
  OIMComputativeRuntime,
572
827
  OIMReactiveCollection,
573
- OIMReactiveIndexManualSetBased,
828
+ OIMReactiveCollectionIndexManualSetBased,
574
829
  OIMEffectDependencyKeyedCollection,
575
830
  OIMEffectDependencyKeyedIndex,
576
831
  } from '@oimdb/core';
@@ -586,7 +841,10 @@ const runtime = new OIMComputativeRuntime(queue);
586
841
  const users = new OIMReactiveCollection<User, string>(queue, {
587
842
  selectPk: (u) => u.id,
588
843
  });
589
- const roleIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
844
+ const roleIndex = new OIMReactiveCollectionIndexManualSetBased<string, string, User>(
845
+ queue,
846
+ { collection: users }
847
+ );
590
848
 
591
849
  // Computed that counts admin users
592
850
  const adminCount = new OIMComputed<number>(runtime, {
@@ -734,7 +992,7 @@ import {
734
992
  OIMComputativeRuntime,
735
993
  OIMEventQueue,
736
994
  OIMReactiveCollection,
737
- OIMReactiveIndexManualSetBased,
995
+ OIMReactiveCollectionIndexManualSetBased,
738
996
  } from '@oimdb/core';
739
997
 
740
998
  const queue = new OIMEventQueue();
@@ -742,7 +1000,10 @@ const runtime = new OIMComputativeRuntime(queue);
742
1000
  const users = new OIMReactiveCollection<User, string>(queue, {
743
1001
  selectPk: (u) => u.id,
744
1002
  });
745
- const roleIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
1003
+ const roleIndex = new OIMReactiveCollectionIndexManualSetBased<string, string, User>(
1004
+ queue,
1005
+ { collection: users }
1006
+ );
746
1007
 
747
1008
  // Watch all admin users
748
1009
  const adminUsersSelector = new OIMEntitiesByIndexKeySetBasedSelector(
@@ -833,17 +1094,23 @@ Choose the right scheduler for your use case:
833
1094
  ### Reactive Collection Hierarchy
834
1095
 
835
1096
  ```
836
- OIMCollection (base)
837
- ├── OIMReactiveCollection (adds updateEventEmitter wired to the queue)
838
- └── OIMRICollection (reactive collection + indexes)
1097
+ OIMCollection (base; upserts return canonical slots)
1098
+ └── OIMReactiveCollection (adds updateEventEmitter wired to the queue)
839
1099
 
840
1100
  OIMIndexSetBased (base for Set-based)
841
1101
  ├── OIMIndexManualSetBased (manual Set-based index)
842
- └── OIMReactiveIndexManualSetBased (reactive Set-based index with event emitter)
1102
+ ├── OIMReactiveIndexManualSetBased (reactive Set-based index with event emitter)
1103
+ └── OIMReactiveCollectionIndexManualSetBased (collection-bound reactive Set-based index)
843
1104
 
844
1105
  OIMIndexArrayBased (base for Array-based)
845
1106
  ├── OIMIndexManualArrayBased (manual Array-based index)
846
- └── OIMReactiveIndexManualArrayBased (reactive Array-based index with event emitter)
1107
+ ├── OIMIndexManualOrderedArrayBased (slot-first manual ordered Array-based index)
1108
+ ├── OIMCollectionIndexManualOrderedArrayBased (collection-bound ordered Array-based index)
1109
+ ├── OIMReactiveIndexManualArrayBased (reactive Array-based index with event emitter)
1110
+ └── OIMReactiveCollectionIndexManualArrayBased (collection-bound reactive Array-based index)
1111
+
1112
+ OIMOrderedListCommandStream (slot-first ordered-list command stream)
1113
+ OIMCollectionOrderedListCommandStream (collection-bound ordered-list command stream)
847
1114
  ```
848
1115
 
849
1116
  ## ⚡ Performance Characteristics
@@ -861,14 +1128,18 @@ OIMIndexArrayBased (base for Array-based)
861
1128
 
862
1129
  ### Index Performance
863
1130
 
864
- **SetBased Indexes** (`OIMReactiveIndexManualSetBased`):
865
- - **Returns**: `Set<TPk>` for efficient membership checks
1131
+ **SetBased Indexes** (`OIMReactiveCollectionIndexManualSetBased`):
1132
+ - **Storage**: Set of canonical entity slots
1133
+ - **PK projection**: `getPksByKey` returns `Set<TPk>` for efficient membership checks
1134
+ - **Entity reads**: selectors/hooks read `slot.item` directly, avoiding per-item collection lookups
866
1135
  - **Best for**: Frequent incremental updates using `addPks`/`removePks`
867
1136
  - **Performance**: O(1) add/remove operations, O(n) for `setPks` (requires Set creation)
868
1137
  - **Use case**: When you need to frequently add/remove individual items
869
1138
 
870
- **ArrayBased Indexes** (`OIMReactiveIndexManualArrayBased`):
871
- - **Returns**: `TPk[]` for direct array access
1139
+ **ArrayBased Indexes** (`OIMReactiveCollectionIndexManualArrayBased`):
1140
+ - **Storage**: Array of canonical entity slots
1141
+ - **PK projection**: `getPksByKey` returns `TPk[]` for direct array access
1142
+ - **Entity reads**: selectors/hooks read `slot.item` directly, preserving array order
872
1143
  - **Best for**: Full array replacements using `setPks`
873
1144
  - **Performance**: O(1) `setPks` operation (direct assignment, no diff computation)
874
1145
  - **Use case**: When you typically replace the entire array (e.g., deck cards, ordered lists) or when you need to preserve element order/sorting
@@ -878,16 +1149,60 @@ OIMIndexArrayBased (base for Array-based)
878
1149
 
879
1150
  ### With React (@oimdb/react)
880
1151
 
881
- The core library integrates seamlessly with React through dedicated hooks:
1152
+ ```bash
1153
+ npm install @oimdb/react
1154
+ ```
1155
+
1156
+ Create your collections outside React, wire them up once, then use hooks inside components:
882
1157
 
883
1158
  ```typescript
884
- import { useSelectEntitiesByPks, selectEntityByPk } from '@oimdb/react';
1159
+ import { createOIMCollectionContext, OIMEventQueue } from '@oimdb/core';
1160
+ import {
1161
+ OIMCollectionsProvider,
1162
+ useOIMCollectionsContext,
1163
+ useSelectEntityByPk,
1164
+ useSelectEntitiesByIndexKeySetBased,
1165
+ } from '@oimdb/react';
885
1166
 
886
- // React hooks automatically subscribe to reactive collections
887
- const user = selectEntityByPk(users, 'user1');
888
- const teamUsers = useSelectEntitiesByPks(users, userIds);
1167
+ // --- store.ts (created once, outside React) ---
1168
+ const queue = new OIMEventQueue();
1169
+ const { collection: users, indexFactory } =
1170
+ createOIMCollectionContext<User, string>(queue, { selectPk: (u) => u.id });
1171
+ const byTeam = indexFactory.derivedSetIndex((u) => [u.teamId]);
1172
+ export const collections = { users };
1173
+ export { byTeam };
1174
+
1175
+ // --- App.tsx ---
1176
+ function App() {
1177
+ return (
1178
+ <OIMCollectionsProvider collections={collections}>
1179
+ <TeamList teamId="team1" />
1180
+ </OIMCollectionsProvider>
1181
+ );
1182
+ }
1183
+
1184
+ // --- TeamList.tsx ---
1185
+ type AppCollections = typeof collections;
1186
+
1187
+ function TeamList({ teamId }: { teamId: string }) {
1188
+ const { users } = useOIMCollectionsContext<AppCollections>();
1189
+ const members = useSelectEntitiesByIndexKeySetBased(users, byTeam, teamId);
1190
+ return (
1191
+ <ul>
1192
+ {members?.map((u) => u && <li key={u.id}>{u.name}</li>)}
1193
+ </ul>
1194
+ );
1195
+ }
1196
+
1197
+ function UserCard({ userId }: { userId: string }) {
1198
+ const { users } = useOIMCollectionsContext<AppCollections>();
1199
+ const user = useSelectEntityByPk(users, userId);
1200
+ return <span>{user?.name}</span>;
1201
+ }
889
1202
  ```
890
1203
 
1204
+ All hooks use `useSyncExternalStore` internally and re-render only when the specific data they watch actually changes.
1205
+
891
1206
  ### With Redux (@oimdb/redux-adapter)
892
1207
 
893
1208
  Migrate from Redux to OIMDB gradually or use both systems side-by-side with automatic two-way synchronization:
@@ -946,6 +1261,34 @@ unsubscribe();
946
1261
 
947
1262
  ## 📚 API Reference
948
1263
 
1264
+ ### DX Factories
1265
+
1266
+ #### `createOIMReactiveCollection<TEntity, TPk>(queue, opts?)`
1267
+ Creates an `OIMReactiveCollection<TEntity, TPk>` with less constructor noise.
1268
+
1269
+ #### `createOIMCollectionContext<TEntity, TPk>(queue, opts?)`
1270
+ Creates a small facade:
1271
+
1272
+ ```typescript
1273
+ type TOIMCollectionContext<TEntity, TPk> = {
1274
+ queue: OIMEventQueue;
1275
+ collection: OIMReactiveCollection<TEntity, TPk>;
1276
+ relations: OIMCollectionRelations<TEntity, TPk>;
1277
+ select: OIMCollectionSelectors<TEntity, TPk>;
1278
+ };
1279
+ ```
1280
+
1281
+ This is a DX entrypoint only: indexes and lists are still created as separate relation objects, not stored inside the collection.
1282
+
1283
+ #### `OIMCollectionSelectors<TEntity, TPk>`
1284
+ DX facade for reactive read selectors backed by one `OIMComputativeRuntime`.
1285
+
1286
+ **Methods:**
1287
+ - `byPk(pk)` - Create `OIMCollectionByPkSelector<TEntity, TPk>`
1288
+ - `byPks(pks)` - Create `OIMCollectionByPksSelector<TEntity, TPk>`
1289
+ - `entitiesBySetIndexKey(index, key)` - Create an entity selector for a Set-based reactive index key
1290
+ - `entitiesByArrayIndexKey(index, key)` - Create an entity selector for an Array-based reactive index key
1291
+
949
1292
  ### Core Classes
950
1293
 
951
1294
  #### `OIMReactiveCollection<TEntity, TPk>`
@@ -962,30 +1305,39 @@ new OIMReactiveCollection(queue: OIMEventQueue, opts?: TOIMCollectionOptions<TEn
962
1305
  - Event batching/deduplication is handled internally by `OIMUpdateEventEmitter`
963
1306
 
964
1307
  **Methods:**
965
- - `upsertOne(entity: TEntity): void` - Insert or update single entity
966
- - `upsertMany(entities: TEntity[]): void` - Insert or update multiple entities
1308
+ - `upsertOne(entity: TEntity): TOIMEntitySlot<TEntity, TPk>` - Insert or update single entity and return its canonical slot
1309
+ - `upsertOneByPk(pk: TPk, entity: Partial<TEntity>): TOIMEntitySlot<TEntity, TPk>` - Insert or update by primary key and return its canonical slot
1310
+ - `upsertMany(entities: TEntity[]): TOIMEntitySlot<TEntity, TPk>[]` - Insert or update multiple entities and return canonical slots
967
1311
  - `removeOne(entity: TEntity): void` - Remove single entity
968
1312
  - `removeMany(entities: TEntity[]): void` - Remove multiple entities
969
1313
  - `getOneByPk(pk: TPk): TEntity | undefined` - Get entity by primary key
970
- - `getManyByPks(pks: readonly TPk[]): Map<TPk, TEntity | undefined>` - Get multiple entities
1314
+ - `getManyByPks(pks: readonly TPk[]): TEntity[]` - Get existing entities for primary keys
1315
+ - `getSlotByPk(pk: TPk): OIMEntitySlot<TEntity, TPk> | undefined` - Get the canonical slot for a primary key
1316
+ - `getSlotsByPks(pks: readonly TPk[]): OIMEntitySlot<TEntity, TPk>[]` - Get existing canonical slots for primary keys
971
1317
 
972
- #### `OIMRICollection<TEntity, TPk, TIndexName, TIndexKey, TIndex, TReactiveIndex, TReactiveIndexMap>`
973
- Reactive collection with integrated indexing capabilities.
1318
+ #### `OIMCollectionRelations<TEntity, TPk>`
1319
+ Factory helper for collection-bound relations. It keeps `queue + collection` together for construction only; created indexes/lists still live next to the collection, not inside it.
974
1320
 
975
1321
  **Constructor:**
976
1322
  ```typescript
977
- new OIMRICollection(queue: OIMEventQueue, opts: {
978
- collectionOpts?: TOIMCollectionOptions<TEntity, TPk>;
979
- indexes: TReactiveIndexMap;
980
- })
1323
+ new OIMCollectionRelations(queue: OIMEventQueue, collection: OIMReactiveCollection<TEntity, TPk>)
981
1324
  ```
982
1325
 
983
- **Properties:**
984
- - `indexes: TReactiveIndexMap` - Named reactive indexes preserving index-to-name mapping
985
- - *(inherits all OIMReactiveCollection properties)*
1326
+ **Factory:**
1327
+ ```typescript
1328
+ createOIMCollectionRelations(queue, collection)
1329
+ ```
1330
+
1331
+ **Methods:**
1332
+ - `setBasedIndex<TKey>()` - Create `OIMReactiveCollectionIndexManualSetBased<TKey, TPk, TEntity>`
1333
+ - `derivedSetIndex<TKey>(selectIndexKeys)` - Create `OIMDerivedCollectionIndexSetBased<TKey, TPk, TEntity>`
1334
+ - `arrayBasedIndex<TKey>()` - Create `OIMReactiveCollectionIndexManualArrayBased<TKey, TPk, TEntity>`
1335
+ - `derivedArrayIndex<TKey>(selectIndexKeys, { orderBy?, compareEntities? })` - Create `OIMDerivedCollectionIndexArrayBased<TKey, TPk, TEntity>`
1336
+ - `orderedIndex<TKey>()` - Create `OIMCollectionIndexManualOrderedArrayBased<TKey, TPk, TEntity>`
1337
+ - `orderedList<TKey>()` - Create `OIMCollectionOrderedListCommandStream<TKey, TPk, TEntity>`
986
1338
 
987
1339
  #### `OIMReactiveIndexManualSetBased<TKey, TPk>`
988
- Reactive Set-based index with manual key-to-entity mapping and change notifications. Returns `Set<TPk>` for efficient membership checks.
1340
+ Reactive Set-based index with manual slot writes and change notifications. Use this as a raw slot-first primitive; use `OIMReactiveCollectionIndexManualSetBased` for PK-oriented writes.
989
1341
 
990
1342
  **Constructor:**
991
1343
  ```typescript
@@ -1002,16 +1354,63 @@ new OIMReactiveIndexManualSetBased(queue: OIMEventQueue, opts?: {
1002
1354
  - `updateEventEmitter: OIMUpdateEventEmitter<TKey>` - Key-specific subscriptions
1003
1355
 
1004
1356
  **Methods:**
1005
- - `setPks(key: TKey, pks: readonly TPk[]): void` - Set primary keys for index key (replaces entire Set)
1006
- - `addPks(key: TKey, pks: readonly TPk[]): void` - Add primary keys to index key (efficient)
1007
- - `removePks(key: TKey, pks: readonly TPk[]): void` - Remove primary keys from index key (efficient)
1357
+ - `setSlots(key: TKey, slots: Iterable<TOIMAnyEntitySlot<TPk>>): void` - Set canonical slots directly
1008
1358
  - `clear(key?: TKey): void` - Clear all keys or specific key
1009
1359
 
1010
1360
  **Query:**
1011
- - `index.getPksByKey(key: TKey): Set<TPk>` - Returns Set of primary keys
1361
+ - `index.getPksByKey(key: TKey): Set<TPk>` - Returns Set projection of primary keys
1362
+ - `index.getSlotsByKey(key: TKey): ReadonlySet<TOIMAnyEntitySlot<TPk>>` - Returns stored slots for fast entity reads
1363
+
1364
+ #### `OIMReactiveCollectionIndexManualSetBased<TKey, TPk, TEntity>`
1365
+ Collection-bound Set-based index. Use this when `setPks`/`addPks`/`removePks` should resolve PKs to canonical slots from a collection.
1366
+
1367
+ **Constructor:**
1368
+ ```typescript
1369
+ new OIMReactiveCollectionIndexManualSetBased(queue: OIMEventQueue, opts: {
1370
+ collection: OIMReactiveCollection<TEntity, TPk>;
1371
+ indexOptions?: {
1372
+ comparePks?: TOIMIndexComparator<TPk>;
1373
+ store?: OIMIndexStoreSetBased<TKey, TPk>;
1374
+ };
1375
+ } | {
1376
+ resolveSlot: TOIMEntitySlotResolver<TPk>;
1377
+ indexOptions?: {
1378
+ comparePks?: TOIMIndexComparator<TPk>;
1379
+ store?: OIMIndexStoreSetBased<TKey, TPk>;
1380
+ };
1381
+ })
1382
+ ```
1383
+
1384
+ Pass exactly one binding: `collection` for normal collection-bound indexes, or `resolveSlot` for custom slot resolution.
1385
+
1386
+ **Methods:**
1387
+ - `setPks(key: TKey, pks: readonly TPk[]): void` - Set primary keys for index key
1388
+ - `addPks(key: TKey, pks: readonly TPk[]): void` - Add primary keys to index key
1389
+ - `removePks(key: TKey, pks: readonly TPk[]): void` - Remove primary keys from index key
1390
+ - `setSlots(key: TKey, slots: Iterable<TOIMAnyEntitySlot<TPk>>): void` - Set canonical slots directly
1391
+ - `clear(key?: TKey): void` - Clear all keys or specific key
1392
+
1393
+ #### `OIMDerivedCollectionIndexSetBased<TKey, TPk, TEntity>`
1394
+ Collection-bound Set-based index that derives membership from collection entities. Use this when index keys come from entity fields and should stay in sync automatically.
1395
+
1396
+ **Constructor:**
1397
+ ```typescript
1398
+ new OIMDerivedCollectionIndexSetBased(queue, collection, {
1399
+ selectIndexKeys: (entity: TEntity) => TKey | readonly TKey[] | undefined | null;
1400
+ buildInitial?: boolean; // defaults to true
1401
+ indexOptions?: {
1402
+ comparePks?: TOIMIndexComparator<TPk>;
1403
+ store?: OIMIndexStoreSetBased<TKey, TPk>;
1404
+ };
1405
+ })
1406
+ ```
1407
+
1408
+ **Methods:**
1409
+ - `rebuildFromCollection(): void` - Rebuild all derived membership from current collection slots
1410
+ - all read/subscription methods from `OIMReactiveCollectionIndexManualSetBased`
1012
1411
 
1013
1412
  #### `OIMReactiveIndexManualArrayBased<TKey, TPk>`
1014
- Reactive Array-based index with manual key-to-entity mapping and change notifications. Returns `TPk[]` for direct array access.
1413
+ Reactive Array-based index with manual slot writes and change notifications. Use this as a raw slot-first primitive; use `OIMReactiveCollectionIndexManualArrayBased` for PK-oriented writes.
1015
1414
 
1016
1415
  **Constructor:**
1017
1416
  ```typescript
@@ -1028,16 +1427,135 @@ new OIMReactiveIndexManualArrayBased(queue: OIMEventQueue, opts?: {
1028
1427
  - `updateEventEmitter: OIMUpdateEventEmitter<TKey>` - Key-specific subscriptions
1029
1428
 
1030
1429
  **Methods:**
1031
- - `setPks(key: TKey, pks: readonly TPk[]): void` - Set primary keys for index key (direct assignment, no diff) - **Recommended for ArrayBased**
1032
- - `addPks(key: TKey, pks: readonly TPk[]): void` - Add primary keys to index key (O(n) operation, less efficient than SetBased)
1033
- - `removePks(key: TKey, pks: readonly TPk[]): void` - Remove primary keys from index key (O(n) operation, less efficient than SetBased)
1430
+ - `setSlots(key: TKey, slots: TOIMAnyEntitySlot<TPk>[]): void` - Set canonical slots directly
1034
1431
  - `clear(key?: TKey): void` - Clear all keys or specific key
1035
1432
 
1036
1433
  **Query:**
1037
- - `index.getPksByKey(key: TKey): TPk[]` - Returns Array of primary keys
1434
+ - `index.getPksByKey(key: TKey): TPk[]` - Returns Array projection of primary keys
1435
+ - `index.getSlotsByKey(key: TKey): readonly TOIMAnyEntitySlot<TPk>[]` - Returns stored slots for fast entity reads
1038
1436
 
1039
1437
  **Note**: While `addPks`/`removePks` are available, they require array operations (Set creation, filtering) making them O(n) compared to O(1) for SetBased indexes. For ArrayBased indexes, prefer `setPks` for better performance when replacing the entire array.
1040
1438
 
1439
+ #### `OIMDerivedCollectionIndexArrayBased<TKey, TPk, TEntity>`
1440
+ Collection-bound Array-based index that derives ordered membership from collection entities. Use this for UI lists where entities define both grouping and order.
1441
+
1442
+ **Constructor:**
1443
+ ```typescript
1444
+ new OIMDerivedCollectionIndexArrayBased(queue, collection, {
1445
+ selectIndexKeys: (entity: TEntity) => TKey | readonly TKey[] | undefined | null;
1446
+ buildInitial?: boolean; // defaults to true
1447
+ orderBy?: (entity: TEntity) => string | number | bigint | boolean;
1448
+ compareEntities?: (a: TEntity, b: TEntity) => number;
1449
+ indexOptions?: {
1450
+ comparePks?: TOIMIndexComparator<TPk>;
1451
+ store?: OIMIndexStoreArrayBased<TKey, TPk>;
1452
+ };
1453
+ })
1454
+ ```
1455
+
1456
+ `compareEntities` takes priority over `orderBy`. Without either option, arrays keep collection slot iteration order.
1457
+
1458
+ **Methods:**
1459
+ - `rebuildFromCollection(): void` - Rebuild all derived ordered membership from current collection slots
1460
+ - all read/subscription methods from `OIMReactiveCollectionIndexManualArrayBased`
1461
+
1462
+ #### `OIMReactiveCollectionIndexManualArrayBased<TKey, TPk, TEntity>`
1463
+ Collection-bound Array-based index. Use this when ordered PK arrays should resolve to canonical slots from a collection.
1464
+
1465
+ **Constructor:**
1466
+ ```typescript
1467
+ new OIMReactiveCollectionIndexManualArrayBased(queue: OIMEventQueue, opts: {
1468
+ collection: OIMReactiveCollection<TEntity, TPk>;
1469
+ indexOptions?: {
1470
+ comparePks?: TOIMIndexComparator<TPk>;
1471
+ store?: OIMIndexStoreArrayBased<TKey, TPk>;
1472
+ };
1473
+ } | {
1474
+ resolveSlot: TOIMEntitySlotResolver<TPk>;
1475
+ indexOptions?: {
1476
+ comparePks?: TOIMIndexComparator<TPk>;
1477
+ store?: OIMIndexStoreArrayBased<TKey, TPk>;
1478
+ };
1479
+ })
1480
+ ```
1481
+
1482
+ Pass exactly one binding: `collection` for normal collection-bound indexes, or `resolveSlot` for custom slot resolution.
1483
+
1484
+ **Methods:**
1485
+ - `setPks(key: TKey, pks: readonly TPk[]): void` - Set primary keys for index key
1486
+ - `addPks(key: TKey, pks: readonly TPk[]): void` - Add primary keys to index key
1487
+ - `removePks(key: TKey, pks: readonly TPk[]): void` - Remove primary keys from index key
1488
+ - `setSlots(key: TKey, slots: TOIMAnyEntitySlot<TPk>[]): void` - Set canonical slots directly
1489
+ - `clear(key?: TKey): void` - Clear all keys or specific key
1490
+
1491
+ #### `OIMIndexManualOrderedArrayBased<TKey, TPk>`
1492
+ Slot-first manual ordered Array-based index with direct list operations and update events. Use this when you need low-level ordered storage without a command stream.
1493
+
1494
+ **Constructor:**
1495
+ ```typescript
1496
+ new OIMIndexManualOrderedArrayBased<TKey, TPk>()
1497
+ ```
1498
+
1499
+ **Methods:**
1500
+ - `pushSlot(key: TKey, slot: TOIMAnyEntitySlot<TPk>): number` - Append a slot and return its inserted index
1501
+ - `insertSlotAt(key: TKey, index: number, slot: TOIMAnyEntitySlot<TPk>): number` - Insert a slot at an index, clamped to the list bounds
1502
+ - `removeAt(key: TKey, index: number): TOIMAnyEntitySlot<TPk> | undefined` - Remove and return the slot at an index
1503
+ - `move(key: TKey, fromIndex: number, toIndex: number): TOIMAnyEntitySlot<TPk> | undefined` - Move a slot within the list
1504
+ - `resetSlots(key: TKey, slots: readonly TOIMAnyEntitySlot<TPk>[]): void` - Replace the whole ordered slot list for a key
1505
+ - `clear(key?: TKey): void` - Clear all keys or a specific key
1506
+
1507
+ **Query:**
1508
+ - `getPksByKey(key: TKey): TPk[]` - Returns the ordered primary-key array
1509
+ - `getSlotsByKey(key: TKey): readonly TOIMAnyEntitySlot<TPk>[]` - Returns the stored ordered slots
1510
+ - `getEntitiesByKey<TEntity>(key: TKey): TEntity[]` - Returns existing entities from stored slots
1511
+
1512
+ #### `OIMCollectionIndexManualOrderedArrayBased<TKey, TPk, TEntity>`
1513
+ Collection-bound ordered Array-based index. Use this when ordered PK writes should resolve to canonical slots from a collection.
1514
+
1515
+ **Methods:**
1516
+ - `push(key: TKey, pk: TPk): number` - Append a PK as its canonical slot
1517
+ - `insertAt(key: TKey, index: number, pk: TPk): number` - Insert a PK as its canonical slot
1518
+ - `reset(key: TKey, pks: readonly TPk[]): void` - Replace the whole ordered list from PKs
1519
+ - `getEntitiesByKey(key: TKey): TEntity[]` - Returns entities from canonical slots
1520
+
1521
+ #### `OIMOrderedListCommandStream<TKey, TPk, TEntity>`
1522
+ Slot-first ordered per-key list with a command stream for imperative consumers. It stores data in an `OIMIndexManualOrderedArrayBased` and emits `TOIMOrderedListCommand` batches through `commandsEventEmitter`.
1523
+
1524
+ **Constructor:**
1525
+ ```typescript
1526
+ new OIMOrderedListCommandStream(
1527
+ queue: OIMEventQueue,
1528
+ index?: OIMIndexManualOrderedArrayBased<TKey, TPk>
1529
+ )
1530
+ ```
1531
+
1532
+ **Properties:**
1533
+ - `index: OIMIndexManualOrderedArrayBased<TKey, TPk>` - Underlying ordered index and source of truth
1534
+ - `commandsEventEmitter: OIMUpdateEventEmitter<TKey>` - Key-specific command notifications delivered after queue flush
1535
+
1536
+ **Methods:**
1537
+ - `setSlots(key: TKey, slots: readonly TOIMEntitySlot<TEntity, TPk>[]): void` - Replace the whole ordered list and emit a `set` command
1538
+ - `pushSlot(key: TKey, slot: TOIMEntitySlot<TEntity, TPk>): void` - Append a slot and emit an `insert` command
1539
+ - `insertSlotAt(key: TKey, index: number, slot: TOIMEntitySlot<TEntity, TPk>): void` - Insert a slot and emit an `insert` command
1540
+ - `removeAt(key: TKey, index: number): void` - Remove by index and emit a `remove` command
1541
+ - `move(key: TKey, fromIndex: number, toIndex: number): void` - Move within the list and emit a `move` command
1542
+ - `consumeCommands(key: TKey): TOIMOrderedListCommand<TPk, TEntity>[]` - Read buffered commands for a key inside the notification handler
1543
+ - `getBufferedCommands(key: TKey): readonly TOIMOrderedListCommand<TPk, TEntity>[]` - Peek at buffered commands without clearing them
1544
+ - `getPksByKey(key: TKey): readonly TPk[]` - Read the current ordered list
1545
+ - `getSlotsByKey(key: TKey): readonly TOIMEntitySlot<TEntity, TPk>[]` - Read current ordered slots
1546
+ - `getEntitiesByKey(key: TKey): TEntity[]` - Read current ordered entities
1547
+ - `clear(key?: TKey): void` - Clear all keys or a specific key
1548
+ - `destroy(): void` - Dispose subscriptions and clear state
1549
+
1550
+ #### `OIMCollectionOrderedListCommandStream<TKey, TPk, TEntity>`
1551
+ Collection-bound ordered-list command stream. Public writes use PKs, commands include both `pk`/`pks` and canonical `slot`/`slots`.
1552
+
1553
+ **Methods:**
1554
+ - `set(key: TKey, pks: readonly TPk[]): void` - Replace the whole ordered list from PKs
1555
+ - `push(key: TKey, pk: TPk): void` - Append a PK
1556
+ - `insertAt(key: TKey, index: number, pk: TPk): void` - Insert a PK
1557
+ - all read/command methods from `OIMOrderedListCommandStream`
1558
+
1041
1559
  #### `OIMEventQueue`
1042
1560
  Event processing queue with configurable scheduling.
1043
1561