@oimdb/core 1.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 ADDED
@@ -0,0 +1,562 @@
1
+ # @oimdb/core
2
+
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
+
5
+ ## ๐Ÿš€ Installation
6
+
7
+ ```bash
8
+ npm install @oimdb/core
9
+ ```
10
+
11
+ ## ๐Ÿ“ฆ What's Included
12
+
13
+ This package exports all the core classes, interfaces, and types needed to build reactive in-memory database solutions:
14
+
15
+ ### Core Classes
16
+ - **OIMReactiveCollection**: Reactive entity storage with automatic change notifications
17
+ - **OIMRICollection**: Reactive collection with integrated indexing capabilities
18
+ - **OIMReactiveIndexManual**: Reactive index with manual key-to-entity mapping
19
+ - **OIMEventQueue**: Configurable event processing queue with scheduler integration
20
+ - **OIMCollection**: Base collection with CRUD operations and event emission
21
+
22
+ ### Event System
23
+ - **OIMUpdateEventEmitter**: Key-specific event subscriptions with coalescing
24
+ - **OIMUpdateEventCoalescer**: Intelligent event batching and deduplication
25
+ - **OIMEventEmitter**: Generic type-safe event emitter
26
+ - **Schedulers**: Multiple event processing strategies (microtask, timeout, animationFrame, immediate)
27
+
28
+ ### Storage & Indexing
29
+ - **OIMCollectionStoreMapDriven**: Map-based storage backend
30
+ - **OIMIndexManual**: Manual index management with custom comparison logic
31
+ - **OIMMap2Keys**: Two-key mapping utilities for complex indexing
32
+
33
+ ### Abstract Classes & Interfaces
34
+ - **OIMCollectionStore**: Storage backend interface
35
+ - **OIMEventQueueScheduler**: Event processing scheduler interface
36
+ - **OIMIndex**: Base index interface
37
+ - **OIMReactiveIndex**: Reactive index interface
38
+
39
+ ### Types & Enums
40
+ - **TOIM\***: Generic types for collections, indices, events, and schedulers
41
+ - **EOIM\***: Enums for event types and scheduler types
42
+ - **IOIM\***: Interfaces for event handlers and scheduler events
43
+
44
+ ## ๐Ÿ”ง Basic Usage
45
+
46
+ ### Creating a Reactive Collection
47
+
48
+ ```typescript
49
+ import {
50
+ OIMReactiveCollection,
51
+ OIMEventQueue,
52
+ OIMEventQueueSchedulerFactory
53
+ } from '@oimdb/core';
54
+
55
+ interface User {
56
+ id: string;
57
+ name: string;
58
+ email: string;
59
+ }
60
+
61
+ // Create event queue with microtask scheduler
62
+ const queue = new OIMEventQueue({
63
+ scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
64
+ });
65
+
66
+ // Create reactive collection
67
+ const users = new OIMReactiveCollection<User, string>(queue, {
68
+ collectionOpts: {
69
+ selectPk: (user) => user.id
70
+ }
71
+ });
72
+
73
+ // Subscribe to key-specific updates
74
+ users.updateEventEmitter.subscribeOnKey('user1', () => {
75
+ console.log('User1 changed!');
76
+ });
77
+
78
+ // Subscribe to multiple keys
79
+ users.updateEventEmitter.subscribeOnKeys(['user1', 'user2'], () => {
80
+ console.log('Users changed!');
81
+ });
82
+
83
+ // CRUD operations
84
+ users.upsertOne({ id: 'user1', name: 'John Doe', email: 'john@example.com' });
85
+ users.upsertMany([
86
+ { id: 'user2', name: 'Jane Smith', email: 'jane@example.com' },
87
+ { id: 'user3', name: 'Bob Wilson', email: 'bob@example.com' }
88
+ ]);
89
+
90
+ // Query operations
91
+ const user = users.getOneByPk('user1');
92
+ const multipleUsers = users.getManyByPks(['user1', 'user2']);
93
+ ```
94
+
95
+ ### Creating a Reactive Index
96
+
97
+ ```typescript
98
+ import { OIMReactiveIndexManual, OIMEventQueue } from '@oimdb/core';
99
+
100
+ // Create reactive index for user roles
101
+ const queue = new OIMEventQueue({
102
+ scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
103
+ });
104
+
105
+ const userRoleIndex = new OIMReactiveIndexManual<string, string>(queue);
106
+
107
+ // Subscribe to specific index key changes
108
+ userRoleIndex.updateEventEmitter.subscribeOnKey('admin', (pks) => {
109
+ console.log('Admin users changed:', pks);
110
+ });
111
+
112
+ // Build the index manually
113
+ userRoleIndex.setPks('admin', ['user1']);
114
+ userRoleIndex.setPks('user', ['user2', 'user3']);
115
+
116
+ // Add more users to existing roles
117
+ userRoleIndex.addPks('admin', ['user2']);
118
+
119
+ // Query the index
120
+ const adminUsers = userRoleIndex.index.getPksByKey('admin'); // ['user1', 'user2']
121
+ const regularUsers = userRoleIndex.index.getPksByKey('user'); // ['user2', 'user3']
122
+
123
+ // Remove users from roles
124
+ userRoleIndex.removePks('admin', ['user1']);
125
+ ```
126
+
127
+ ### Event Queue and Schedulers
128
+
129
+ ```typescript
130
+ import {
131
+ OIMEventQueue,
132
+ OIMEventQueueSchedulerFactory,
133
+ TOIMSchedulerType
134
+ } from '@oimdb/core';
135
+
136
+ // Create event queues with different schedulers
137
+ const microtaskQueue = new OIMEventQueue({
138
+ scheduler: OIMEventQueueSchedulerFactory.create('microtask')
139
+ });
140
+
141
+ const timeoutQueue = new OIMEventQueue({
142
+ scheduler: OIMEventQueueSchedulerFactory.create('timeout', { delay: 100 })
143
+ });
144
+
145
+ const animationFrameQueue = new OIMEventQueue({
146
+ scheduler: OIMEventQueueSchedulerFactory.create('animationFrame')
147
+ });
148
+
149
+ const immediateQueue = new OIMEventQueue({
150
+ scheduler: OIMEventQueueSchedulerFactory.create('immediate')
151
+ });
152
+
153
+ // Manual queue operations
154
+ const manualQueue = new OIMEventQueue(); // No scheduler
155
+
156
+ manualQueue.enqueue(() => console.log('Task 1'));
157
+ manualQueue.enqueue(() => console.log('Task 2'));
158
+
159
+ // Manually flush when ready
160
+ manualQueue.flush();
161
+
162
+ // Queue introspection
163
+ console.log('Queue length:', manualQueue.length);
164
+ console.log('Is empty:', manualQueue.isEmpty);
165
+ ```
166
+
167
+ ## ๐Ÿ—๏ธ Advanced Usage
168
+
169
+ ### Reactive Collection with Indexes (OIMRICollection)
170
+
171
+ ```typescript
172
+ import {
173
+ OIMRICollection,
174
+ OIMReactiveIndexManual,
175
+ OIMEventQueue,
176
+ OIMEventQueueSchedulerFactory
177
+ } from '@oimdb/core';
178
+
179
+ interface User {
180
+ id: string;
181
+ name: string;
182
+ email: string;
183
+ teamId: string;
184
+ role: 'admin' | 'user';
185
+ }
186
+
187
+ // Create event queue
188
+ const queue = new OIMEventQueue({
189
+ scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
190
+ });
191
+
192
+ // Create indexes
193
+ const teamIndex = new OIMReactiveIndexManual<string, string>(queue);
194
+ const roleIndex = new OIMReactiveIndexManual<string, string>(queue);
195
+
196
+ // Create collection with indexes
197
+ const users = new OIMRICollection(queue, {
198
+ collectionOpts: {
199
+ selectPk: (user: User) => user.id
200
+ },
201
+ indexes: {
202
+ byTeam: teamIndex,
203
+ byRole: roleIndex
204
+ }
205
+ });
206
+
207
+ // Subscribe to index changes
208
+ users.indexes.byTeam.updateEventEmitter.subscribeOnKey('engineering', (pks) => {
209
+ console.log('Engineering team changed:', pks);
210
+ });
211
+
212
+ // Add users and update indexes
213
+ users.upsertMany([
214
+ { id: 'u1', name: 'John', email: 'john@test.com', teamId: 'engineering', role: 'admin' },
215
+ { id: 'u2', name: 'Jane', email: 'jane@test.com', teamId: 'engineering', role: 'user' }
216
+ ]);
217
+
218
+ // Update indexes manually
219
+ users.indexes.byTeam.setPks('engineering', ['u1', 'u2']);
220
+ users.indexes.byRole.setPks('admin', ['u1']);
221
+ ```
222
+
223
+ ### Custom Entity Updater
224
+
225
+ ```typescript
226
+ import {
227
+ TOIMEntityUpdater,
228
+ OIMReactiveCollection,
229
+ OIMEventQueue
230
+ } from '@oimdb/core';
231
+
232
+ // Custom deep merge updater
233
+ const deepMergeUpdater: TOIMEntityUpdater<User> = (newEntity, oldEntity) => {
234
+ const result = { ...oldEntity };
235
+
236
+ for (const [key, value] of Object.entries(newEntity)) {
237
+ if (value !== undefined) {
238
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
239
+ result[key] = deepMergeUpdater(value, result[key] || {});
240
+ } else {
241
+ result[key] = value;
242
+ }
243
+ }
244
+ }
245
+
246
+ return result;
247
+ };
248
+
249
+ // Use custom updater with reactive collection
250
+ const queue = new OIMEventQueue();
251
+ const users = new OIMReactiveCollection<User, string>(queue, {
252
+ collectionOpts: {
253
+ selectPk: (user) => user.id,
254
+ updateEntity: deepMergeUpdater
255
+ }
256
+ });
257
+
258
+ // Now updates will use deep merge logic
259
+ users.upsertOne({ id: 'user1', name: 'John' });
260
+ users.upsertOne({ id: 'user1', email: 'john@example.com' }); // Merges with existing
261
+ ```
262
+
263
+ ### Event Coalescing and Update Subscriptions
264
+
265
+ ```typescript
266
+ import {
267
+ OIMReactiveCollection,
268
+ OIMEventQueue,
269
+ OIMEventQueueSchedulerFactory
270
+ } from '@oimdb/core';
271
+
272
+ // Create collection with microtask scheduler for coalescing
273
+ const queue = new OIMEventQueue({
274
+ scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
275
+ });
276
+
277
+ const users = new OIMReactiveCollection<User, string>(queue);
278
+
279
+ // Subscribe to coalesced updates for specific keys
280
+ users.updateEventEmitter.subscribeOnKey('user1', () => {
281
+ console.log('User1 updated (coalesced)');
282
+ });
283
+
284
+ // Multiple rapid updates to same key will be coalesced
285
+ users.upsertOne({ id: 'user1', name: 'John' });
286
+ users.upsertOne({ id: 'user1', name: 'John Doe' });
287
+ users.upsertOne({ id: 'user1', email: 'john@example.com' });
288
+
289
+ // Only one notification will fire (in next microtask)
290
+
291
+ // Access the underlying coalescer directly
292
+ users.coalescer.emitter.on('upsert', (coalescedUpdates) => {
293
+ console.log('Raw coalesced updates:', coalescedUpdates);
294
+ });
295
+ ```
296
+
297
+ ## ๐Ÿ”„ Reactive Architecture
298
+
299
+ ### Event-Driven Updates
300
+
301
+ OIMDB core uses a reactive architecture where changes automatically trigger notifications to subscribers:
302
+
303
+ ```typescript
304
+ // Collection updates trigger events through the event queue
305
+ collection.upsertOne(entity) โ†’ coalescer โ†’ event queue โ†’ subscribers
306
+
307
+ // Key-specific subscriptions only notify when relevant data changes
308
+ updateEventEmitter.subscribeOnKey('user1', callback) // Only fires for user1 changes
309
+ ```
310
+
311
+ ### Event Coalescing
312
+
313
+ Multiple rapid changes to the same entity are automatically coalesced:
314
+
315
+ ```typescript
316
+ // These three updates...
317
+ users.upsertOne({ id: 'user1', name: 'John' });
318
+ users.upsertOne({ id: 'user1', email: 'john@test.com' });
319
+ users.upsertOne({ id: 'user1', role: 'admin' });
320
+
321
+ // ...result in only one notification with the final state
322
+ // This prevents unnecessary re-renders and improves performance
323
+ ```
324
+
325
+ ### Scheduler Types
326
+
327
+ Choose the right scheduler for your use case:
328
+
329
+ - **`microtask`**: Most common - executes before next browser render
330
+ - **`timeout`**: Configurable delay for custom batching strategies
331
+ - **`animationFrame`**: Syncs with browser rendering (60fps)
332
+ - **`immediate`**: Fastest execution using platform-specific APIs
333
+
334
+ ### Reactive Collection Hierarchy
335
+
336
+ ```
337
+ OIMCollection (base)
338
+ โ”œโ”€โ”€ OIMReactiveCollection (adds event emitter + coalescing)
339
+ โ””โ”€โ”€ OIMRICollection (reactive collection + indexes)
340
+
341
+ OIMIndex (base)
342
+ โ”œโ”€โ”€ OIMIndexManual (manual index management)
343
+ โ””โ”€โ”€ OIMReactiveIndexManual (reactive index with event emitter)
344
+ ```
345
+
346
+ ## โšก Performance Characteristics
347
+
348
+ - **Collections**: O(1) primary key lookups using Map-based storage
349
+ - **Reactive Collections**: O(1) lookups + efficient event coalescing
350
+ - **Indices**: O(1) index lookups with lazy evaluation
351
+ - **Event System**: Smart coalescing prevents redundant notifications
352
+ - **Memory**: Efficient key-based subscriptions, no global listeners
353
+ - **Schedulers**: Configurable timing for optimal batching:
354
+ - **Microtask**: ~1-5ms delay, ideal for UI updates
355
+ - **Immediate**: <1ms, fastest execution
356
+ - **Timeout**: Custom delay for batching strategies
357
+ - **AnimationFrame**: 16ms, synced with 60fps rendering
358
+
359
+ ## ๐Ÿ”— Integration Patterns
360
+
361
+ ### With React (@oimdb/react)
362
+
363
+ The core library integrates seamlessly with React through dedicated hooks:
364
+
365
+ ```typescript
366
+ import { useSelectEntitiesByPks, selectEntityByPk } from '@oimdb/react';
367
+
368
+ // React hooks automatically subscribe to reactive collections
369
+ const user = selectEntityByPk(users, 'user1');
370
+ const teamUsers = useSelectEntitiesByPks(users, userIds);
371
+ ```
372
+
373
+ ### Standalone Usage
374
+
375
+ Use core classes directly for maximum control:
376
+
377
+ ```typescript
378
+ // Manual subscription management
379
+ const unsubscribe = users.updateEventEmitter.subscribeOnKey('user1', () => {
380
+ // Handle user1 changes
381
+ });
382
+
383
+ // Clean up when done
384
+ unsubscribe();
385
+ ```
386
+
387
+ ## ๐Ÿ“š API Reference
388
+
389
+ ### Core Classes
390
+
391
+ #### `OIMReactiveCollection<TEntity, TPk>`
392
+ Reactive collection with automatic change notifications and event coalescing.
393
+
394
+ **Constructor:**
395
+ ```typescript
396
+ new OIMReactiveCollection(queue: OIMEventQueue, opts?: {
397
+ collectionOpts?: TOIMCollectionOptions<TEntity, TPk>
398
+ })
399
+ ```
400
+
401
+ **Properties:**
402
+ - `collection: OIMCollection<TEntity, TPk>` - Underlying collection
403
+ - `updateEventEmitter: OIMUpdateEventEmitter<TPk>` - Key-specific subscriptions
404
+ - `coalescer: OIMUpdateEventCoalescerCollection<TPk>` - Event coalescing
405
+
406
+ **Methods:**
407
+ - `upsertOne(entity: TEntity): void` - Insert or update single entity
408
+ - `upsertMany(entities: TEntity[]): void` - Insert or update multiple entities
409
+ - `removeOne(entity: TEntity): void` - Remove single entity
410
+ - `removeMany(entities: TEntity[]): void` - Remove multiple entities
411
+ - `getOneByPk(pk: TPk): TEntity | undefined` - Get entity by primary key
412
+ - `getManyByPks(pks: readonly TPk[]): Map<TPk, TEntity | undefined>` - Get multiple entities
413
+
414
+ #### `OIMRICollection<TEntity, TPk, TIndexName, TIndexKey, TIndex, TReactiveIndex>`
415
+ Reactive collection with integrated indexing capabilities.
416
+
417
+ **Constructor:**
418
+ ```typescript
419
+ new OIMRICollection(queue: OIMEventQueue, opts: {
420
+ collectionOpts?: TOIMCollectionOptions<TEntity, TPk>;
421
+ indexes: Record<TIndexName, TReactiveIndex>;
422
+ })
423
+ ```
424
+
425
+ **Properties:**
426
+ - `indexes: Record<TIndexName, TReactiveIndex>` - Named reactive indexes
427
+ - *(inherits all OIMReactiveCollection properties)*
428
+
429
+ #### `OIMReactiveIndexManual<TKey, TPk>`
430
+ Reactive index with manual key-to-entity mapping and change notifications.
431
+
432
+ **Constructor:**
433
+ ```typescript
434
+ new OIMReactiveIndexManual(queue: OIMEventQueue, opts?: {
435
+ index?: OIMIndexManual<TKey, TPk>
436
+ })
437
+ ```
438
+
439
+ **Properties:**
440
+ - `index: OIMIndexManual<TKey, TPk>` - Underlying index
441
+ - `updateEventEmitter: OIMUpdateEventEmitter<TKey>` - Key-specific subscriptions
442
+
443
+ **Methods:**
444
+ - `setPks(key: TKey, pks: readonly TPk[]): void` - Set primary keys for index key
445
+ - `addPks(key: TKey, pks: readonly TPk[]): void` - Add primary keys to index key
446
+ - `removePks(key: TKey, pks: readonly TPk[]): void` - Remove primary keys from index key
447
+ - `clear(key?: TKey): void` - Clear all keys or specific key
448
+
449
+ #### `OIMEventQueue`
450
+ Event processing queue with configurable scheduling.
451
+
452
+ **Constructor:**
453
+ ```typescript
454
+ new OIMEventQueue(options?: TOIMEventQueueOptions)
455
+ ```
456
+
457
+ **Properties:**
458
+ - `length: number` - Number of queued functions
459
+ - `isEmpty: boolean` - Whether queue is empty
460
+
461
+ **Methods:**
462
+ - `enqueue(fn: () => void): void` - Add function to queue
463
+ - `flush(): void` - Execute all queued functions
464
+ - `clear(): void` - Clear queue without executing
465
+ - `destroy(): void` - Clean up scheduler subscriptions
466
+
467
+ ### Schedulers
468
+
469
+ #### `OIMEventQueueSchedulerFactory`
470
+ Factory for creating different scheduler types:
471
+
472
+ ```typescript
473
+ import { TOIMSchedulerType } from '@oimdb/core';
474
+
475
+ // Available scheduler types
476
+ type TOIMSchedulerType = 'immediate' | 'microtask' | 'timeout' | 'animationFrame';
477
+ ```
478
+
479
+ **Static Methods:**
480
+ - `create(type: 'microtask'): OIMEventQueueSchedulerMicrotask`
481
+ - `create(type: 'animationFrame'): OIMEventQueueSchedulerAnimationFrame`
482
+ - `create(type: 'timeout', options?: { delay: number }): OIMEventQueueSchedulerTimeout`
483
+ - `create(type: 'immediate'): OIMEventQueueSchedulerImmediate`
484
+ - `createMicrotask(): OIMEventQueueSchedulerMicrotask`
485
+ - `createAnimationFrame(): OIMEventQueueSchedulerAnimationFrame`
486
+ - `createTimeout(delay?: number): OIMEventQueueSchedulerTimeout`
487
+ - `createImmediate(): OIMEventQueueSchedulerImmediate`
488
+
489
+ ### Types
490
+
491
+ #### `TOIMCollectionOptions<TEntity, TPk>`
492
+ Collection configuration options:
493
+ - `selectPk?: TOIMPkSelector<TEntity, TPk>` - Primary key selector function
494
+ - `store?: OIMCollectionStore<TEntity, TPk>` - Storage backend
495
+ - `updateEntity?: TOIMEntityUpdater<TEntity>` - Entity update strategy
496
+
497
+ #### `TOIMEntityUpdater<TEntity>`
498
+ Entity update function signature:
499
+ ```typescript
500
+ (newEntity: TEntity, oldEntity: TEntity) => TEntity
501
+ ```
502
+
503
+ #### `TOIMSchedulerType`
504
+ Available scheduler types:
505
+ ```typescript
506
+ 'microtask' | 'animationFrame' | 'timeout' | 'immediate'
507
+ ```
508
+
509
+ #### `TOIMEventQueueOptions`
510
+ Event queue configuration:
511
+ - `scheduler?: OIMEventQueueScheduler` - Optional scheduler for automatic flushing
512
+
513
+ ## ๐Ÿงช Testing
514
+
515
+ ```typescript
516
+ import {
517
+ OIMReactiveCollection,
518
+ OIMEventQueue,
519
+ OIMEventQueueSchedulerFactory
520
+ } from '@oimdb/core';
521
+
522
+ describe('OIMReactiveCollection', () => {
523
+ let users: OIMReactiveCollection<User, string>;
524
+ let queue: OIMEventQueue;
525
+
526
+ beforeEach(() => {
527
+ queue = new OIMEventQueue({
528
+ scheduler: OIMEventQueueSchedulerFactory.createMicrotask()
529
+ });
530
+
531
+ users = new OIMReactiveCollection(queue, {
532
+ collectionOpts: {
533
+ selectPk: (user) => user.id
534
+ }
535
+ });
536
+ });
537
+
538
+ it('should upsert and retrieve entities', () => {
539
+ const user = { id: 'user1', name: 'John', email: 'john@example.com' };
540
+ users.upsertOne(user);
541
+
542
+ expect(users.getOneByPk('user1')).toEqual(user);
543
+ });
544
+
545
+ it('should notify subscribers of changes', (done) => {
546
+ users.updateEventEmitter.subscribeOnKey('user1', () => {
547
+ done(); // Test passes when callback is called
548
+ });
549
+
550
+ users.upsertOne({ id: 'user1', name: 'John' });
551
+ queue.flush(); // Trigger immediate flush for testing
552
+ });
553
+ });
554
+ ```
555
+
556
+ ## ๐Ÿค Contributing
557
+
558
+ This package is part of the OIMDB ecosystem. See the main project repository for contribution guidelines.
559
+
560
+ ## ๐Ÿ“„ License
561
+
562
+ MIT License - see LICENSE file for details.