@oimdb/core 3.5.0 → 3.6.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 +46 -46
- package/dist/index.cjs +1317 -207
- package/dist/index.d.cts +669 -65
- package/dist/index.d.ts +669 -65
- package/dist/index.js +1292 -206
- package/llms.txt +54 -7
- package/package.json +1 -1
package/llms.txt
CHANGED
|
@@ -28,9 +28,13 @@ All reactive updates are batched. Mutations accumulate, then `queue.flush()` del
|
|
|
28
28
|
import { OIMEventQueue, OIMEventQueueSchedulerImmediate } from '@oimdb/core';
|
|
29
29
|
|
|
30
30
|
const queue = new OIMEventQueue({
|
|
31
|
-
scheduler: new OIMEventQueueSchedulerImmediate(), //
|
|
31
|
+
scheduler: new OIMEventQueueSchedulerImmediate(), // macrotask: setImmediate (Node), fallback MessageChannel / setTimeout(0) (browser)
|
|
32
32
|
});
|
|
33
|
-
// or:
|
|
33
|
+
// or: OIMEventQueueSchedulerMicrotask (next microtask, before paint — lowest latency),
|
|
34
|
+
// OIMEventQueueSchedulerAnimationFrame, OIMEventQueueSchedulerTimeout
|
|
35
|
+
// Async schedulers coalesce; call queue.flush() directly for sync delivery (e.g. tests).
|
|
36
|
+
// OIMEventQueueSchedulerSync (createSync()) flushes synchronously per write, NO batching —
|
|
37
|
+
// debug/diagnostic only: swap in at the root to test if a bug is coalescing-related.
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
### Collections
|
|
@@ -84,6 +88,32 @@ Membership writes are incremental (touch only the changed pks):
|
|
|
84
88
|
- `setPks` / `addPks` resolve each pk → slot (O(1) lookup); if you already hold slots (`collection.upsertMany` returns them) use `setSlots` / `appendSlots` to skip the lookup.
|
|
85
89
|
- `getEntitiesByKey(key)` returns `(TEntity | undefined)[]` — positional holes for not-yet-loaded pks, aligned with `getPksByKey` (not dropped), so you can render a loading state.
|
|
86
90
|
|
|
91
|
+
### Global (keyless / whole-collection) indexes
|
|
92
|
+
|
|
93
|
+
When you need ONE index/ordered list over the **whole collection** ("all") there is no meaningful key. Use a **Global** index instead of inventing a phantom key like `'all'`. Same two shapes (array-based ordered / set-based unordered) and two modes (manual / derived), but keyless: `getPks()` / `getSlots()` / `getEntities()` take no key, and `subscribe(handler)` replaces `subscribeOnKey`. Internally a Global index holds ONE bucket with a single-carrier emitter (no key→carrier map / hashing), so it is strictly cheaper than a keyed index with one key.
|
|
94
|
+
|
|
95
|
+
Classes mirror the keyed ones with `Global` inserted: `OIMGlobalIndexManual{Array,Set}Based` (raw slots), `OIMReactiveGlobalIndexManual{Array,Set}Based`, `OIMReactiveCollectionGlobalIndexManual{Array,Set}Based` (pk writes), `OIMDerivedCollectionGlobalIndex{Array,Set}Based`.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// Manual, collection-bound (pk writes) — e.g. a server-provided page order
|
|
99
|
+
const list = indexFactory.arrayBasedGlobalIndex();
|
|
100
|
+
list.setPks(['u2', 'u1']); // also addPks / removePks (no key)
|
|
101
|
+
list.getPks(); // ['u2', 'u1']
|
|
102
|
+
list.subscribe(() => { /* one bucket changed */ });
|
|
103
|
+
queue.flush();
|
|
104
|
+
|
|
105
|
+
// Derived — auto-tracks the whole collection, ordered
|
|
106
|
+
const recent = indexFactory.derivedArrayGlobalIndex({ orderBy: (u) => u.createdAt });
|
|
107
|
+
recent.getEntities(); // (User | undefined)[] in order, kept in sync
|
|
108
|
+
|
|
109
|
+
// Derived set — whole-collection membership; `filter` narrows it
|
|
110
|
+
const active = indexFactory.derivedSetGlobalIndex({ filter: (u) => !u.archived });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- Derived options: `orderBy` / `compareEntities` (array only) for order, `filter: (e) => boolean` to include a subset (default: entity exists), `buildInitial` (default true).
|
|
114
|
+
- Selectors: `select.entitiesByArrayGlobalIndex(list)` / `select.entitiesBySetGlobalIndex(set)` → `(TEntity | undefined)[]` (no key).
|
|
115
|
+
- React: `useSelectEntitiesByGlobalIndex{Array,Set}Based(collection, index)`, `useSelectPksByGlobalIndex{Array,Set}Based(index)`, plus `…Signal` variants — all drop the `key` argument.
|
|
116
|
+
|
|
87
117
|
### Ordered list command stream
|
|
88
118
|
|
|
89
119
|
For an ordered per-key list that emits **position-addressed commands** (insert / remove / move / set / reset) instead of a new array to diff — for imperative renderers that apply edits in place. `OIMCollectionOrderedListCommandStream` (pk writes, via `indexFactory.orderedList()`) or `OIMOrderedListCommandStream` (slot writes). Commands are not in the event payload — subscribe, then pull synchronously.
|
|
@@ -104,19 +134,36 @@ stream.getEntitiesByKey('main'); // (TEntity | undefined)[] — holes preserved
|
|
|
104
134
|
|
|
105
135
|
`count` defaults to 1 (read `count ?? 1`). A whole-list change collapses to a single `reset`. Reads: `getPksByKey` / `getSlotsByKey` / `getEntitiesByKey`.
|
|
106
136
|
|
|
107
|
-
**Mapping the list to your own elements** — `createOIMOrderedListMappedCommandStream(source,
|
|
137
|
+
**Mapping the list to your own elements** — `createOIMOrderedListMappedCommandStream(source, create)` projects element-wise: the same commands with `item` replaced by `create(slot)`'s result (a DOM node, view model, …). `create` is a plain function (no options object). Identity is positional — a `move` reuses the **same** mapped instance (never recreated); `create` runs on insert/set/reset/initial build. No teardown callback: the leaving element IS the `remove`/`set`/`reset` command the consumer applies against its own mirror, so cleanup belongs there. It needs no queue (rides the source's batching) and is itself an `IOIMOrderedListCommandSource`, so it consumes like the raw stream and chains via `.map(create)`. New exports: `IOIMOrderedListCommandSource`, `OIMOrderedListMappedCommandStream`, `createOIMOrderedListMappedCommandStream`.
|
|
108
138
|
|
|
109
139
|
```ts
|
|
110
|
-
const nodes = createOIMOrderedListMappedCommandStream(stream,
|
|
111
|
-
|
|
112
|
-
|
|
140
|
+
const nodes = createOIMOrderedListMappedCommandStream(stream, (slot) => engine.makeNode(slot.item));
|
|
141
|
+
nodes.subscribeCommands('main', () => {
|
|
142
|
+
for (const cmd of nodes.consumeCommands('main')) {
|
|
143
|
+
// cmd.item is your node; tear it down on remove/set/reset from your own mirror
|
|
144
|
+
}
|
|
113
145
|
});
|
|
114
|
-
nodes.subscribeCommands('main', () => engine.apply(nodes.consumeCommands('main'))); // cmd.item is your node
|
|
115
146
|
nodes.getItemsByKey('main'); // current mapped elements — initial render
|
|
116
147
|
```
|
|
117
148
|
|
|
118
149
|
> Content updates (entity fields changed) are not list commands — they arrive via the collection's per-pk subscription. The stream carries structure only.
|
|
119
150
|
|
|
151
|
+
**Deriving commands from an index** — the stream above is written imperatively. To instead get commands from an index whose order is maintained automatically (`derivedArrayIndex`, or any reactive array-based index replaced via `setPks`), use `createOIMOrderedListCommandStreamDiffDriven(queue, index, opts?)` / `OIMOrderedListCommandStreamDiffDriven`. On each per-key index change it diffs the previous order against the new one and emits the `insert`/`move`/`remove` that transforms it — identity by pk (a reorder is a `move` of the same slot, never a recreate), delivered on `AFTER_FLUSH`. An order-preserving change emits nothing. `opts.resetThreshold` (default `0` = always diff): below that fraction of shared pks it emits one `reset` instead of many edits. Output is an `IOIMOrderedListCommandSource`, so `createOIMOrderedListMappedCommandStream` composes on top to carry your own nodes. New exports: `OIMOrderedListCommandStreamDiffDriven`, `createOIMOrderedListCommandStreamDiffDriven`, `TOIMOrderedListDiffOptions`, `diffOrderedListByPk` (pure diff), `OIMOrderedListCommandBuffer` (shared abstract base of both streams).
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const cardsByDeck = cards.indexFactory.derivedArrayIndex((c) => c.deckId, { orderBy: (c) => c.position });
|
|
155
|
+
const stream = createOIMOrderedListCommandStreamDiffDriven(queue, cardsByDeck);
|
|
156
|
+
stream.subscribeCommands('deck1', () => engine.apply(stream.consumeCommands('deck1'))); // insert/move/remove
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Mapping a plain index to stable objects** — for an ordinary set/array index (no commands), `createOIMIndexSlotMap(index, create)` / `OIMIndexSlotMap` memoizes `create(slot)` by slot identity (a `WeakMap`): the same slot returns the same object reference on every read, so `React.memo` / `Object.is` diffing works and view-models are reused. Same plain-function shape as the ordered mapper, no `destroy` (plain values, GC reclaims). `getByKey(key)` → mapped objects; `subscribeOnKey`/`subscribeOnKeys` passthrough to the index. New exports: `OIMIndexSlotMap`, `createOIMIndexSlotMap`, `IOIMIndexSlotSource`.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const usersByTeam = users.indexFactory.derivedSetIndex((u) => u.teamId);
|
|
163
|
+
const rows = createOIMIndexSlotMap(usersByTeam, (slot) => makeRow(slot.item));
|
|
164
|
+
rows.subscribeOnKey('team1', () => { for (const row of rows.getByKey('team1')) { /* stable per slot */ } });
|
|
165
|
+
```
|
|
166
|
+
|
|
120
167
|
### DX Layer — Collection Model
|
|
121
168
|
|
|
122
169
|
`createOIMCollectionKit` wires queue + collection + index factory + selectors together.
|