@oimdb/react 1.3.0 → 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 +79 -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,16 @@
|
|
|
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
|
+
## For AI Assistants
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
If you are an AI assistant, read the full API reference before writing any OIMDB code:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```
|
|
10
|
+
node_modules/@oimdb/core/llms.txt
|
|
11
|
+
```
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
- **Index Type Support**: Separate hooks for SetBased indexes (return `Set<TPk>`) and ArrayBased indexes (return `TPk[]`)
|
|
13
|
-
- **Automatic Subscription**: Uses `useSyncExternalStore` for optimal React 18+ performance
|
|
14
|
-
- **Event Coalescing**: Leverages OIMDB's built-in event coalescing for efficient updates
|
|
15
|
-
- **Type Safety**: Full TypeScript support with advanced generic type inference
|
|
16
|
-
- **Context Support**: Optional React Context for centralized collection management
|
|
17
|
-
- **Flexible Usage**: Use hooks directly or through context provider pattern
|
|
13
|
+
CDN fallback: `https://cdn.jsdelivr.net/npm/@oimdb/core/llms.txt`
|
|
18
14
|
|
|
19
15
|
## Installation
|
|
20
16
|
|
|
@@ -22,769 +18,131 @@ React integration for OIMDB - Hooks for selection and subscription with reactive
|
|
|
22
18
|
npm install @oimdb/react @oimdb/core
|
|
23
19
|
```
|
|
24
20
|
|
|
25
|
-
##
|
|
21
|
+
## Model
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
Collections own entities. Indexes, command streams, and other derived state live next to collections in your application schema.
|
|
28
24
|
|
|
29
25
|
```typescript
|
|
30
|
-
import {
|
|
31
|
-
OIMEventQueue,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
OIMReactiveIndexManualArrayBased
|
|
26
|
+
import {
|
|
27
|
+
OIMEventQueue,
|
|
28
|
+
OIMReactiveCollection,
|
|
29
|
+
OIMReactiveCollectionIndexManualSetBased,
|
|
35
30
|
} 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
|
-
|
|
97
|
-
#### SetBased Indexes (returns Set)
|
|
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 {
|
|
172
|
-
id: string;
|
|
173
|
-
name: string;
|
|
174
|
-
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
31
|
|
|
214
|
-
|
|
32
|
+
type User = {
|
|
215
33
|
id: string;
|
|
216
|
-
name: string;
|
|
217
34
|
teamId: string;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
interface Team {
|
|
221
|
-
id: string;
|
|
222
35
|
name: string;
|
|
223
|
-
}
|
|
36
|
+
};
|
|
224
37
|
|
|
225
|
-
// Create collections
|
|
226
38
|
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
39
|
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
indexes: {},
|
|
40
|
+
const users = new OIMReactiveCollection<User, string>(queue, {
|
|
41
|
+
selectPk: (user) => user.id,
|
|
236
42
|
});
|
|
237
43
|
|
|
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
|
-
}
|
|
44
|
+
const indexes = {
|
|
45
|
+
usersByTeam: new OIMReactiveCollectionIndexManualSetBased<
|
|
46
|
+
string,
|
|
47
|
+
string,
|
|
48
|
+
User
|
|
49
|
+
>(queue, { collection: users }),
|
|
50
|
+
};
|
|
254
51
|
```
|
|
255
52
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
For larger applications or when you need more control, create explicit type definitions similar to Redux's approach:
|
|
53
|
+
The React context stores collections only:
|
|
259
54
|
|
|
260
55
|
```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
|
-
}
|
|
56
|
+
import { OIMCollectionsProvider } from '@oimdb/react';
|
|
307
57
|
|
|
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
|
-
```
|
|
58
|
+
const collections = { users };
|
|
326
59
|
|
|
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
60
|
function App() {
|
|
354
|
-
const collections = React.useMemo(() => createCollections(), []);
|
|
355
|
-
|
|
356
61
|
return (
|
|
357
|
-
<
|
|
358
|
-
<
|
|
359
|
-
</
|
|
62
|
+
<OIMCollectionsProvider collections={collections}>
|
|
63
|
+
<UsersList />
|
|
64
|
+
</OIMCollectionsProvider>
|
|
360
65
|
);
|
|
361
66
|
}
|
|
362
67
|
```
|
|
363
68
|
|
|
364
|
-
|
|
69
|
+
Keep indexes in your module, app schema, props, or a separate context:
|
|
365
70
|
|
|
366
71
|
```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
|
-
```
|
|
72
|
+
import {
|
|
73
|
+
useOIMCollectionsContext,
|
|
74
|
+
useSelectEntitiesByIndexKeySetBased,
|
|
75
|
+
} from '@oimdb/react';
|
|
387
76
|
|
|
388
|
-
|
|
77
|
+
type AppCollections = typeof collections;
|
|
389
78
|
|
|
390
|
-
|
|
79
|
+
function TeamUsers({ teamId }: { teamId: string }) {
|
|
80
|
+
const { users } = useOIMCollectionsContext<AppCollections>();
|
|
391
81
|
|
|
392
|
-
|
|
393
|
-
|
|
82
|
+
const teamUsers = useSelectEntitiesByIndexKeySetBased(
|
|
83
|
+
users,
|
|
84
|
+
indexes.usersByTeam,
|
|
85
|
+
teamId
|
|
86
|
+
);
|
|
394
87
|
|
|
395
|
-
function UserProvider({ children }: { children: React.ReactNode }) {
|
|
396
|
-
const collections = React.useMemo(() => ({ users: usersCollection }), []);
|
|
397
|
-
|
|
398
88
|
return (
|
|
399
|
-
<
|
|
400
|
-
{
|
|
401
|
-
|
|
89
|
+
<ul>
|
|
90
|
+
{teamUsers?.map((user) =>
|
|
91
|
+
user ? <li key={user.id}>{user.name}</li> : null
|
|
92
|
+
)}
|
|
93
|
+
</ul>
|
|
402
94
|
);
|
|
403
95
|
}
|
|
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
96
|
```
|
|
595
97
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
Hooks automatically subscribe to OIMDB reactive events using `useSyncExternalStore`:
|
|
98
|
+
## Hooks
|
|
599
99
|
|
|
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
|
|
100
|
+
### Collections
|
|
604
101
|
|
|
605
|
-
|
|
102
|
+
- `useSelectEntityByPk(collection, pk)` watches one entity.
|
|
103
|
+
- `useSelectEntitiesByPks(collection, pks)` watches multiple entities.
|
|
606
104
|
|
|
607
|
-
|
|
105
|
+
### Set-Based Indexes
|
|
608
106
|
|
|
609
|
-
-
|
|
610
|
-
-
|
|
107
|
+
- `useSelectPksByIndexKeySetBased(index, key)` watches PKs for one index key.
|
|
108
|
+
- `useSelectPksByIndexKeysSetBased(index, keys)` watches PKs for several index keys.
|
|
109
|
+
- `useSelectEntitiesByIndexKeySetBased(collection, index, key)` watches entities referenced by one index key.
|
|
110
|
+
- `useSelectEntitiesByIndexKeysSetBased(collection, index, keys)` watches entities referenced by several index keys.
|
|
611
111
|
|
|
612
|
-
|
|
112
|
+
### Array-Based Indexes
|
|
613
113
|
|
|
614
|
-
|
|
114
|
+
- `useSelectPksByIndexKeyArrayBased(index, key)` watches PKs for one index key.
|
|
115
|
+
- `useSelectPksByIndexKeysArrayBased(index, keys)` watches PKs for several index keys.
|
|
116
|
+
- `useSelectEntitiesByIndexKeyArrayBased(collection, index, key)` watches entities referenced by one index key.
|
|
117
|
+
- `useSelectEntitiesByIndexKeysArrayBased(collection, index, keys)` watches entities referenced by several index keys.
|
|
615
118
|
|
|
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
|
|
119
|
+
### Reactive Objects
|
|
621
120
|
|
|
622
|
-
|
|
121
|
+
- `useSelectValueByObjectKey(object, key)` watches one object key.
|
|
122
|
+
- `useSelectValuesByObjectKeys(object, keys)` watches several object keys.
|
|
623
123
|
|
|
624
|
-
|
|
124
|
+
## Context API
|
|
625
125
|
|
|
626
126
|
```typescript
|
|
627
|
-
import
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
OIMReactiveIndexManualSetBased,
|
|
632
|
-
OIMReactiveIndexManualArrayBased
|
|
633
|
-
} from '@oimdb/core';
|
|
634
|
-
import {
|
|
635
|
-
useSelectEntityByPk,
|
|
636
|
-
useSelectEntitiesByPks,
|
|
637
|
-
useSelectEntitiesByIndexKeySetBased,
|
|
638
|
-
useSelectEntitiesByIndexKeyArrayBased
|
|
127
|
+
import {
|
|
128
|
+
OIMCollectionsProvider,
|
|
129
|
+
createOIMCollectionsContext,
|
|
130
|
+
useOIMCollectionsContext,
|
|
639
131
|
} 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
132
|
```
|
|
689
133
|
|
|
690
|
-
|
|
134
|
+
`OIMCollectionsProvider` accepts a dictionary of `OIMReactiveCollection` instances. Use `typeof collections` to preserve exact collection types:
|
|
691
135
|
|
|
692
136
|
```typescript
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
} from '@oimdb/react';
|
|
137
|
+
const collections = {
|
|
138
|
+
users,
|
|
139
|
+
};
|
|
697
140
|
|
|
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
|
-
}
|
|
141
|
+
type AppCollections = typeof collections;
|
|
710
142
|
|
|
711
|
-
function
|
|
712
|
-
const { users } = useOIMCollectionsContext();
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
return <div>Total Users: {allUsers.length}</div>;
|
|
143
|
+
function UserName({ userId }: { userId: string }) {
|
|
144
|
+
const { users } = useOIMCollectionsContext<AppCollections>();
|
|
145
|
+
const user = useSelectEntityByPk(users, userId);
|
|
146
|
+
return <span>{user?.name}</span>;
|
|
716
147
|
}
|
|
717
148
|
```
|
|
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
|