@oimdb/react 1.3.0 → 2.0.1
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 +81 -721
- package/dist/index.cjs +176 -34
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +174 -32
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# @oimdb/react
|
|
2
2
|
|
|
3
|
-
React
|
|
3
|
+
React hooks and context helpers for OIMDB reactive collections, objects, and indexes.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
📖 **[Full documentation](https://abaikov.github.io/oimdb/)**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## For AI Assistants
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
If you are an AI assistant, read the full API reference before writing any OIMDB code:
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **Context Support**: Optional React Context for centralized collection management
|
|
17
|
-
- **Flexible Usage**: Use hooks directly or through context provider pattern
|
|
11
|
+
```
|
|
12
|
+
node_modules/@oimdb/core/llms.txt
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
CDN fallback: `https://cdn.jsdelivr.net/npm/@oimdb/core/llms.txt`
|
|
18
16
|
|
|
19
17
|
## Installation
|
|
20
18
|
|
|
@@ -22,769 +20,131 @@ React integration for OIMDB - Hooks for selection and subscription with reactive
|
|
|
22
20
|
npm install @oimdb/react @oimdb/core
|
|
23
21
|
```
|
|
24
22
|
|
|
25
|
-
##
|
|
23
|
+
## Model
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
Collections own entities. Indexes, command streams, and other derived state live next to collections in your application schema.
|
|
28
26
|
|
|
29
27
|
```typescript
|
|
30
|
-
import {
|
|
31
|
-
OIMEventQueue,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
OIMReactiveIndexManualArrayBased
|
|
28
|
+
import {
|
|
29
|
+
OIMEventQueue,
|
|
30
|
+
OIMReactiveCollection,
|
|
31
|
+
OIMReactiveCollectionIndexManualSetBased,
|
|
35
32
|
} from '@oimdb/core';
|
|
36
|
-
import {
|
|
37
|
-
useSelectEntitiesByPks,
|
|
38
|
-
useSelectEntitiesByIndexKeySetBased,
|
|
39
|
-
useSelectEntitiesByIndexKeyArrayBased,
|
|
40
|
-
useSelectEntityByPk
|
|
41
|
-
} from '@oimdb/react';
|
|
42
|
-
|
|
43
|
-
// Create event queue and reactive collections
|
|
44
|
-
const queue = new OIMEventQueue();
|
|
45
|
-
|
|
46
|
-
// Choose index type based on your needs:
|
|
47
|
-
// - SetBased: for frequent add/remove operations, order doesn't matter
|
|
48
|
-
// - ArrayBased: for full replacements or when order/sorting matters
|
|
49
|
-
const userTeamIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
|
|
50
|
-
const deckCardsIndex = new OIMReactiveIndexManualArrayBased<string, string>(queue);
|
|
51
|
-
|
|
52
|
-
const usersCollection = new OIMRICollection(queue, {
|
|
53
|
-
collectionOpts: { selectPk: (user: User) => user.id },
|
|
54
|
-
indexes: { byTeam: userTeamIndex },
|
|
55
|
-
});
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Single Entity Selection
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
function UserProfile({ userId }: { userId: string }) {
|
|
62
|
-
const user = useSelectEntityByPk(usersCollection, userId);
|
|
63
|
-
|
|
64
|
-
if (!user) return <div>Loading...</div>;
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div>
|
|
68
|
-
<h2>{user.name}</h2>
|
|
69
|
-
<p>Email: {user.email}</p>
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Multiple Entities Selection
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
function UserList({ userIds }: { userIds: string[] }) {
|
|
79
|
-
const users = useSelectEntitiesByPks(usersCollection, userIds);
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<ul>
|
|
83
|
-
{users.map((user, index) => (
|
|
84
|
-
<li key={user?.id || index}>
|
|
85
|
-
{user ? user.name : 'Loading...'}
|
|
86
|
-
</li>
|
|
87
|
-
))}
|
|
88
|
-
</ul>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Index-based Selection
|
|
94
|
-
|
|
95
|
-
OIMDB provides separate hooks for SetBased and ArrayBased indexes:
|
|
96
33
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
import { useSelectEntitiesByIndexKeySetBased, useSelectPksByIndexKeySetBased } from '@oimdb/react';
|
|
101
|
-
|
|
102
|
-
function TeamMembers({ teamId }: { teamId: string }) {
|
|
103
|
-
// For SetBased indexes, use SetBased hooks
|
|
104
|
-
const teamUsers = useSelectEntitiesByIndexKeySetBased(
|
|
105
|
-
usersCollection,
|
|
106
|
-
usersCollection.indexes.byTeam, // OIMReactiveIndexManualSetBased
|
|
107
|
-
teamId
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
// Or get just the PKs as Set
|
|
111
|
-
const teamUserIds = useSelectPksByIndexKeySetBased(
|
|
112
|
-
usersCollection.indexes.byTeam,
|
|
113
|
-
teamId
|
|
114
|
-
); // Returns Set<string>
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<div>
|
|
118
|
-
{teamUsers.map((user, index) => (
|
|
119
|
-
<div key={user?.id || index}>
|
|
120
|
-
{user ? `${user.name} (${user.role})` : 'Loading...'}
|
|
121
|
-
</div>
|
|
122
|
-
))}
|
|
123
|
-
</div>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
#### ArrayBased Indexes (returns Array)
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
import { useSelectEntitiesByIndexKeyArrayBased, useSelectPksByIndexKeyArrayBased } from '@oimdb/react';
|
|
132
|
-
|
|
133
|
-
function DeckCards({ deckId }: { deckId: string }) {
|
|
134
|
-
// For ArrayBased indexes, use ArrayBased hooks
|
|
135
|
-
const cards = useSelectEntitiesByIndexKeyArrayBased(
|
|
136
|
-
cardsCollection,
|
|
137
|
-
cardsCollection.indexes.byDeck, // OIMReactiveIndexManualArrayBased
|
|
138
|
-
deckId
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
// Or get just the PKs as Array (preserves order)
|
|
142
|
-
const cardIds = useSelectPksByIndexKeyArrayBased(
|
|
143
|
-
cardsCollection.indexes.byDeck,
|
|
144
|
-
deckId
|
|
145
|
-
); // Returns string[] (preserves order/sorting)
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
<div>
|
|
149
|
-
{cards.map((card, index) => (
|
|
150
|
-
<div key={card?.id || index}>
|
|
151
|
-
{card ? `${index + 1}. ${card.name}` : 'Loading...'}
|
|
152
|
-
</div>
|
|
153
|
-
))}
|
|
154
|
-
</div>
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## React Context Integration
|
|
160
|
-
|
|
161
|
-
For applications with multiple collections, use the React Context pattern for centralized management:
|
|
162
|
-
|
|
163
|
-
### Context Setup
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
import {
|
|
167
|
-
OIMRICollectionsProvider,
|
|
168
|
-
useOIMCollectionsContext
|
|
169
|
-
} from '@oimdb/react';
|
|
170
|
-
|
|
171
|
-
interface User {
|
|
34
|
+
type User = {
|
|
172
35
|
id: string;
|
|
173
|
-
name: string;
|
|
174
36
|
teamId: string;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
interface Team {
|
|
178
|
-
id: string;
|
|
179
|
-
name: string;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function createCollections() {
|
|
183
|
-
const queue = new OIMEventQueue();
|
|
184
|
-
|
|
185
|
-
// Use SetBased for frequent add/remove operations
|
|
186
|
-
const userTeamIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
|
|
187
|
-
const usersCollection = new OIMRICollection(queue, {
|
|
188
|
-
collectionOpts: { selectPk: (user: User) => user.id },
|
|
189
|
-
indexes: { byTeam: userTeamIndex },
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const teamsCollection = new OIMRICollection(queue, {
|
|
193
|
-
collectionOpts: { selectPk: (team: Team) => team.id },
|
|
194
|
-
indexes: {},
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
return { users: usersCollection, teams: teamsCollection } as const;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
type AppCollections = ReturnType<typeof createCollections>;
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## TypeScript Typing Strategies
|
|
204
|
-
|
|
205
|
-
For maximum type safety, you should properly type your collections dictionary. There are two main approaches, similar to how Redux handles state typing:
|
|
206
|
-
|
|
207
|
-
### Approach 1: Using `typeof` (Recommended for Simple Cases)
|
|
208
|
-
|
|
209
|
-
The simplest approach is to use TypeScript's `typeof` operator to infer types from your collection instances:
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
import { OIMEventQueue, OIMRICollection, OIMReactiveIndexManual } from '@oimdb/core';
|
|
213
|
-
|
|
214
|
-
interface User {
|
|
215
|
-
id: string;
|
|
216
37
|
name: string;
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
interface Team {
|
|
221
|
-
id: string;
|
|
222
|
-
name: string;
|
|
223
|
-
}
|
|
38
|
+
};
|
|
224
39
|
|
|
225
|
-
// Create collections
|
|
226
40
|
const queue = new OIMEventQueue();
|
|
227
|
-
const userTeamIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
|
|
228
|
-
const usersCollection = new OIMRICollection(queue, {
|
|
229
|
-
collectionOpts: { selectPk: (user: User) => user.id },
|
|
230
|
-
indexes: { byTeam: userTeamIndex },
|
|
231
|
-
});
|
|
232
41
|
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
indexes: {},
|
|
42
|
+
const users = new OIMReactiveCollection<User, string>(queue, {
|
|
43
|
+
selectPk: (user) => user.id,
|
|
236
44
|
});
|
|
237
45
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
type AppCollections = typeof collections;
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
**Usage:**
|
|
249
|
-
```typescript
|
|
250
|
-
function MyComponent() {
|
|
251
|
-
const { users, teams } = useOIMCollectionsContext<AppCollections>();
|
|
252
|
-
// users and teams are fully typed with all their generics preserved
|
|
253
|
-
}
|
|
46
|
+
const indexes = {
|
|
47
|
+
usersByTeam: new OIMReactiveCollectionIndexManualSetBased<
|
|
48
|
+
string,
|
|
49
|
+
string,
|
|
50
|
+
User
|
|
51
|
+
>(queue, { collection: users }),
|
|
52
|
+
};
|
|
254
53
|
```
|
|
255
54
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
For larger applications or when you need more control, create explicit type definitions similar to Redux's approach:
|
|
55
|
+
The React context stores collections only:
|
|
259
56
|
|
|
260
57
|
```typescript
|
|
261
|
-
import {
|
|
262
|
-
OIMEventQueue,
|
|
263
|
-
OIMRICollection,
|
|
264
|
-
OIMReactiveIndexManualSetBased
|
|
265
|
-
} from '@oimdb/core';
|
|
266
|
-
import type {
|
|
267
|
-
TOIMPk,
|
|
268
|
-
OIMIndexSetBased,
|
|
269
|
-
OIMReactiveIndexSetBased
|
|
270
|
-
} from '@oimdb/core';
|
|
271
|
-
|
|
272
|
-
interface User {
|
|
273
|
-
id: string;
|
|
274
|
-
name: string;
|
|
275
|
-
teamId: string;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
interface Team {
|
|
279
|
-
id: string;
|
|
280
|
-
name: string;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Define your collection types explicitly
|
|
284
|
-
type UserCollection = OIMRICollection<
|
|
285
|
-
User,
|
|
286
|
-
string,
|
|
287
|
-
'byTeam',
|
|
288
|
-
string,
|
|
289
|
-
OIMIndexSetBased<string, string>,
|
|
290
|
-
OIMReactiveIndexSetBased<string, string, OIMIndexSetBased<string, string>>
|
|
291
|
-
>;
|
|
292
|
-
|
|
293
|
-
type TeamCollection = OIMRICollection<
|
|
294
|
-
Team,
|
|
295
|
-
string,
|
|
296
|
-
never,
|
|
297
|
-
never,
|
|
298
|
-
OIMIndexSetBased<never, never>,
|
|
299
|
-
OIMReactiveIndexSetBased<never, never, OIMIndexSetBased<never, never>>
|
|
300
|
-
>;
|
|
301
|
-
|
|
302
|
-
// Define your collections dictionary type
|
|
303
|
-
interface AppCollections {
|
|
304
|
-
users: UserCollection;
|
|
305
|
-
teams: TeamCollection;
|
|
306
|
-
}
|
|
58
|
+
import { OIMCollectionsProvider } from '@oimdb/react';
|
|
307
59
|
|
|
308
|
-
|
|
309
|
-
function createCollections(): AppCollections {
|
|
310
|
-
const queue = new OIMEventQueue();
|
|
311
|
-
const userTeamIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
users: new OIMRICollection(queue, {
|
|
315
|
-
collectionOpts: { selectPk: (user: User) => user.id },
|
|
316
|
-
indexes: { byTeam: userTeamIndex },
|
|
317
|
-
}) as UserCollection,
|
|
318
|
-
|
|
319
|
-
teams: new OIMRICollection(queue, {
|
|
320
|
-
collectionOpts: { selectPk: (team: Team) => team.id },
|
|
321
|
-
indexes: {},
|
|
322
|
-
}) as TeamCollection,
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
```
|
|
60
|
+
const collections = { users };
|
|
326
61
|
|
|
327
|
-
**Usage:**
|
|
328
|
-
```typescript
|
|
329
|
-
function MyComponent() {
|
|
330
|
-
const { users, teams } = useOIMCollectionsContext<AppCollections>();
|
|
331
|
-
// Full type safety with explicit types
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### When to Use Each Approach
|
|
336
|
-
|
|
337
|
-
- **Use `typeof`** when:
|
|
338
|
-
- You have simple collection setups
|
|
339
|
-
- You want TypeScript to infer everything automatically
|
|
340
|
-
- You prefer less boilerplate
|
|
341
|
-
- Your collections are created in one place
|
|
342
|
-
|
|
343
|
-
- **Use explicit types** when:
|
|
344
|
-
- You need to share types across multiple files
|
|
345
|
-
- You want to document your data structure explicitly
|
|
346
|
-
- You're building a library or shared module
|
|
347
|
-
- You need to ensure type consistency across your application
|
|
348
|
-
- You prefer Redux-style explicit typing patterns
|
|
349
|
-
|
|
350
|
-
### Provider Setup
|
|
351
|
-
|
|
352
|
-
```typescript
|
|
353
62
|
function App() {
|
|
354
|
-
const collections = React.useMemo(() => createCollections(), []);
|
|
355
|
-
|
|
356
63
|
return (
|
|
357
|
-
<
|
|
358
|
-
<
|
|
359
|
-
</
|
|
64
|
+
<OIMCollectionsProvider collections={collections}>
|
|
65
|
+
<UsersList />
|
|
66
|
+
</OIMCollectionsProvider>
|
|
360
67
|
);
|
|
361
68
|
}
|
|
362
69
|
```
|
|
363
70
|
|
|
364
|
-
|
|
71
|
+
Keep indexes in your module, app schema, props, or a separate context:
|
|
365
72
|
|
|
366
73
|
```typescript
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const allUsers = useSelectEntitiesByPks(users, []);
|
|
372
|
-
// Use appropriate hook based on index type
|
|
373
|
-
const teamMembers = useSelectEntitiesByIndexKeySetBased(
|
|
374
|
-
users,
|
|
375
|
-
users.indexes.byTeam, // SetBased index
|
|
376
|
-
'team1'
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
return (
|
|
380
|
-
<div>
|
|
381
|
-
<h2>All Users: {allUsers.length}</h2>
|
|
382
|
-
<h3>Team 1 Members: {teamMembers.length}</h3>
|
|
383
|
-
</div>
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
```
|
|
74
|
+
import {
|
|
75
|
+
useOIMCollectionsContext,
|
|
76
|
+
useSelectEntitiesByIndexKeySetBased,
|
|
77
|
+
} from '@oimdb/react';
|
|
387
78
|
|
|
388
|
-
|
|
79
|
+
type AppCollections = typeof collections;
|
|
389
80
|
|
|
390
|
-
|
|
81
|
+
function TeamUsers({ teamId }: { teamId: string }) {
|
|
82
|
+
const { users } = useOIMCollectionsContext<AppCollections>();
|
|
391
83
|
|
|
392
|
-
|
|
393
|
-
|
|
84
|
+
const teamUsers = useSelectEntitiesByIndexKeySetBased(
|
|
85
|
+
users,
|
|
86
|
+
indexes.usersByTeam,
|
|
87
|
+
teamId
|
|
88
|
+
);
|
|
394
89
|
|
|
395
|
-
function UserProvider({ children }: { children: React.ReactNode }) {
|
|
396
|
-
const collections = React.useMemo(() => ({ users: usersCollection }), []);
|
|
397
|
-
|
|
398
90
|
return (
|
|
399
|
-
<
|
|
400
|
-
{
|
|
401
|
-
|
|
91
|
+
<ul>
|
|
92
|
+
{teamUsers?.map((user) =>
|
|
93
|
+
user ? <li key={user.id}>{user.name}</li> : null
|
|
94
|
+
)}
|
|
95
|
+
</ul>
|
|
402
96
|
);
|
|
403
97
|
}
|
|
404
|
-
|
|
405
|
-
function UserComponent() {
|
|
406
|
-
const { users } = useOIMCollectionsContext(UserContext);
|
|
407
|
-
// Use users collection...
|
|
408
|
-
}
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
## API Reference
|
|
412
|
-
|
|
413
|
-
### `useSelectEntityByPk(reactiveCollection, pk)`
|
|
414
|
-
|
|
415
|
-
Subscribes to a single entity from a reactive collection.
|
|
416
|
-
|
|
417
|
-
**Parameters:**
|
|
418
|
-
- `reactiveCollection: OIMReactiveCollection<TEntity, TPk>` - Reactive collection instance
|
|
419
|
-
- `pk: TPk` - Primary key of the entity
|
|
420
|
-
|
|
421
|
-
**Returns:**
|
|
422
|
-
- `TEntity | undefined` - Entity data or undefined if not found
|
|
423
|
-
|
|
424
|
-
### `useSelectEntitiesByPks(reactiveCollection, pks)`
|
|
425
|
-
|
|
426
|
-
Subscribes to multiple entities from a reactive collection.
|
|
427
|
-
|
|
428
|
-
**Parameters:**
|
|
429
|
-
- `reactiveCollection: OIMReactiveCollection<TEntity, TPk>` - Reactive collection instance
|
|
430
|
-
- `pks: readonly TPk[]` - Array of primary keys
|
|
431
|
-
|
|
432
|
-
**Returns:**
|
|
433
|
-
- `(TEntity | undefined)[]` - Array of entities (undefined for missing entities)
|
|
434
|
-
|
|
435
|
-
### Index-based Selection Hooks
|
|
436
|
-
|
|
437
|
-
OIMDB provides separate hooks for SetBased and ArrayBased indexes to ensure type safety and correct return types.
|
|
438
|
-
|
|
439
|
-
#### SetBased Index Hooks
|
|
440
|
-
|
|
441
|
-
##### `useSelectEntitiesByIndexKeySetBased(reactiveCollection, reactiveIndex, key)`
|
|
442
|
-
|
|
443
|
-
Subscribes to entities indexed by a specific key from a SetBased index.
|
|
444
|
-
|
|
445
|
-
**Parameters:**
|
|
446
|
-
- `reactiveCollection: OIMReactiveCollection<TEntity, TPk>` - Reactive collection instance
|
|
447
|
-
- `reactiveIndex: OIMReactiveIndexSetBased<TKey, TPk, TIndex>` - SetBased reactive index instance
|
|
448
|
-
- `key: TKey` - Index key to query
|
|
449
|
-
|
|
450
|
-
**Returns:**
|
|
451
|
-
- `(TEntity | undefined)[]` - Array of entities for the given index key
|
|
452
|
-
|
|
453
|
-
##### `useSelectEntitiesByIndexKeysSetBased(reactiveCollection, reactiveIndex, keys)`
|
|
454
|
-
|
|
455
|
-
Subscribes to entities indexed by multiple keys from a SetBased index.
|
|
456
|
-
|
|
457
|
-
**Parameters:**
|
|
458
|
-
- `reactiveCollection: OIMReactiveCollection<TEntity, TPk>` - Reactive collection instance
|
|
459
|
-
- `reactiveIndex: OIMReactiveIndexSetBased<TKey, TPk, TIndex>` - SetBased reactive index instance
|
|
460
|
-
- `keys: readonly TKey[]` - Array of index keys to query
|
|
461
|
-
|
|
462
|
-
**Returns:**
|
|
463
|
-
- `(TEntity | undefined)[]` - Array of entities for the given index keys
|
|
464
|
-
|
|
465
|
-
##### `useSelectPksByIndexKeySetBased(reactiveIndex, key)`
|
|
466
|
-
|
|
467
|
-
Subscribes to primary keys indexed by a specific key from a SetBased index.
|
|
468
|
-
|
|
469
|
-
**Parameters:**
|
|
470
|
-
- `reactiveIndex: OIMReactiveIndexSetBased<TKey, TPk, TIndex>` - SetBased reactive index instance
|
|
471
|
-
- `key: TKey` - Index key to query
|
|
472
|
-
|
|
473
|
-
**Returns:**
|
|
474
|
-
- `Set<TPk>` - Set of primary keys for the given index key
|
|
475
|
-
|
|
476
|
-
##### `useSelectPksByIndexKeysSetBased(reactiveIndex, keys)`
|
|
477
|
-
|
|
478
|
-
Subscribes to primary keys indexed by multiple keys from a SetBased index.
|
|
479
|
-
|
|
480
|
-
**Parameters:**
|
|
481
|
-
- `reactiveIndex: OIMReactiveIndexSetBased<TKey, TPk, TIndex>` - SetBased reactive index instance
|
|
482
|
-
- `keys: readonly TKey[]` - Array of index keys to query
|
|
483
|
-
|
|
484
|
-
**Returns:**
|
|
485
|
-
- `Map<TKey, Set<TPk>>` - Map of index keys to their corresponding primary key Sets
|
|
486
|
-
|
|
487
|
-
#### ArrayBased Index Hooks
|
|
488
|
-
|
|
489
|
-
##### `useSelectEntitiesByIndexKeyArrayBased(reactiveCollection, reactiveIndex, key)`
|
|
490
|
-
|
|
491
|
-
Subscribes to entities indexed by a specific key from an ArrayBased index.
|
|
492
|
-
|
|
493
|
-
**Parameters:**
|
|
494
|
-
- `reactiveCollection: OIMReactiveCollection<TEntity, TPk>` - Reactive collection instance
|
|
495
|
-
- `reactiveIndex: OIMReactiveIndexArrayBased<TKey, TPk, TIndex>` - ArrayBased reactive index instance
|
|
496
|
-
- `key: TKey` - Index key to query
|
|
497
|
-
|
|
498
|
-
**Returns:**
|
|
499
|
-
- `(TEntity | undefined)[]` - Array of entities for the given index key (preserves order)
|
|
500
|
-
|
|
501
|
-
##### `useSelectEntitiesByIndexKeysArrayBased(reactiveCollection, reactiveIndex, keys)`
|
|
502
|
-
|
|
503
|
-
Subscribes to entities indexed by multiple keys from an ArrayBased index.
|
|
504
|
-
|
|
505
|
-
**Parameters:**
|
|
506
|
-
- `reactiveCollection: OIMReactiveCollection<TEntity, TPk>` - Reactive collection instance
|
|
507
|
-
- `reactiveIndex: OIMReactiveIndexArrayBased<TKey, TPk, TIndex>` - ArrayBased reactive index instance
|
|
508
|
-
- `keys: readonly TKey[]` - Array of index keys to query
|
|
509
|
-
|
|
510
|
-
**Returns:**
|
|
511
|
-
- `(TEntity | undefined)[]` - Array of entities for the given index keys (preserves order)
|
|
512
|
-
|
|
513
|
-
##### `useSelectPksByIndexKeyArrayBased(reactiveIndex, key)`
|
|
514
|
-
|
|
515
|
-
Subscribes to primary keys indexed by a specific key from an ArrayBased index.
|
|
516
|
-
|
|
517
|
-
**Parameters:**
|
|
518
|
-
- `reactiveIndex: OIMReactiveIndexArrayBased<TKey, TPk, TIndex>` - ArrayBased reactive index instance
|
|
519
|
-
- `key: TKey` - Index key to query
|
|
520
|
-
|
|
521
|
-
**Returns:**
|
|
522
|
-
- `TPk[]` - Array of primary keys for the given index key (preserves order/sorting)
|
|
523
|
-
|
|
524
|
-
##### `useSelectPksByIndexKeysArrayBased(reactiveIndex, keys)`
|
|
525
|
-
|
|
526
|
-
Subscribes to primary keys indexed by multiple keys from an ArrayBased index.
|
|
527
|
-
|
|
528
|
-
**Parameters:**
|
|
529
|
-
- `reactiveIndex: OIMReactiveIndexArrayBased<TKey, TPk, TIndex>` - ArrayBased reactive index instance
|
|
530
|
-
- `keys: readonly TKey[]` - Array of index keys to query
|
|
531
|
-
|
|
532
|
-
**Returns:**
|
|
533
|
-
- `Map<TKey, TPk[]>` - Map of index keys to their corresponding primary key arrays (preserves order)
|
|
534
|
-
|
|
535
|
-
## Context API Reference
|
|
536
|
-
|
|
537
|
-
### `OIMRICollectionsProvider<T>`
|
|
538
|
-
|
|
539
|
-
Provider component for collections context.
|
|
540
|
-
|
|
541
|
-
**Props:**
|
|
542
|
-
- `collections: T` - Dictionary of reactive collections
|
|
543
|
-
- `children: ReactNode` - React children
|
|
544
|
-
- `context?: React.Context<OIMContextValue<T>>` - Optional custom context
|
|
545
|
-
|
|
546
|
-
### `useOIMCollectionsContext<T>(context?)`
|
|
547
|
-
|
|
548
|
-
Hook to access collections from context.
|
|
549
|
-
|
|
550
|
-
**Parameters:**
|
|
551
|
-
- `context?: React.Context<OIMContextValue<T>>` - Optional custom context
|
|
552
|
-
|
|
553
|
-
**Returns:**
|
|
554
|
-
- `T` - Collections dictionary with full type safety
|
|
555
|
-
|
|
556
|
-
**Throws:**
|
|
557
|
-
- Error if used outside of provider
|
|
558
|
-
|
|
559
|
-
### `createOIMCollectionsContext<T>()`
|
|
560
|
-
|
|
561
|
-
Creates a custom collections context with specific typing.
|
|
562
|
-
|
|
563
|
-
**Returns:**
|
|
564
|
-
- `React.Context<OIMContextValue<T>>` - Typed React context
|
|
565
|
-
|
|
566
|
-
### Type Utilities
|
|
567
|
-
|
|
568
|
-
#### `CollectionsDictionary`
|
|
569
|
-
|
|
570
|
-
Base type for any collections dictionary. Use `typeof` to extract types from your collection instances, or define explicit types using `OIMRICollection` generics.
|
|
571
|
-
|
|
572
|
-
## Architecture
|
|
573
|
-
|
|
574
|
-
### Reactive Collections Integration
|
|
575
|
-
|
|
576
|
-
The hooks work directly with OIMDB reactive objects:
|
|
577
|
-
|
|
578
|
-
```typescript
|
|
579
|
-
// Use reactive collections and indexes directly
|
|
580
|
-
const user = useSelectEntityByPk(reactiveCollection, 'user123');
|
|
581
|
-
|
|
582
|
-
// Use appropriate hook based on index type
|
|
583
|
-
const posts = useSelectEntitiesByIndexKeySetBased(
|
|
584
|
-
reactiveCollection,
|
|
585
|
-
reactiveIndexSetBased, // SetBased index
|
|
586
|
-
'tech'
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
const orderedCards = useSelectEntitiesByIndexKeyArrayBased(
|
|
590
|
-
reactiveCollection,
|
|
591
|
-
reactiveIndexArrayBased, // ArrayBased index
|
|
592
|
-
'deck1'
|
|
593
|
-
);
|
|
594
98
|
```
|
|
595
99
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
Hooks automatically subscribe to OIMDB reactive events using `useSyncExternalStore`:
|
|
100
|
+
## Hooks
|
|
599
101
|
|
|
600
|
-
|
|
601
|
-
- **Index updates**: Subscribe to `reactiveIndex.updateEventEmitter`
|
|
602
|
-
- **Optimized subscriptions**: Subscribe only to specific keys for efficient updates
|
|
603
|
-
- **Automatic cleanup**: Unsubscribe when component unmounts
|
|
102
|
+
### Collections
|
|
604
103
|
|
|
605
|
-
|
|
104
|
+
- `useSelectEntityByPk(collection, pk)` watches one entity.
|
|
105
|
+
- `useSelectEntitiesByPks(collection, pks)` watches multiple entities.
|
|
606
106
|
|
|
607
|
-
|
|
107
|
+
### Set-Based Indexes
|
|
608
108
|
|
|
609
|
-
-
|
|
610
|
-
-
|
|
109
|
+
- `useSelectPksByIndexKeySetBased(index, key)` watches PKs for one index key.
|
|
110
|
+
- `useSelectPksByIndexKeysSetBased(index, keys)` watches PKs for several index keys.
|
|
111
|
+
- `useSelectEntitiesByIndexKeySetBased(collection, index, key)` watches entities referenced by one index key.
|
|
112
|
+
- `useSelectEntitiesByIndexKeysSetBased(collection, index, keys)` watches entities referenced by several index keys.
|
|
611
113
|
|
|
612
|
-
|
|
114
|
+
### Array-Based Indexes
|
|
613
115
|
|
|
614
|
-
|
|
116
|
+
- `useSelectPksByIndexKeyArrayBased(index, key)` watches PKs for one index key.
|
|
117
|
+
- `useSelectPksByIndexKeysArrayBased(index, keys)` watches PKs for several index keys.
|
|
118
|
+
- `useSelectEntitiesByIndexKeyArrayBased(collection, index, key)` watches entities referenced by one index key.
|
|
119
|
+
- `useSelectEntitiesByIndexKeysArrayBased(collection, index, keys)` watches entities referenced by several index keys.
|
|
615
120
|
|
|
616
|
-
|
|
617
|
-
- **Event Coalescing**: OIMDB's built-in event coalescing reduces unnecessary re-renders
|
|
618
|
-
- **Key-specific subscriptions**: Only listen to changes for relevant data
|
|
619
|
-
- **Memory Management**: Automatic cleanup prevents memory leaks
|
|
620
|
-
- **Efficient batching**: Updates are batched through React's concurrent features
|
|
121
|
+
### Reactive Objects
|
|
621
122
|
|
|
622
|
-
|
|
123
|
+
- `useSelectValueByObjectKey(object, key)` watches one object key.
|
|
124
|
+
- `useSelectValuesByObjectKeys(object, keys)` watches several object keys.
|
|
623
125
|
|
|
624
|
-
|
|
126
|
+
## Context API
|
|
625
127
|
|
|
626
128
|
```typescript
|
|
627
|
-
import
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
OIMReactiveIndexManualSetBased,
|
|
632
|
-
OIMReactiveIndexManualArrayBased
|
|
633
|
-
} from '@oimdb/core';
|
|
634
|
-
import {
|
|
635
|
-
useSelectEntityByPk,
|
|
636
|
-
useSelectEntitiesByPks,
|
|
637
|
-
useSelectEntitiesByIndexKeySetBased,
|
|
638
|
-
useSelectEntitiesByIndexKeyArrayBased
|
|
129
|
+
import {
|
|
130
|
+
OIMCollectionsProvider,
|
|
131
|
+
createOIMCollectionsContext,
|
|
132
|
+
useOIMCollectionsContext,
|
|
639
133
|
} from '@oimdb/react';
|
|
640
|
-
|
|
641
|
-
interface User {
|
|
642
|
-
id: string;
|
|
643
|
-
name: string;
|
|
644
|
-
email: string;
|
|
645
|
-
teamId: string;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// Setup
|
|
649
|
-
function createUserCollection() {
|
|
650
|
-
const queue = new OIMEventQueue();
|
|
651
|
-
// Use SetBased for frequent add/remove operations
|
|
652
|
-
const teamIndex = new OIMReactiveIndexManualSetBased<string, string>(queue);
|
|
653
|
-
|
|
654
|
-
return new OIMRICollection(queue, {
|
|
655
|
-
collectionOpts: { selectPk: (user: User) => user.id },
|
|
656
|
-
indexes: { byTeam: teamIndex },
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
const usersCollection = createUserCollection();
|
|
661
|
-
|
|
662
|
-
// Component
|
|
663
|
-
function UserProfile({ userId }: { userId: string }) {
|
|
664
|
-
const user = useSelectEntityByPk(usersCollection, userId);
|
|
665
|
-
|
|
666
|
-
if (!user) return <div>Loading...</div>;
|
|
667
|
-
|
|
668
|
-
return <h2>{user.name}</h2>;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function TeamDashboard({ teamId }: { teamId: string }) {
|
|
672
|
-
// Use SetBased hook for SetBased index
|
|
673
|
-
const teamMembers = useSelectEntitiesByIndexKeySetBased(
|
|
674
|
-
usersCollection,
|
|
675
|
-
usersCollection.indexes.byTeam, // OIMReactiveIndexManualSetBased
|
|
676
|
-
teamId
|
|
677
|
-
);
|
|
678
|
-
|
|
679
|
-
return (
|
|
680
|
-
<div>
|
|
681
|
-
<h3>Team Members ({teamMembers.length})</h3>
|
|
682
|
-
{teamMembers.map(user => (
|
|
683
|
-
<div key={user?.id}>{user?.name}</div>
|
|
684
|
-
))}
|
|
685
|
-
</div>
|
|
686
|
-
);
|
|
687
|
-
}
|
|
688
134
|
```
|
|
689
135
|
|
|
690
|
-
|
|
136
|
+
`OIMCollectionsProvider` accepts a dictionary of `OIMReactiveCollection` instances. Use `typeof collections` to preserve exact collection types:
|
|
691
137
|
|
|
692
138
|
```typescript
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
} from '@oimdb/react';
|
|
139
|
+
const collections = {
|
|
140
|
+
users,
|
|
141
|
+
};
|
|
697
142
|
|
|
698
|
-
|
|
699
|
-
const collections = React.useMemo(() => ({
|
|
700
|
-
users: createUserCollection(),
|
|
701
|
-
// ... other collections
|
|
702
|
-
}), []);
|
|
703
|
-
|
|
704
|
-
return (
|
|
705
|
-
<OIMRICollectionsProvider collections={collections}>
|
|
706
|
-
<Dashboard />
|
|
707
|
-
</OIMRICollectionsProvider>
|
|
708
|
-
);
|
|
709
|
-
}
|
|
143
|
+
type AppCollections = typeof collections;
|
|
710
144
|
|
|
711
|
-
function
|
|
712
|
-
const { users } = useOIMCollectionsContext();
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
return <div>Total Users: {allUsers.length}</div>;
|
|
145
|
+
function UserName({ userId }: { userId: string }) {
|
|
146
|
+
const { users } = useOIMCollectionsContext<AppCollections>();
|
|
147
|
+
const user = useSelectEntityByPk(users, userId);
|
|
148
|
+
return <span>{user?.name}</span>;
|
|
716
149
|
}
|
|
717
150
|
```
|
|
718
|
-
|
|
719
|
-
## Migration from v0.x
|
|
720
|
-
|
|
721
|
-
The v1.x API has changed significantly to work with reactive collections:
|
|
722
|
-
|
|
723
|
-
### Hook Name Changes
|
|
724
|
-
|
|
725
|
-
```typescript
|
|
726
|
-
// v0.x - Abstract storage interfaces
|
|
727
|
-
const user = useEntity(userStorage, 'user123');
|
|
728
|
-
const users = useEntities(userStorage, userIds);
|
|
729
|
-
const posts = useIndex(postStorage, categoryIndex, 'tech');
|
|
730
|
-
|
|
731
|
-
// v1.x - Reactive collections with typed indexes
|
|
732
|
-
const user = useSelectEntityByPk(reactiveCollection, 'user123');
|
|
733
|
-
const users = useSelectEntitiesByPks(reactiveCollection, userIds);
|
|
734
|
-
// Use appropriate hook based on index type
|
|
735
|
-
const posts = useSelectEntitiesByIndexKeySetBased(
|
|
736
|
-
reactiveCollection,
|
|
737
|
-
reactiveIndexSetBased,
|
|
738
|
-
'tech'
|
|
739
|
-
);
|
|
740
|
-
const orderedItems = useSelectEntitiesByIndexKeyArrayBased(
|
|
741
|
-
reactiveCollection,
|
|
742
|
-
reactiveIndexArrayBased,
|
|
743
|
-
'category1'
|
|
744
|
-
);
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
### Collection Creation
|
|
748
|
-
|
|
749
|
-
```typescript
|
|
750
|
-
// v0.x - With DX layer
|
|
751
|
-
const db = createDb({ scheduler: 'microtask' });
|
|
752
|
-
const users = db.createCollection<User>();
|
|
753
|
-
const user = useEntity(users.advanced.collection, userId);
|
|
754
|
-
|
|
755
|
-
// v1.x - Direct reactive collections
|
|
756
|
-
const queue = new OIMEventQueue();
|
|
757
|
-
const usersCollection = new OIMRICollection(queue, {
|
|
758
|
-
collectionOpts: { selectPk: (user: User) => user.id },
|
|
759
|
-
indexes: {},
|
|
760
|
-
});
|
|
761
|
-
const user = useSelectEntityByPk(usersCollection, userId);
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
### Context API
|
|
765
|
-
|
|
766
|
-
```typescript
|
|
767
|
-
// v0.x - No context support
|
|
768
|
-
|
|
769
|
-
// v1.x - Full context support
|
|
770
|
-
const collections = { users: usersCollection };
|
|
771
|
-
<OIMRICollectionsProvider collections={collections}>
|
|
772
|
-
<App />
|
|
773
|
-
</OIMRICollectionsProvider>
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
### Key Changes
|
|
777
|
-
|
|
778
|
-
- **Hook naming**: More explicit names like `useSelectEntityByPk` vs `useEntity`
|
|
779
|
-
- **Parameters**: Direct reactive collection objects instead of storage abstractions
|
|
780
|
-
- **Context**: New context API for centralized collection management
|
|
781
|
-
- **Type safety**: Enhanced TypeScript support with better inference
|
|
782
|
-
|
|
783
|
-
## Dependencies
|
|
784
|
-
|
|
785
|
-
- `@oimdb/core` - Core OIMDB functionality
|
|
786
|
-
- `react` - React hooks and components
|
|
787
|
-
|
|
788
|
-
## License
|
|
789
|
-
|
|
790
|
-
MIT
|