@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 CHANGED
@@ -318,16 +318,20 @@ cardsByDeck.commandsEventEmitter.subscribeOnKey('deck1', () => {
318
318
  for (const command of commands) {
319
319
  switch (command.type) {
320
320
  case 'insert':
321
- // Insert command.pk / command.slot.item at command.index
321
+ // Insert command.item (the slot; entity is command.item.item)
322
+ // at command.index
322
323
  break;
323
324
  case 'remove':
324
- // Remove command.pk from command.index
325
+ // Remove command.count ?? 1 elements starting at command.index
325
326
  break;
326
327
  case 'move':
327
- // Move command.pk from command.fromIndex to command.toIndex
328
+ // Move command.count ?? 1 elements from command.from to command.to
328
329
  break;
329
330
  case 'set':
330
- // Replace the whole list with command.pks or command.slots
331
+ // Replace the single element at command.index with command.item
332
+ break;
333
+ case 'reset':
334
+ // Replace the whole list with command.items (slots)
331
335
  break;
332
336
  }
333
337
  }
@@ -1546,7 +1550,16 @@ Collection-bound ordered Array-based index. Use this when ordered PK writes shou
1546
1550
  - `getEntitiesByKey(key: TKey): (TEntity | undefined)[]` - Returns entities from canonical slots (holes are `undefined`)
1547
1551
 
1548
1552
  #### `OIMOrderedListCommandStream<TKey, TPk, TEntity>`
1549
- Slot-first ordered per-key list with a command stream for imperative consumers. It stores data in an `OIMIndexManualOrderedArrayBased` and emits `TOIMOrderedListCommand` batches through `commandsEventEmitter`.
1553
+ Slot-first ordered per-key list with a command stream for imperative consumers. It stores data in an `OIMIndexManualOrderedArrayBased` and emits position-addressed `TOIMOrderedListCommand<Slot>` batches through `commandsEventEmitter`. Each command's `item` is the entity slot (read the entity via `item.item`):
1554
+
1555
+ ```typescript
1556
+ type TOIMOrderedListCommand<TItem> =
1557
+ | { type: 'insert'; index: number; item: TItem }
1558
+ | { type: 'remove'; index: number; count?: number } // count may be > 1
1559
+ | { type: 'move'; from: number; to: number; count?: number }
1560
+ | { type: 'set'; index: number; item: TItem } // replace one element
1561
+ | { type: 'reset'; items: readonly TItem[] }; // replace whole list
1562
+ ```
1550
1563
 
1551
1564
  **Constructor:**
1552
1565
  ```typescript
@@ -1560,14 +1573,17 @@ new OIMOrderedListCommandStream(
1560
1573
  - `index: OIMIndexManualOrderedArrayBased<TKey, TPk>` - Underlying ordered index and source of truth
1561
1574
  - `commandsEventEmitter: OIMUpdateEventEmitter<TKey>` - Key-specific command notifications delivered after queue flush
1562
1575
 
1563
- **Methods:**
1564
- - `setSlots(key: TKey, slots: readonly TOIMEntitySlot<TEntity, TPk>[]): void` - Replace the whole ordered list and emit a `set` command
1565
- - `pushSlot(key: TKey, slot: TOIMEntitySlot<TEntity, TPk>): void` - Append a slot and emit an `insert` command
1566
- - `insertSlotAt(key: TKey, index: number, slot: TOIMEntitySlot<TEntity, TPk>): void` - Insert a slot and emit an `insert` command
1576
+ **Methods:** (`Slot` = `TOIMEntitySlot<TEntity, TPk>`)
1577
+ - `setSlots(key: TKey, slots: readonly Slot[]): void` - Replace the whole ordered list and emit a `reset` command
1578
+ - `pushSlot(key: TKey, slot: Slot): void` - Append a slot and emit an `insert` command
1579
+ - `insertSlotAt(key: TKey, index: number, slot: Slot): void` - Insert a slot and emit an `insert` command
1580
+ - `setSlotAt(key: TKey, index: number, slot: Slot): void` - Replace the slot at `index` in place and emit a `set` command
1567
1581
  - `removeAt(key: TKey, index: number): void` - Remove by index and emit a `remove` command
1582
+ - `removeRange(key: TKey, index: number, count: number): void` - Remove `count` consecutive elements from `index` and emit a `remove` command with `count`
1568
1583
  - `move(key: TKey, fromIndex: number, toIndex: number): void` - Move within the list and emit a `move` command
1569
- - `consumeCommands(key: TKey): TOIMOrderedListCommand<TPk, TEntity>[]` - Read buffered commands for a key inside the notification handler
1570
- - `getBufferedCommands(key: TKey): readonly TOIMOrderedListCommand<TPk, TEntity>[]` - Peek at buffered commands without clearing them
1584
+ - `moveRange(key: TKey, from: number, to: number, count: number): void` - Move `count` consecutive elements and emit a `move` command with `count` (`to` is post-extraction space)
1585
+ - `consumeCommands(key: TKey): TOIMOrderedListCommand<Slot>[]` - Read buffered commands for a key inside the notification handler
1586
+ - `getBufferedCommands(key: TKey): readonly TOIMOrderedListCommand<Slot>[]` - Peek at buffered commands without clearing them
1571
1587
  - `getPksByKey(key: TKey): readonly TPk[]` - Read the current ordered list
1572
1588
  - `getSlotsByKey(key: TKey): readonly TOIMEntitySlot<TEntity, TPk>[]` - Read current ordered slots
1573
1589
  - `getEntitiesByKey(key: TKey): (TEntity | undefined)[]` - Read current ordered entities (holes are `undefined`)
@@ -1575,10 +1591,11 @@ new OIMOrderedListCommandStream(
1575
1591
  - `destroy(): void` - Dispose subscriptions and clear state
1576
1592
 
1577
1593
  #### `OIMCollectionOrderedListCommandStream<TKey, TPk, TEntity>`
1578
- Collection-bound ordered-list command stream. Public writes use PKs, commands include both `pk`/`pks` and canonical `slot`/`slots`.
1594
+ Collection-bound ordered-list command stream. Public writes use PKs that resolve to canonical collection slots; emitted commands carry the slot as `item` (entity via `item.item`).
1579
1595
 
1580
1596
  **Methods:**
1581
- - `set(key: TKey, pks: readonly TPk[]): void` - Replace the whole ordered list from PKs
1597
+ - `set(key: TKey, pks: readonly TPk[]): void` - Replace the whole ordered list from PKs (emits `reset`)
1598
+ - `setAt(key: TKey, index: number, pk: TPk): void` - Replace the element at `index` with the slot for `pk` (emits `set`)
1582
1599
  - `push(key: TKey, pk: TPk): void` - Append a PK
1583
1600
  - `insertAt(key: TKey, index: number, pk: TPk): void` - Insert a PK
1584
1601
  - all read/command methods from `OIMOrderedListCommandStream`
package/dist/index.cjs CHANGED
@@ -83,6 +83,7 @@ __export(index_exports, {
83
83
  OIMObjectValueByKeySelector: () => OIMObjectValueByKeySelector,
84
84
  OIMObjectValuesByKeysSelector: () => OIMObjectValuesByKeysSelector,
85
85
  OIMOrderedListCommandStream: () => OIMOrderedListCommandStream,
86
+ OIMOrderedListMappedCommandStream: () => OIMOrderedListMappedCommandStream,
86
87
  OIMPkSelectorFactory: () => OIMPkSelectorFactory,
87
88
  OIMReactiveCollection: () => OIMReactiveCollection,
88
89
  OIMReactiveCollectionIndexManualArrayBased: () => OIMReactiveCollectionIndexManualArrayBased,
@@ -95,8 +96,10 @@ __export(index_exports, {
95
96
  OIMReactiveObject: () => OIMReactiveObject,
96
97
  OIMSelector: () => OIMSelector,
97
98
  createInPlaceEntityUpdater: () => createInPlaceEntityUpdater,
99
+ createMergeEntityUpdater: () => createMergeEntityUpdater,
98
100
  createOIMCollectionIndexFactory: () => createOIMCollectionIndexFactory,
99
- createOIMCollectionKit: () => createOIMCollectionKit
101
+ createOIMCollectionKit: () => createOIMCollectionKit,
102
+ createOIMOrderedListMappedCommandStream: () => createOIMOrderedListMappedCommandStream
100
103
  });
101
104
  module.exports = __toCommonJS(index_exports);
102
105
 
@@ -967,12 +970,15 @@ var OIMCollectionStoreMapDriven = class extends OIMCollectionStore {
967
970
  }
968
971
  };
969
972
 
973
+ // src/core/createMergeEntityUpdater.ts
974
+ function createMergeEntityUpdater() {
975
+ return (draft, prev) => ({ ...prev, ...draft });
976
+ }
977
+
970
978
  // src/core/OIMEntityUpdaterFactory.ts
971
979
  var OIMEntityUpdaterFactory = class {
972
980
  createMergeEntityUpdater() {
973
- return (draft, prev) => {
974
- return { ...prev, ...draft };
975
- };
981
+ return createMergeEntityUpdater();
976
982
  }
977
983
  };
978
984
 
@@ -1291,6 +1297,22 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1291
1297
  this.emitUpdate([key]);
1292
1298
  return removed;
1293
1299
  }
1300
+ /**
1301
+ * Remove up to `count` consecutive slots starting at `index`.
1302
+ * Returns the number actually removed (clamped to bounds; 0 if nothing).
1303
+ */
1304
+ removeRange(key, index, count) {
1305
+ const list = this.store.getOneByKey(key);
1306
+ if (!list) return 0;
1307
+ if (index < 0 || index >= list.length || count <= 0) return 0;
1308
+ const actual = Math.min(count, list.length - index);
1309
+ list.splice(index, actual);
1310
+ if (list.length === 0) {
1311
+ this.store.removeOneByKey(key);
1312
+ }
1313
+ this.emitUpdate([key]);
1314
+ return actual;
1315
+ }
1294
1316
  move(key, fromIndex, toIndex) {
1295
1317
  const list = this.store.getOneByKey(key);
1296
1318
  if (!list) return void 0;
@@ -1303,6 +1325,32 @@ var OIMIndexManualOrderedArrayBased = class extends OIMIndexArrayBased {
1303
1325
  this.emitUpdate([key]);
1304
1326
  return slot;
1305
1327
  }
1328
+ /** Replace the slot at `index` in place. Returns the safe index, or -1. */
1329
+ setSlotAt(key, index, slot) {
1330
+ const list = this.store.getOneByKey(key);
1331
+ if (!list) return -1;
1332
+ if (index < 0 || index >= list.length) return -1;
1333
+ list[index] = slot;
1334
+ this.emitUpdate([key]);
1335
+ return index;
1336
+ }
1337
+ /**
1338
+ * Move up to `count` consecutive slots from `from` to `to`. `to` is in the
1339
+ * coordinate space *after* the block is extracted (same as single `move`).
1340
+ * Returns the number actually moved (0 if a no-op / out of bounds).
1341
+ */
1342
+ moveRange(key, from, to, count) {
1343
+ const list = this.store.getOneByKey(key);
1344
+ if (!list) return 0;
1345
+ if (from < 0 || from >= list.length || count <= 0) return 0;
1346
+ const actual = Math.min(count, list.length - from);
1347
+ const insertAt = Math.max(0, Math.min(to, list.length - actual));
1348
+ if (insertAt === from) return 0;
1349
+ const block = list.splice(from, actual);
1350
+ list.splice(insertAt, 0, ...block);
1351
+ this.emitUpdate([key]);
1352
+ return actual;
1353
+ }
1306
1354
  resetSlots(key, slots) {
1307
1355
  if (slots.length === 0) {
1308
1356
  if (this.store.getOneByKey(key) !== void 0) {
@@ -1513,15 +1561,15 @@ var OIMUpdateEventEmitter = class {
1513
1561
  const flushingKeys = this.updatedKeys;
1514
1562
  this.updatedKeys = /* @__PURE__ */ new Set();
1515
1563
  if (this.keyHandlers.size === 0 || flushingKeys.size === 0) return;
1516
- if (flushingKeys.size * 2 < this.keyHandlers.size) {
1517
- flushingKeys.forEach((key) => {
1518
- const handlers = this.keyHandlers.get(key);
1564
+ if (flushingKeys.size === this.keyHandlers.size) {
1565
+ this.keyHandlers.forEach((handlers, key) => {
1566
+ if (!flushingKeys.has(key)) return;
1519
1567
  if (!handlers || handlers.size === 0) return;
1520
1568
  this.notifyHandlers(handlers);
1521
1569
  });
1522
1570
  } else {
1523
- this.keyHandlers.forEach((handlers, key) => {
1524
- if (!flushingKeys.has(key)) return;
1571
+ flushingKeys.forEach((key) => {
1572
+ const handlers = this.keyHandlers.get(key);
1525
1573
  if (!handlers || handlers.size === 0) return;
1526
1574
  this.notifyHandlers(handlers);
1527
1575
  });
@@ -1633,7 +1681,7 @@ var OIMOrderedListCommandStream = class {
1633
1681
  if (payload.keys.length === 0) return;
1634
1682
  for (let i = 0; i < payload.keys.length; i++) {
1635
1683
  const key = payload.keys[i];
1636
- this.appendSetCommand(key, this.getSlotsByKey(key));
1684
+ this.appendResetCommand(key, this.getSlotsByKey(key));
1637
1685
  }
1638
1686
  };
1639
1687
  this.commandsEventEmitter = new OIMUpdateEventEmitter(
@@ -1658,6 +1706,14 @@ var OIMOrderedListCommandStream = class {
1658
1706
  key
1659
1707
  );
1660
1708
  }
1709
+ /** {@link IOIMOrderedListCommandSource} — alias of {@link getSlotsByKey}. */
1710
+ getItemsByKey(key) {
1711
+ return this.getSlotsByKey(key);
1712
+ }
1713
+ /** {@link IOIMOrderedListCommandSource} — per-key command subscription. */
1714
+ subscribeCommands(key, handler) {
1715
+ return this.commandsEventEmitter.subscribeOnKey(key, handler);
1716
+ }
1661
1717
  getEntitiesByKey(key) {
1662
1718
  return this.index.getEntitiesByKey(key);
1663
1719
  }
@@ -1670,12 +1726,7 @@ var OIMOrderedListCommandStream = class {
1670
1726
  pushSlot(key, slot) {
1671
1727
  this.withWrite(() => {
1672
1728
  const index = this.index.pushSlot(key, slot);
1673
- this.appendCommand(key, {
1674
- type: "insert",
1675
- pk: slot.pk,
1676
- slot,
1677
- index
1678
- });
1729
+ this.appendCommand(key, { type: "insert", index, item: slot });
1679
1730
  });
1680
1731
  }
1681
1732
  insertSlotAt(key, index, slot) {
@@ -1683,9 +1734,20 @@ var OIMOrderedListCommandStream = class {
1683
1734
  const safeIndex = this.index.insertSlotAt(key, index, slot);
1684
1735
  this.appendCommand(key, {
1685
1736
  type: "insert",
1686
- pk: slot.pk,
1687
- slot,
1688
- index: safeIndex
1737
+ index: safeIndex,
1738
+ item: slot
1739
+ });
1740
+ });
1741
+ }
1742
+ /** Replace the slot at `index` in place. */
1743
+ setSlotAt(key, index, slot) {
1744
+ this.withWrite(() => {
1745
+ const safeIndex = this.index.setSlotAt(key, index, slot);
1746
+ if (safeIndex < 0) return;
1747
+ this.appendCommand(key, {
1748
+ type: "set",
1749
+ index: safeIndex,
1750
+ item: slot
1689
1751
  });
1690
1752
  });
1691
1753
  }
@@ -1693,12 +1755,15 @@ var OIMOrderedListCommandStream = class {
1693
1755
  this.withWrite(() => {
1694
1756
  const slot = this.index.removeAt(key, index);
1695
1757
  if (slot === void 0) return;
1696
- this.appendCommand(key, {
1697
- type: "remove",
1698
- pk: slot.pk,
1699
- slot,
1700
- index
1701
- });
1758
+ this.appendCommand(key, { type: "remove", index });
1759
+ });
1760
+ }
1761
+ /** Remove `count` consecutive elements from `index`; emits `remove` with `count`. */
1762
+ removeRange(key, index, count) {
1763
+ this.withWrite(() => {
1764
+ const removed = this.index.removeRange(key, index, count);
1765
+ if (removed <= 0) return;
1766
+ this.appendCommand(key, { type: "remove", index, count: removed });
1702
1767
  });
1703
1768
  }
1704
1769
  move(key, fromIndex, toIndex) {
@@ -1707,17 +1772,26 @@ var OIMOrderedListCommandStream = class {
1707
1772
  if (slot === void 0) return;
1708
1773
  this.appendCommand(key, {
1709
1774
  type: "move",
1710
- pk: slot.pk,
1711
- slot,
1712
- fromIndex,
1713
- toIndex
1775
+ from: fromIndex,
1776
+ to: toIndex
1714
1777
  });
1715
1778
  });
1716
1779
  }
1780
+ /**
1781
+ * Move `count` consecutive elements from `from` to `to`; emits `move` with
1782
+ * `count`. `to` is in the post-extraction coordinate space (as single `move`).
1783
+ */
1784
+ moveRange(key, from, to, count) {
1785
+ this.withWrite(() => {
1786
+ const moved = this.index.moveRange(key, from, to, count);
1787
+ if (moved <= 0) return;
1788
+ this.appendCommand(key, { type: "move", from, to, count: moved });
1789
+ });
1790
+ }
1717
1791
  setSlots(key, slots) {
1718
1792
  this.withWrite(() => {
1719
1793
  this.index.resetSlots(key, slots);
1720
- this.appendSetCommand(key, slots);
1794
+ this.appendResetCommand(key, slots);
1721
1795
  });
1722
1796
  }
1723
1797
  getBufferedCommands(key) {
@@ -1761,26 +1835,21 @@ var OIMOrderedListCommandStream = class {
1761
1835
  cmds = [];
1762
1836
  this.commandsByKey.set(key, cmds);
1763
1837
  }
1764
- if (cmd.type === "set") {
1838
+ if (cmd.type === "reset") {
1765
1839
  cmds.length = 0;
1766
1840
  cmds.push(cmd);
1767
- } else if (cmds.length > 0 && cmds[0].type === "set") {
1768
- const slots = this.getSlotsByKey(key);
1769
- cmds[0] = this.createSetCommand(slots);
1841
+ } else if (cmds.length > 0 && cmds[0].type === "reset") {
1842
+ cmds[0] = this.createResetCommand(this.getSlotsByKey(key));
1770
1843
  } else {
1771
1844
  cmds.push(cmd);
1772
1845
  }
1773
1846
  this.commandsEventEmitter.markUpdatedKeys([key]);
1774
1847
  }
1775
- appendSetCommand(key, slots) {
1776
- this.appendCommand(key, this.createSetCommand(slots));
1848
+ appendResetCommand(key, slots) {
1849
+ this.appendCommand(key, this.createResetCommand(slots));
1777
1850
  }
1778
- createSetCommand(slots) {
1779
- return {
1780
- type: "set",
1781
- pks: slots.map((slot) => slot.pk),
1782
- slots
1783
- };
1851
+ createResetCommand(slots) {
1852
+ return { type: "reset", items: slots };
1784
1853
  }
1785
1854
  };
1786
1855
 
@@ -1797,12 +1866,7 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
1797
1866
  this.withWrite(() => {
1798
1867
  const slot = this.resolvePk(pk);
1799
1868
  const index = this.index.pushSlot(key, slot);
1800
- this.appendCommand(key, {
1801
- type: "insert",
1802
- pk,
1803
- slot,
1804
- index
1805
- });
1869
+ this.appendCommand(key, { type: "insert", index, item: slot });
1806
1870
  });
1807
1871
  }
1808
1872
  insertAt(key, index, pk) {
@@ -1811,9 +1875,21 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
1811
1875
  const safeIndex = this.index.insertSlotAt(key, index, slot);
1812
1876
  this.appendCommand(key, {
1813
1877
  type: "insert",
1814
- pk,
1815
- slot,
1816
- index: safeIndex
1878
+ index: safeIndex,
1879
+ item: slot
1880
+ });
1881
+ });
1882
+ }
1883
+ /** Replace the element at `index` with the slot for `pk`, in place. */
1884
+ setAt(key, index, pk) {
1885
+ this.withWrite(() => {
1886
+ const slot = this.resolvePk(pk);
1887
+ const safeIndex = this.index.setSlotAt(key, index, slot);
1888
+ if (safeIndex < 0) return;
1889
+ this.appendCommand(key, {
1890
+ type: "set",
1891
+ index: safeIndex,
1892
+ item: slot
1817
1893
  });
1818
1894
  });
1819
1895
  }
@@ -1821,7 +1897,7 @@ var OIMCollectionOrderedListCommandStream = class extends OIMOrderedListCommandS
1821
1897
  this.withWrite(() => {
1822
1898
  const slots = pks.map((pk) => this.resolvePk(pk));
1823
1899
  this.index.resetSlots(key, slots);
1824
- this.appendSetCommand(key, slots);
1900
+ this.appendResetCommand(key, slots);
1825
1901
  });
1826
1902
  }
1827
1903
  getSlotsByKey(key) {
@@ -3400,20 +3476,27 @@ var OIMCollectionChangedFields = class {
3400
3476
  constructor(queue, collection, opts) {
3401
3477
  this.isInWrite = false;
3402
3478
  this.lastEntitySnapshotByPk = /* @__PURE__ */ new Map();
3479
+ // PKs written through this wrapper during the current flush. Their fields are
3480
+ // already tracked accurately via trackPatch, so the flush-time external-write
3481
+ // handler skips them — no redundant diff + snapshot re-clone. Cleared per flush.
3482
+ this.writtenThroughThisFlush = /* @__PURE__ */ new Set();
3403
3483
  this.changedFieldsByPk = /* @__PURE__ */ new Map();
3404
3484
  this.changedPksByField = /* @__PURE__ */ new Map();
3405
3485
  this.onAfterFlush = () => {
3406
3486
  this.clear();
3487
+ this.writtenThroughThisFlush.clear();
3407
3488
  };
3408
3489
  this.onCollectionUpdate = (payload) => {
3409
3490
  if (this.isInWrite) return;
3410
3491
  if (payload.pks.length === 0) return;
3411
3492
  for (let i = 0; i < payload.pks.length; i++) {
3412
3493
  const pk = payload.pks[i];
3494
+ if (this.writtenThroughThisFlush.has(pk)) continue;
3413
3495
  this.trackExternalMutationByPk(pk);
3414
3496
  }
3415
3497
  };
3416
3498
  this.collection = collection;
3499
+ this.detectExternalMutations = opts?.detectExternalMutations !== false;
3417
3500
  this.selectPk = opts?.selectPk ?? (() => {
3418
3501
  throw new Error(
3419
3502
  `[OIMCollectionChangedFields]: selectPk was not provided. Use upsertOneByPk(...) or pass opts.selectPk.`
@@ -3428,15 +3511,17 @@ var OIMCollectionChangedFields = class {
3428
3511
  0 /* AFTER_FLUSH */,
3429
3512
  this.onAfterFlush
3430
3513
  );
3431
- const maybeReactive = collection;
3432
- this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
3433
- this.onCollectionUpdate({
3434
- pks
3435
- });
3436
- }) : collection.emitter.on(
3437
- 0 /* UPDATE */,
3438
- this.onCollectionUpdate
3439
- );
3514
+ if (this.detectExternalMutations) {
3515
+ const maybeReactive = collection;
3516
+ this.unsubscribeFromCollectionEmitter = maybeReactive.subscribeOnAnyUpdate ? maybeReactive.subscribeOnAnyUpdate((pks) => {
3517
+ this.onCollectionUpdate({
3518
+ pks
3519
+ });
3520
+ }) : collection.emitter.on(
3521
+ 0 /* UPDATE */,
3522
+ this.onCollectionUpdate
3523
+ );
3524
+ }
3440
3525
  }
3441
3526
  upsertOneByPk(pk, patch) {
3442
3527
  const slot = this.withWrite(() => {
@@ -3492,7 +3577,7 @@ var OIMCollectionChangedFields = class {
3492
3577
  this.changedPksEventEmitter.destroy();
3493
3578
  this.changedFieldsEventEmitter.destroy();
3494
3579
  this.unsubscribeAfterFlush();
3495
- this.unsubscribeFromCollectionEmitter();
3580
+ this.unsubscribeFromCollectionEmitter?.();
3496
3581
  this.clear();
3497
3582
  }
3498
3583
  withWrite(fn) {
@@ -3534,6 +3619,8 @@ var OIMCollectionChangedFields = class {
3534
3619
  return changed;
3535
3620
  }
3536
3621
  updateEntitySnapshot(pk) {
3622
+ if (!this.detectExternalMutations) return;
3623
+ this.writtenThroughThisFlush.add(pk);
3537
3624
  const entity = this.collection.getOneByPk(pk);
3538
3625
  if (!entity) {
3539
3626
  this.lastEntitySnapshotByPk.delete(pk);
@@ -3573,6 +3660,162 @@ var OIMCollectionChangedFields = class {
3573
3660
  }
3574
3661
  };
3575
3662
 
3663
+ // src/modules/wrapper/index/OIMOrderedListMappedCommandStream.ts
3664
+ var OIMOrderedListMappedCommandStream = class _OIMOrderedListMappedCommandStream {
3665
+ constructor(source, opts) {
3666
+ /** Per-key positional mirror of the mapped elements. */
3667
+ this.mirror = /* @__PURE__ */ new Map();
3668
+ /** Mapped commands produced for the current delivery, per key. */
3669
+ this.buffer = /* @__PURE__ */ new Map();
3670
+ this.subscribers = /* @__PURE__ */ new Map();
3671
+ this.sourceUnsubscribers = /* @__PURE__ */ new Map();
3672
+ this.source = source;
3673
+ this.create = opts.create;
3674
+ this.destroyItem = opts.destroy;
3675
+ }
3676
+ subscribeCommands(key, handler) {
3677
+ this.ensureWired(key);
3678
+ let subs = this.subscribers.get(key);
3679
+ if (!subs) {
3680
+ subs = /* @__PURE__ */ new Set();
3681
+ this.subscribers.set(key, subs);
3682
+ }
3683
+ subs.add(handler);
3684
+ return () => {
3685
+ const current = this.subscribers.get(key);
3686
+ if (!current) return;
3687
+ current.delete(handler);
3688
+ if (current.size === 0) this.subscribers.delete(key);
3689
+ };
3690
+ }
3691
+ consumeCommands(key) {
3692
+ const cmds = this.buffer.get(key);
3693
+ if (!cmds || cmds.length === 0) return [];
3694
+ return cmds.slice();
3695
+ }
3696
+ getItemsByKey(key) {
3697
+ this.ensureWired(key);
3698
+ return this.mirror.get(key) ?? [];
3699
+ }
3700
+ /** Map this source again, chaining the projection. */
3701
+ map(opts) {
3702
+ return new _OIMOrderedListMappedCommandStream(
3703
+ this,
3704
+ opts
3705
+ );
3706
+ }
3707
+ destroy() {
3708
+ this.sourceUnsubscribers.forEach((unsubscribe) => unsubscribe());
3709
+ this.sourceUnsubscribers.clear();
3710
+ if (this.destroyItem) {
3711
+ this.mirror.forEach(
3712
+ (items) => items.forEach((item) => this.destroyItem(item))
3713
+ );
3714
+ }
3715
+ this.mirror.clear();
3716
+ this.buffer.clear();
3717
+ this.subscribers.clear();
3718
+ }
3719
+ /** Build the initial mirror and subscribe to the source once per key. */
3720
+ ensureWired(key) {
3721
+ if (this.mirror.has(key)) return;
3722
+ const items = this.source.getItemsByKey(key);
3723
+ this.mirror.set(key, items.map((item) => this.create(item)));
3724
+ const unsubscribe = this.source.subscribeCommands(
3725
+ key,
3726
+ () => this.onSourceCommands(key)
3727
+ );
3728
+ this.sourceUnsubscribers.set(key, unsubscribe);
3729
+ }
3730
+ onSourceCommands(key) {
3731
+ const cmds = this.source.consumeCommands(key);
3732
+ if (cmds.length === 0) return;
3733
+ let mirror = this.mirror.get(key);
3734
+ if (!mirror) {
3735
+ mirror = [];
3736
+ this.mirror.set(key, mirror);
3737
+ }
3738
+ const out = [];
3739
+ for (let i = 0; i < cmds.length; i++) {
3740
+ const cmd = cmds[i];
3741
+ switch (cmd.type) {
3742
+ case "insert": {
3743
+ const mapped = this.create(cmd.item);
3744
+ mirror.splice(cmd.index, 0, mapped);
3745
+ out.push({
3746
+ type: "insert",
3747
+ index: cmd.index,
3748
+ item: mapped
3749
+ });
3750
+ break;
3751
+ }
3752
+ case "remove": {
3753
+ const count = cmd.count ?? 1;
3754
+ const removed = mirror.splice(cmd.index, count);
3755
+ if (this.destroyItem) {
3756
+ for (let r = 0; r < removed.length; r++) {
3757
+ this.destroyItem(removed[r]);
3758
+ }
3759
+ }
3760
+ out.push({ type: "remove", index: cmd.index, count });
3761
+ break;
3762
+ }
3763
+ case "move": {
3764
+ const count = cmd.count ?? 1;
3765
+ const segment = mirror.splice(cmd.from, count);
3766
+ mirror.splice(cmd.to, 0, ...segment);
3767
+ out.push({
3768
+ type: "move",
3769
+ from: cmd.from,
3770
+ to: cmd.to,
3771
+ count
3772
+ });
3773
+ break;
3774
+ }
3775
+ case "set": {
3776
+ const mapped = this.create(cmd.item);
3777
+ const replaced = mirror.splice(cmd.index, 1, mapped);
3778
+ if (this.destroyItem && replaced.length > 0) {
3779
+ this.destroyItem(replaced[0]);
3780
+ }
3781
+ out.push({ type: "set", index: cmd.index, item: mapped });
3782
+ break;
3783
+ }
3784
+ case "reset": {
3785
+ if (this.destroyItem) {
3786
+ for (let r = 0; r < mirror.length; r++) {
3787
+ this.destroyItem(mirror[r]);
3788
+ }
3789
+ }
3790
+ const mapped = cmd.items.map((item) => this.create(item));
3791
+ mirror = mapped;
3792
+ this.mirror.set(key, mapped);
3793
+ out.push({ type: "reset", items: mapped });
3794
+ break;
3795
+ }
3796
+ }
3797
+ }
3798
+ this.buffer.set(key, out);
3799
+ try {
3800
+ const subs = this.subscribers.get(key);
3801
+ if (subs && subs.size > 0) {
3802
+ const snapshot = Array.from(subs);
3803
+ for (let s = 0; s < snapshot.length; s++) {
3804
+ const handler = snapshot[s];
3805
+ if (subs.has(handler)) handler();
3806
+ }
3807
+ }
3808
+ } finally {
3809
+ this.buffer.delete(key);
3810
+ }
3811
+ }
3812
+ };
3813
+
3814
+ // src/modules/wrapper/index/createOIMOrderedListMappedCommandStream.ts
3815
+ function createOIMOrderedListMappedCommandStream(source, opts) {
3816
+ return new OIMOrderedListMappedCommandStream(source, opts);
3817
+ }
3818
+
3576
3819
  // src/core/event-queue-scheduler/OIMEventQueueSchedulerAnimationFrame.ts
3577
3820
  var OIMEventQueueSchedulerAnimationFrame = class extends OIMEventQueueScheduler {
3578
3821
  constructor() {
@@ -4206,6 +4449,7 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
4206
4449
  OIMObjectValueByKeySelector,
4207
4450
  OIMObjectValuesByKeysSelector,
4208
4451
  OIMOrderedListCommandStream,
4452
+ OIMOrderedListMappedCommandStream,
4209
4453
  OIMPkSelectorFactory,
4210
4454
  OIMReactiveCollection,
4211
4455
  OIMReactiveCollectionIndexManualArrayBased,
@@ -4218,6 +4462,8 @@ var OIMIndexPksByKeysArrayBasedSelector = class extends OIMSelector {
4218
4462
  OIMReactiveObject,
4219
4463
  OIMSelector,
4220
4464
  createInPlaceEntityUpdater,
4465
+ createMergeEntityUpdater,
4221
4466
  createOIMCollectionIndexFactory,
4222
- createOIMCollectionKit
4467
+ createOIMCollectionKit,
4468
+ createOIMOrderedListMappedCommandStream
4223
4469
  });