@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 +562 -0
- package/dist/index.cjs +1186 -0
- package/dist/index.d.cts +588 -0
- package/dist/index.d.ts +588 -0
- package/dist/index.js +1130 -0
- package/package.json +39 -0
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.
|