@oimdb/core 3.3.0 → 3.5.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 +30 -13
- package/dist/index.cjs +309 -63
- package/dist/index.d.cts +192 -24
- package/dist/index.d.ts +192 -24
- package/dist/index.js +305 -62
- package/llms.txt +33 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -865,12 +865,15 @@ var OIMCollectionStoreMapDriven = class extends OIMCollectionStore {
|
|
|
865
865
|
}
|
|
866
866
|
};
|
|
867
867
|
|
|
868
|
+
// src/core/createMergeEntityUpdater.ts
|
|
869
|
+
function createMergeEntityUpdater() {
|
|
870
|
+
return (draft, prev) => ({ ...prev, ...draft });
|
|
871
|
+
}
|
|
872
|
+
|
|
868
873
|
// src/core/OIMEntityUpdaterFactory.ts
|
|
869
874
|
var OIMEntityUpdaterFactory = class {
|
|
870
875
|
createMergeEntityUpdater() {
|
|
871
|
-
return (
|
|
872
|
-
return { ...prev, ...draft };
|
|
873
|
-
};
|
|
876
|
+
return createMergeEntityUpdater();
|
|
874
877
|
}
|
|
875
878
|
};
|
|
876
879
|
|
|
@@ -1189,6 +1192,22 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
|
|
|
1189
1192
|
this.emitUpdate([key]);
|
|
1190
1193
|
return removed;
|
|
1191
1194
|
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Remove up to `count` consecutive slots starting at `index`.
|
|
1197
|
+
* Returns the number actually removed (clamped to bounds; 0 if nothing).
|
|
1198
|
+
*/
|
|
1199
|
+
removeRange(key, index, count) {
|
|
1200
|
+
const list = this.store.getOneByKey(key);
|
|
1201
|
+
if (!list) return 0;
|
|
1202
|
+
if (index < 0 || index >= list.length || count <= 0) return 0;
|
|
1203
|
+
const actual = Math.min(count, list.length - index);
|
|
1204
|
+
list.splice(index, actual);
|
|
1205
|
+
if (list.length === 0) {
|
|
1206
|
+
this.store.removeOneByKey(key);
|
|
1207
|
+
}
|
|
1208
|
+
this.emitUpdate([key]);
|
|
1209
|
+
return actual;
|
|
1210
|
+
}
|
|
1192
1211
|
move(key, fromIndex, toIndex) {
|
|
1193
1212
|
const list = this.store.getOneByKey(key);
|
|
1194
1213
|
if (!list) return void 0;
|
|
@@ -1201,6 +1220,32 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
|
|
|
1201
1220
|
this.emitUpdate([key]);
|
|
1202
1221
|
return slot;
|
|
1203
1222
|
}
|
|
1223
|
+
/** Replace the slot at `index` in place. Returns the safe index, or -1. */
|
|
1224
|
+
setSlotAt(key, index, slot) {
|
|
1225
|
+
const list = this.store.getOneByKey(key);
|
|
1226
|
+
if (!list) return -1;
|
|
1227
|
+
if (index < 0 || index >= list.length) return -1;
|
|
1228
|
+
list[index] = slot;
|
|
1229
|
+
this.emitUpdate([key]);
|
|
1230
|
+
return index;
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Move up to `count` consecutive slots from `from` to `to`. `to` is in the
|
|
1234
|
+
* coordinate space *after* the block is extracted (same as single `move`).
|
|
1235
|
+
* Returns the number actually moved (0 if a no-op / out of bounds).
|
|
1236
|
+
*/
|
|
1237
|
+
moveRange(key, from, to, count) {
|
|
1238
|
+
const list = this.store.getOneByKey(key);
|
|
1239
|
+
if (!list) return 0;
|
|
1240
|
+
if (from < 0 || from >= list.length || count <= 0) return 0;
|
|
1241
|
+
const actual = Math.min(count, list.length - from);
|
|
1242
|
+
const insertAt = Math.max(0, Math.min(to, list.length - actual));
|
|
1243
|
+
if (insertAt === from) return 0;
|
|
1244
|
+
const block = list.splice(from, actual);
|
|
1245
|
+
list.splice(insertAt, 0, ...block);
|
|
1246
|
+
this.emitUpdate([key]);
|
|
1247
|
+
return actual;
|
|
1248
|
+
}
|
|
1204
1249
|
resetSlots(key, slots) {
|
|
1205
1250
|
if (slots.length === 0) {
|
|
1206
1251
|
if (this.store.getOneByKey(key) !== void 0) {
|
|
@@ -1411,15 +1456,15 @@ var OIMUpdateEventEmitter = class {
|
|
|
1411
1456
|
const flushingKeys = this.updatedKeys;
|
|
1412
1457
|
this.updatedKeys = /* @__PURE__ */ new Set();
|
|
1413
1458
|
if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
|
|
1414
|
-
if (flushingKeys.size
|
|
1415
|
-
|
|
1416
|
-
|
|
1459
|
+
if (flushingKeys.size === this.keyHandlers.size) {
|
|
1460
|
+
this.keyHandlers.forEach((handlers, key) => {
|
|
1461
|
+
if (!flushingKeys.has(key)) return;
|
|
1417
1462
|
if (!handlers || handlers.size === 0) return;
|
|
1418
1463
|
this.notifyHandlers(handlers);
|
|
1419
1464
|
});
|
|
1420
1465
|
} else {
|
|
1421
|
-
|
|
1422
|
-
|
|
1466
|
+
flushingKeys.forEach((key) => {
|
|
1467
|
+
const handlers = this.keyHandlers.get(key);
|
|
1423
1468
|
if (!handlers || handlers.size === 0) return;
|
|
1424
1469
|
this.notifyHandlers(handlers);
|
|
1425
1470
|
});
|
|
@@ -1531,7 +1576,7 @@ var OIMOrderedListCommandStream = class {
|
|
|
1531
1576
|
if (payload.keys.length === 0) return;
|
|
1532
1577
|
for (let i = 0; i < payload.keys.length; i++) {
|
|
1533
1578
|
const key = payload.keys[i];
|
|
1534
|
-
this.
|
|
1579
|
+
this.appendResetCommand(key, this.getSlotsByKey(key));
|
|
1535
1580
|
}
|
|
1536
1581
|
};
|
|
1537
1582
|
this.commandsEventEmitter = new OIMUpdateEventEmitter(
|
|
@@ -1556,6 +1601,14 @@ var OIMOrderedListCommandStream = class {
|
|
|
1556
1601
|
key
|
|
1557
1602
|
);
|
|
1558
1603
|
}
|
|
1604
|
+
/** {@link IOIMOrderedListCommandSource} — alias of {@link getSlotsByKey}. */
|
|
1605
|
+
getItemsByKey(key) {
|
|
1606
|
+
return this.getSlotsByKey(key);
|
|
1607
|
+
}
|
|
1608
|
+
/** {@link IOIMOrderedListCommandSource} — per-key command subscription. */
|
|
1609
|
+
subscribeCommands(key, handler) {
|
|
1610
|
+
return this.commandsEventEmitter.subscribeOnKey(key, handler);
|
|
1611
|
+
}
|
|
1559
1612
|
getEntitiesByKey(key) {
|
|
1560
1613
|
return this.index.getEntitiesByKey(key);
|
|
1561
1614
|
}
|
|
@@ -1568,12 +1621,7 @@ var OIMOrderedListCommandStream = class {
|
|
|
1568
1621
|
pushSlot(key, slot) {
|
|
1569
1622
|
this.withWrite(() => {
|
|
1570
1623
|
const index = this.index.pushSlot(key, slot);
|
|
1571
|
-
this.appendCommand(key, {
|
|
1572
|
-
type: "insert",
|
|
1573
|
-
pk: slot.pk,
|
|
1574
|
-
slot,
|
|
1575
|
-
index
|
|
1576
|
-
});
|
|
1624
|
+
this.appendCommand(key, { type: "insert", index, item: slot });
|
|
1577
1625
|
});
|
|
1578
1626
|
}
|
|
1579
1627
|
insertSlotAt(key, index, slot) {
|
|
@@ -1581,9 +1629,20 @@ var OIMOrderedListCommandStream = class {
|
|
|
1581
1629
|
const safeIndex = this.index.insertSlotAt(key, index, slot);
|
|
1582
1630
|
this.appendCommand(key, {
|
|
1583
1631
|
type: "insert",
|
|
1584
|
-
|
|
1585
|
-
slot
|
|
1586
|
-
|
|
1632
|
+
index: safeIndex,
|
|
1633
|
+
item: slot
|
|
1634
|
+
});
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
/** Replace the slot at `index` in place. */
|
|
1638
|
+
setSlotAt(key, index, slot) {
|
|
1639
|
+
this.withWrite(() => {
|
|
1640
|
+
const safeIndex = this.index.setSlotAt(key, index, slot);
|
|
1641
|
+
if (safeIndex < 0) return;
|
|
1642
|
+
this.appendCommand(key, {
|
|
1643
|
+
type: "set",
|
|
1644
|
+
index: safeIndex,
|
|
1645
|
+
item: slot
|
|
1587
1646
|
});
|
|
1588
1647
|
});
|
|
1589
1648
|
}
|
|
@@ -1591,12 +1650,15 @@ var OIMOrderedListCommandStream = class {
|
|
|
1591
1650
|
this.withWrite(() => {
|
|
1592
1651
|
const slot = this.index.removeAt(key, index);
|
|
1593
1652
|
if (slot === void 0) return;
|
|
1594
|
-
this.appendCommand(key, {
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1653
|
+
this.appendCommand(key, { type: "remove", index });
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
/** Remove `count` consecutive elements from `index`; emits `remove` with `count`. */
|
|
1657
|
+
removeRange(key, index, count) {
|
|
1658
|
+
this.withWrite(() => {
|
|
1659
|
+
const removed = this.index.removeRange(key, index, count);
|
|
1660
|
+
if (removed <= 0) return;
|
|
1661
|
+
this.appendCommand(key, { type: "remove", index, count: removed });
|
|
1600
1662
|
});
|
|
1601
1663
|
}
|
|
1602
1664
|
move(key, fromIndex, toIndex) {
|
|
@@ -1605,17 +1667,26 @@ var OIMOrderedListCommandStream = class {
|
|
|
1605
1667
|
if (slot === void 0) return;
|
|
1606
1668
|
this.appendCommand(key, {
|
|
1607
1669
|
type: "move",
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
fromIndex,
|
|
1611
|
-
toIndex
|
|
1670
|
+
from: fromIndex,
|
|
1671
|
+
to: toIndex
|
|
1612
1672
|
});
|
|
1613
1673
|
});
|
|
1614
1674
|
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Move `count` consecutive elements from `from` to `to`; emits `move` with
|
|
1677
|
+
* `count`. `to` is in the post-extraction coordinate space (as single `move`).
|
|
1678
|
+
*/
|
|
1679
|
+
moveRange(key, from, to, count) {
|
|
1680
|
+
this.withWrite(() => {
|
|
1681
|
+
const moved = this.index.moveRange(key, from, to, count);
|
|
1682
|
+
if (moved <= 0) return;
|
|
1683
|
+
this.appendCommand(key, { type: "move", from, to, count: moved });
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1615
1686
|
setSlots(key, slots) {
|
|
1616
1687
|
this.withWrite(() => {
|
|
1617
1688
|
this.index.resetSlots(key, slots);
|
|
1618
|
-
this.
|
|
1689
|
+
this.appendResetCommand(key, slots);
|
|
1619
1690
|
});
|
|
1620
1691
|
}
|
|
1621
1692
|
getBufferedCommands(key) {
|
|
@@ -1659,26 +1730,21 @@ var OIMOrderedListCommandStream = class {
|
|
|
1659
1730
|
cmds = [];
|
|
1660
1731
|
this.commandsByKey.set(key, cmds);
|
|
1661
1732
|
}
|
|
1662
|
-
if (cmd.type === "
|
|
1733
|
+
if (cmd.type === "reset") {
|
|
1663
1734
|
cmds.length = 0;
|
|
1664
1735
|
cmds.push(cmd);
|
|
1665
|
-
} else if (cmds.length > 0 && cmds[0].type === "
|
|
1666
|
-
|
|
1667
|
-
cmds[0] = this.createSetCommand(slots);
|
|
1736
|
+
} else if (cmds.length > 0 && cmds[0].type === "reset") {
|
|
1737
|
+
cmds[0] = this.createResetCommand(this.getSlotsByKey(key));
|
|
1668
1738
|
} else {
|
|
1669
1739
|
cmds.push(cmd);
|
|
1670
1740
|
}
|
|
1671
1741
|
this.commandsEventEmitter.markUpdatedKeys([key]);
|
|
1672
1742
|
}
|
|
1673
|
-
|
|
1674
|
-
this.appendCommand(key, this.
|
|
1743
|
+
appendResetCommand(key, slots) {
|
|
1744
|
+
this.appendCommand(key, this.createResetCommand(slots));
|
|
1675
1745
|
}
|
|
1676
|
-
|
|
1677
|
-
return {
|
|
1678
|
-
type: "set",
|
|
1679
|
-
pks: slots.map((slot) => slot.pk),
|
|
1680
|
-
slots
|
|
1681
|
-
};
|
|
1746
|
+
createResetCommand(slots) {
|
|
1747
|
+
return { type: "reset", items: slots };
|
|
1682
1748
|
}
|
|
1683
1749
|
};
|
|
1684
1750
|
|
|
@@ -1695,12 +1761,7 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
|
|
|
1695
1761
|
this.withWrite(() => {
|
|
1696
1762
|
const slot = this.resolvePk(pk);
|
|
1697
1763
|
const index = this.index.pushSlot(key, slot);
|
|
1698
|
-
this.appendCommand(key, {
|
|
1699
|
-
type: "insert",
|
|
1700
|
-
pk,
|
|
1701
|
-
slot,
|
|
1702
|
-
index
|
|
1703
|
-
});
|
|
1764
|
+
this.appendCommand(key, { type: "insert", index, item: slot });
|
|
1704
1765
|
});
|
|
1705
1766
|
}
|
|
1706
1767
|
insertAt(key, index, pk) {
|
|
@@ -1709,9 +1770,21 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
|
|
|
1709
1770
|
const safeIndex = this.index.insertSlotAt(key, index, slot);
|
|
1710
1771
|
this.appendCommand(key, {
|
|
1711
1772
|
type: "insert",
|
|
1712
|
-
|
|
1713
|
-
slot
|
|
1714
|
-
|
|
1773
|
+
index: safeIndex,
|
|
1774
|
+
item: slot
|
|
1775
|
+
});
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
/** Replace the element at `index` with the slot for `pk`, in place. */
|
|
1779
|
+
setAt(key, index, pk) {
|
|
1780
|
+
this.withWrite(() => {
|
|
1781
|
+
const slot = this.resolvePk(pk);
|
|
1782
|
+
const safeIndex = this.index.setSlotAt(key, index, slot);
|
|
1783
|
+
if (safeIndex < 0) return;
|
|
1784
|
+
this.appendCommand(key, {
|
|
1785
|
+
type: "set",
|
|
1786
|
+
index: safeIndex,
|
|
1787
|
+
item: slot
|
|
1715
1788
|
});
|
|
1716
1789
|
});
|
|
1717
1790
|
}
|
|
@@ -1719,7 +1792,7 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
|
|
|
1719
1792
|
this.withWrite(() => {
|
|
1720
1793
|
const slots = pks.map((pk) => this.resolvePk(pk));
|
|
1721
1794
|
this.index.resetSlots(key, slots);
|
|
1722
|
-
this.
|
|
1795
|
+
this.appendResetCommand(key, slots);
|
|
1723
1796
|
});
|
|
1724
1797
|
}
|
|
1725
1798
|
getSlotsByKey(key) {
|
|
@@ -3298,20 +3371,27 @@ var OIMCollectionChangedFields = class {
|
|
|
3298
3371
|
constructor(queue, collection, opts) {
|
|
3299
3372
|
this.isInWrite = false;
|
|
3300
3373
|
this.lastEntitySnapshotByPk = /* @__PURE__ */ new Map();
|
|
3374
|
+
// PKs written through this wrapper during the current flush. Their fields are
|
|
3375
|
+
// already tracked accurately via trackPatch, so the flush-time external-write
|
|
3376
|
+
// handler skips them — no redundant diff + snapshot re-clone. Cleared per flush.
|
|
3377
|
+
this.writtenThroughThisFlush = /* @__PURE__ */ new Set();
|
|
3301
3378
|
this.changedFieldsByPk = /* @__PURE__ */ new Map();
|
|
3302
3379
|
this.changedPksByField = /* @__PURE__ */ new Map();
|
|
3303
3380
|
this.onAfterFlush = () => {
|
|
3304
3381
|
this.clear();
|
|
3382
|
+
this.writtenThroughThisFlush.clear();
|
|
3305
3383
|
};
|
|
3306
3384
|
this.onCollectionUpdate = (payload) => {
|
|
3307
3385
|
if (this.isInWrite) return;
|
|
3308
3386
|
if (payload.pks.length === 0) return;
|
|
3309
3387
|
for (let i = 0; i < payload.pks.length; i++) {
|
|
3310
3388
|
const pk = payload.pks[i];
|
|
3389
|
+
if (this.writtenThroughThisFlush.has(pk)) continue;
|
|
3311
3390
|
this.trackExternalMutationByPk(pk);
|
|
3312
3391
|
}
|
|
3313
3392
|
};
|
|
3314
3393
|
this.collection = collection;
|
|
3394
|
+
this.detectExternalMutations = opts?.detectExternalMutations !== false;
|
|
3315
3395
|
this.selectPk = opts?.selectPk ?? (() => {
|
|
3316
3396
|
throw new Error(
|
|
3317
3397
|
`[OIMCollectionChangedFields]: selectPk was not provided. Use upsertOneByPk(...) or pass opts.selectPk.`
|
|
@@ -3326,15 +3406,17 @@ var OIMCollectionChangedFields = class {
|
|
|
3326
3406
|
0 /* AFTER_FLUSH */,
|
|
3327
3407
|
this.onAfterFlush
|
|
3328
3408
|
);
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
this.
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3409
|
+
if (this.detectExternalMutations) {
|
|
3410
|
+
const maybeReactive = collection;
|
|
3411
|
+
this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
|
|
3412
|
+
this.onCollectionUpdate({
|
|
3413
|
+
pks
|
|
3414
|
+
});
|
|
3415
|
+
}) : collection.emitter.on(
|
|
3416
|
+
0 /* UPDATE */,
|
|
3417
|
+
this.onCollectionUpdate
|
|
3418
|
+
);
|
|
3419
|
+
}
|
|
3338
3420
|
}
|
|
3339
3421
|
upsertOneByPk(pk, patch) {
|
|
3340
3422
|
const slot = this.withWrite(() => {
|
|
@@ -3390,7 +3472,7 @@ var OIMCollectionChangedFields = class {
|
|
|
3390
3472
|
this.changedPksEventEmitter.destroy();
|
|
3391
3473
|
this.changedFieldsEventEmitter.destroy();
|
|
3392
3474
|
this.unsubscribeAfterFlush();
|
|
3393
|
-
this.unsubscribeFromCollectionEmitter();
|
|
3475
|
+
this.unsubscribeFromCollectionEmitter?.();
|
|
3394
3476
|
this.clear();
|
|
3395
3477
|
}
|
|
3396
3478
|
withWrite(fn) {
|
|
@@ -3432,6 +3514,8 @@ var OIMCollectionChangedFields = class {
|
|
|
3432
3514
|
return changed;
|
|
3433
3515
|
}
|
|
3434
3516
|
updateEntitySnapshot(pk) {
|
|
3517
|
+
if (!this.detectExternalMutations) return;
|
|
3518
|
+
this.writtenThroughThisFlush.add(pk);
|
|
3435
3519
|
const entity = this.collection.getOneByPk(pk);
|
|
3436
3520
|
if (!entity) {
|
|
3437
3521
|
this.lastEntitySnapshotByPk.delete(pk);
|
|
@@ -3471,6 +3555,162 @@ var OIMCollectionChangedFields = class {
|
|
|
3471
3555
|
}
|
|
3472
3556
|
};
|
|
3473
3557
|
|
|
3558
|
+
// src/modules/wrapper/index/OIMOrderedListMappedCommandStream.ts
|
|
3559
|
+
var OIMOrderedListMappedCommandStream = class _OIMOrderedListMappedCommandStream {
|
|
3560
|
+
constructor(source, opts) {
|
|
3561
|
+
/** Per-key positional mirror of the mapped elements. */
|
|
3562
|
+
this.mirror = /* @__PURE__ */ new Map();
|
|
3563
|
+
/** Mapped commands produced for the current delivery, per key. */
|
|
3564
|
+
this.buffer = /* @__PURE__ */ new Map();
|
|
3565
|
+
this.subscribers = /* @__PURE__ */ new Map();
|
|
3566
|
+
this.sourceUnsubscribers = /* @__PURE__ */ new Map();
|
|
3567
|
+
this.source = source;
|
|
3568
|
+
this.create = opts.create;
|
|
3569
|
+
this.destroyItem = opts.destroy;
|
|
3570
|
+
}
|
|
3571
|
+
subscribeCommands(key, handler) {
|
|
3572
|
+
this.ensureWired(key);
|
|
3573
|
+
let subs = this.subscribers.get(key);
|
|
3574
|
+
if (!subs) {
|
|
3575
|
+
subs = /* @__PURE__ */ new Set();
|
|
3576
|
+
this.subscribers.set(key, subs);
|
|
3577
|
+
}
|
|
3578
|
+
subs.add(handler);
|
|
3579
|
+
return () => {
|
|
3580
|
+
const current = this.subscribers.get(key);
|
|
3581
|
+
if (!current) return;
|
|
3582
|
+
current.delete(handler);
|
|
3583
|
+
if (current.size === 0) this.subscribers.delete(key);
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
consumeCommands(key) {
|
|
3587
|
+
const cmds = this.buffer.get(key);
|
|
3588
|
+
if (!cmds || cmds.length === 0) return [];
|
|
3589
|
+
return cmds.slice();
|
|
3590
|
+
}
|
|
3591
|
+
getItemsByKey(key) {
|
|
3592
|
+
this.ensureWired(key);
|
|
3593
|
+
return this.mirror.get(key) ?? [];
|
|
3594
|
+
}
|
|
3595
|
+
/** Map this source again, chaining the projection. */
|
|
3596
|
+
map(opts) {
|
|
3597
|
+
return new _OIMOrderedListMappedCommandStream(
|
|
3598
|
+
this,
|
|
3599
|
+
opts
|
|
3600
|
+
);
|
|
3601
|
+
}
|
|
3602
|
+
destroy() {
|
|
3603
|
+
this.sourceUnsubscribers.forEach((unsubscribe) => unsubscribe());
|
|
3604
|
+
this.sourceUnsubscribers.clear();
|
|
3605
|
+
if (this.destroyItem) {
|
|
3606
|
+
this.mirror.forEach(
|
|
3607
|
+
(items) => items.forEach((item) => this.destroyItem(item))
|
|
3608
|
+
);
|
|
3609
|
+
}
|
|
3610
|
+
this.mirror.clear();
|
|
3611
|
+
this.buffer.clear();
|
|
3612
|
+
this.subscribers.clear();
|
|
3613
|
+
}
|
|
3614
|
+
/** Build the initial mirror and subscribe to the source once per key. */
|
|
3615
|
+
ensureWired(key) {
|
|
3616
|
+
if (this.mirror.has(key)) return;
|
|
3617
|
+
const items = this.source.getItemsByKey(key);
|
|
3618
|
+
this.mirror.set(key, items.map((item) => this.create(item)));
|
|
3619
|
+
const unsubscribe = this.source.subscribeCommands(
|
|
3620
|
+
key,
|
|
3621
|
+
() => this.onSourceCommands(key)
|
|
3622
|
+
);
|
|
3623
|
+
this.sourceUnsubscribers.set(key, unsubscribe);
|
|
3624
|
+
}
|
|
3625
|
+
onSourceCommands(key) {
|
|
3626
|
+
const cmds = this.source.consumeCommands(key);
|
|
3627
|
+
if (cmds.length === 0) return;
|
|
3628
|
+
let mirror = this.mirror.get(key);
|
|
3629
|
+
if (!mirror) {
|
|
3630
|
+
mirror = [];
|
|
3631
|
+
this.mirror.set(key, mirror);
|
|
3632
|
+
}
|
|
3633
|
+
const out = [];
|
|
3634
|
+
for (let i = 0; i < cmds.length; i++) {
|
|
3635
|
+
const cmd = cmds[i];
|
|
3636
|
+
switch (cmd.type) {
|
|
3637
|
+
case "insert": {
|
|
3638
|
+
const mapped = this.create(cmd.item);
|
|
3639
|
+
mirror.splice(cmd.index, 0, mapped);
|
|
3640
|
+
out.push({
|
|
3641
|
+
type: "insert",
|
|
3642
|
+
index: cmd.index,
|
|
3643
|
+
item: mapped
|
|
3644
|
+
});
|
|
3645
|
+
break;
|
|
3646
|
+
}
|
|
3647
|
+
case "remove": {
|
|
3648
|
+
const count = cmd.count ?? 1;
|
|
3649
|
+
const removed = mirror.splice(cmd.index, count);
|
|
3650
|
+
if (this.destroyItem) {
|
|
3651
|
+
for (let r = 0; r < removed.length; r++) {
|
|
3652
|
+
this.destroyItem(removed[r]);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
out.push({ type: "remove", index: cmd.index, count });
|
|
3656
|
+
break;
|
|
3657
|
+
}
|
|
3658
|
+
case "move": {
|
|
3659
|
+
const count = cmd.count ?? 1;
|
|
3660
|
+
const segment = mirror.splice(cmd.from, count);
|
|
3661
|
+
mirror.splice(cmd.to, 0, ...segment);
|
|
3662
|
+
out.push({
|
|
3663
|
+
type: "move",
|
|
3664
|
+
from: cmd.from,
|
|
3665
|
+
to: cmd.to,
|
|
3666
|
+
count
|
|
3667
|
+
});
|
|
3668
|
+
break;
|
|
3669
|
+
}
|
|
3670
|
+
case "set": {
|
|
3671
|
+
const mapped = this.create(cmd.item);
|
|
3672
|
+
const replaced = mirror.splice(cmd.index, 1, mapped);
|
|
3673
|
+
if (this.destroyItem && replaced.length > 0) {
|
|
3674
|
+
this.destroyItem(replaced[0]);
|
|
3675
|
+
}
|
|
3676
|
+
out.push({ type: "set", index: cmd.index, item: mapped });
|
|
3677
|
+
break;
|
|
3678
|
+
}
|
|
3679
|
+
case "reset": {
|
|
3680
|
+
if (this.destroyItem) {
|
|
3681
|
+
for (let r = 0; r < mirror.length; r++) {
|
|
3682
|
+
this.destroyItem(mirror[r]);
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
const mapped = cmd.items.map((item) => this.create(item));
|
|
3686
|
+
mirror = mapped;
|
|
3687
|
+
this.mirror.set(key, mapped);
|
|
3688
|
+
out.push({ type: "reset", items: mapped });
|
|
3689
|
+
break;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
this.buffer.set(key, out);
|
|
3694
|
+
try {
|
|
3695
|
+
const subs = this.subscribers.get(key);
|
|
3696
|
+
if (subs && subs.size > 0) {
|
|
3697
|
+
const snapshot = Array.from(subs);
|
|
3698
|
+
for (let s = 0; s < snapshot.length; s++) {
|
|
3699
|
+
const handler = snapshot[s];
|
|
3700
|
+
if (subs.has(handler)) handler();
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
} finally {
|
|
3704
|
+
this.buffer.delete(key);
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
};
|
|
3708
|
+
|
|
3709
|
+
// src/modules/wrapper/index/createOIMOrderedListMappedCommandStream.ts
|
|
3710
|
+
function createOIMOrderedListMappedCommandStream(source, opts) {
|
|
3711
|
+
return new OIMOrderedListMappedCommandStream(source, opts);
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3474
3714
|
// src/core/event-queue-scheduler/OIMEventQueueSchedulerAnimationFrame.ts
|
|
3475
3715
|
var OIMEventQueueSchedulerAnimationFrame = class extends OIMEventQueueScheduler {
|
|
3476
3716
|
constructor() {
|
|
@@ -4103,6 +4343,7 @@ export {
|
|
|
4103
4343
|
OIMObjectValueByKeySelector,
|
|
4104
4344
|
OIMObjectValuesByKeysSelector,
|
|
4105
4345
|
OIMOrderedListCommandStream,
|
|
4346
|
+
OIMOrderedListMappedCommandStream,
|
|
4106
4347
|
OIMPkSelectorFactory,
|
|
4107
4348
|
OIMReactiveCollection,
|
|
4108
4349
|
OIMReactiveCollectionIndexManualArrayBased,
|
|
@@ -4115,6 +4356,8 @@ export {
|
|
|
4115
4356
|
OIMReactiveObject,
|
|
4116
4357
|
OIMSelector,
|
|
4117
4358
|
createInPlaceEntityUpdater,
|
|
4359
|
+
createMergeEntityUpdater,
|
|
4118
4360
|
createOIMCollectionIndexFactory,
|
|
4119
|
-
createOIMCollectionKit
|
|
4361
|
+
createOIMCollectionKit,
|
|
4362
|
+
createOIMOrderedListMappedCommandStream
|
|
4120
4363
|
};
|
package/llms.txt
CHANGED
|
@@ -84,6 +84,39 @@ Membership writes are incremental (touch only the changed pks):
|
|
|
84
84
|
- `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
85
|
- `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
86
|
|
|
87
|
+
### Ordered list command stream
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createOIMOrderedListMappedCommandStream } from '@oimdb/core';
|
|
93
|
+
|
|
94
|
+
stream.push('main', 'u1'); // append by pk; also insertAt/setAt/removeAt/removeRange/moveRange/set
|
|
95
|
+
stream.subscribeCommands('main', () => {
|
|
96
|
+
for (const cmd of stream.consumeCommands('main')) {
|
|
97
|
+
// { type:'insert', index, item } | { type:'remove', index, count? }
|
|
98
|
+
// | { type:'move', from, to, count? } | { type:'set', index, item }
|
|
99
|
+
// | { type:'reset', items } — item is the entity slot; entity is item.item
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
stream.getEntitiesByKey('main'); // (TEntity | undefined)[] — holes preserved
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`count` defaults to 1 (read `count ?? 1`). A whole-list change collapses to a single `reset`. Reads: `getPksByKey` / `getSlotsByKey` / `getEntitiesByKey`.
|
|
106
|
+
|
|
107
|
+
**Mapping the list to your own elements** — `createOIMOrderedListMappedCommandStream(source, { create, destroy? })` projects element-wise: the same commands with `item` replaced by `create(slot)`'s result (a DOM node, view model, …). Identity is positional — a `move` reuses the **same** mapped instance (never recreated); `create` runs on insert/set/reset/initial build, `destroy` on remove / replaced-set / reset / `destroy()`. 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(...)`. New exports: `IOIMOrderedListCommandSource`, `TOIMOrderedListMapOptions`, `OIMOrderedListMappedCommandStream`, `createOIMOrderedListMappedCommandStream`.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const nodes = createOIMOrderedListMappedCommandStream(stream, {
|
|
111
|
+
create: (slot) => engine.makeNode(slot.item),
|
|
112
|
+
destroy: (node) => engine.dropNode(node),
|
|
113
|
+
});
|
|
114
|
+
nodes.subscribeCommands('main', () => engine.apply(nodes.consumeCommands('main'))); // cmd.item is your node
|
|
115
|
+
nodes.getItemsByKey('main'); // current mapped elements — initial render
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> Content updates (entity fields changed) are not list commands — they arrive via the collection's per-pk subscription. The stream carries structure only.
|
|
119
|
+
|
|
87
120
|
### DX Layer — Collection Model
|
|
88
121
|
|
|
89
122
|
`createOIMCollectionKit` wires queue + collection + index factory + selectors together.
|