@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/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 (draft, prev) => {
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 * 2 < this.keyHandlers.size) {
1415
- flushingKeys.forEach((key) => {
1416
- const handlers = this.keyHandlers.get(key);
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
- this.keyHandlers.forEach((handlers, key) => {
1422
- if (!flushingKeys.has(key)) return;
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.appendSetCommand(key, this.getSlotsByKey(key));
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
- pk: slot.pk,
1585
- slot,
1586
- index: safeIndex
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
- type: "remove",
1596
- pk: slot.pk,
1597
- slot,
1598
- index
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
- pk: slot.pk,
1609
- slot,
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.appendSetCommand(key, slots);
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 === "set") {
1733
+ if (cmd.type === "reset") {
1663
1734
  cmds.length = 0;
1664
1735
  cmds.push(cmd);
1665
- } else if (cmds.length > 0 && cmds[0].type === "set") {
1666
- const slots = this.getSlotsByKey(key);
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
- appendSetCommand(key, slots) {
1674
- this.appendCommand(key, this.createSetCommand(slots));
1743
+ appendResetCommand(key, slots) {
1744
+ this.appendCommand(key, this.createResetCommand(slots));
1675
1745
  }
1676
- createSetCommand(slots) {
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
- pk,
1713
- slot,
1714
- index: safeIndex
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.appendSetCommand(key, slots);
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
- const maybeReactive = collection;
3330
- this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
3331
- this.onCollectionUpdate({
3332
- pks
3333
- });
3334
- }) : collection.emitter.on(
3335
- 0 /* UPDATE */,
3336
- this.onCollectionUpdate
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oimdb/core",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "description": "Core in-memory data library with type-safe indices, reactive subscriptions, and event processing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",