doxum 0.1.16 → 0.1.17

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
@@ -1,8 +1,8 @@
1
- import { A as insert, D as equal, F as remove, L as profile, M as keys, N as matches, _ as resolveTreeContainer, a as compiledShape, c as debugKey, d as overlaps, f as read$1, g as resolveLocated, h as resolveContainer, i as AddressIndex, j as install, l as memberKey, n as fail, o as contains, p as resolveAddress, r as invalidValue, s as createAddressResolver, t as MutationRejected, v as resolveValue } from "./issue-DVaGQGeP.js";
1
+ import { A as insert, D as equal, F as remove, L as profile, M as keys, N as matches, _ as resolveTreeContainer, a as compiledShape, c as debugKey, d as overlaps, f as read$1, g as resolveLocated, h as resolveContainer, i as AddressIndex, j as install, l as memberKey, n as fail, o as contains, p as resolveAddress, r as invalidValue, s as createAddressResolver, t as MutationRejected, u as nodeAt, v as resolveValue } from "./issue-DVaGQGeP.js";
2
2
  import { a as ParseError, c as copyValue, i as snapshot, l as equalValue, n as createAccess, o as checkKey, r as replace, s as checkValue, t as collectionAccess, u as parse } from "./scope-d-L87NeC.js";
3
3
  import { _ as affected, a as documentReadableOwner, d as accessOf, f as bindRuntimeAccess, g as TransactionRejected, h as DocumentReentrancyError, i as disposeNotification, l as subscribeRoot, m as DocumentDisposedError, n as bindDocumentReadable, o as notify, p as readWith, r as createNotification, s as shareNotification, t as attachProjection, u as subscribeTargets, v as belongs, y as same } from "./notification-IQIJyH3Q.js";
4
4
  import { n as bindRuntimeDriver, o as decodeChanges, r as disposeRuntimeDriver, s as sealChanges, t as assertRuntimeWritable } from "./driver-CSnhMjbP.js";
5
- import { a as input, i as derive, o as observe, r as definitionOf } from "./definition-c6XQXzvK.js";
5
+ import { a as input, i as derive, o as observe, r as definitionOf } from "./definition-DUo1lfQ8.js";
6
6
  //#region core/src/schema.ts
7
7
  const node = (value) => Object.freeze(value);
8
8
  const field = (validator) => node({
@@ -1205,7 +1205,7 @@ const createScheduler = (onError) => {
1205
1205
  const blocked = node.sources.find((source) => source.fault || source.disposed);
1206
1206
  if (blocked) node.fault = errorFor(node, blocked.fault ?? new ProjectionDisposedError(), "blocked");
1207
1207
  else {
1208
- const build = node.forced || wasFaulted || node.sources.some((source) => source.reset());
1208
+ const build = wasFaulted || node.sources.some((source) => source.reset());
1209
1209
  try {
1210
1210
  changed = node.evaluate(build);
1211
1211
  const failure = node.sources.find((source) => source.fault)?.fault;
@@ -1226,8 +1226,6 @@ const createScheduler = (onError) => {
1226
1226
  }
1227
1227
  }
1228
1228
  }
1229
- node.forced = false;
1230
- node.statusChanged = wasFaulted !== (node.fault !== void 0);
1231
1229
  completed.push(node);
1232
1230
  if (changed || wasFaulted || node.fault) {
1233
1231
  profile.projection("publishedNodes");
@@ -1367,23 +1365,6 @@ const createScheduler = (onError) => {
1367
1365
  phase = "idle";
1368
1366
  }
1369
1367
  },
1370
- rebuild(node) {
1371
- assertIdle();
1372
- if (node.disposed) throw new ProjectionDisposedError();
1373
- node.forced = true;
1374
- enqueue(node);
1375
- run();
1376
- },
1377
- disposeNode(node) {
1378
- if (node.disposed) return;
1379
- assertIdle();
1380
- if (node.consumers.size) throw new Error("Cannot dispose a projection node with downstream consumers.");
1381
- node.disposed = true;
1382
- node.sources.forEach((source) => source.consumers.delete(node));
1383
- nodes.delete(node);
1384
- node.release();
1385
- for (const [handle, record] of records) if (record === node) records.delete(handle);
1386
- },
1387
1368
  batch,
1388
1369
  batchContext: () => activeBatch,
1389
1370
  dispose() {
@@ -1393,521 +1374,49 @@ const createScheduler = (onError) => {
1393
1374
  const failures = [];
1394
1375
  cleanups.forEach((cleanup) => {
1395
1376
  try {
1396
- cleanup();
1397
- } catch (error) {
1398
- failures.push(error);
1399
- }
1400
- });
1401
- cleanups.clear();
1402
- nodes.forEach((node) => {
1403
- node.disposed = true;
1404
- node.release();
1405
- node.consumers.clear();
1406
- });
1407
- records.forEach((source) => {
1408
- source.disposed = true;
1409
- source.consumers.clear();
1410
- source.clear();
1411
- });
1412
- nodes.clear();
1413
- records.clear();
1414
- pending.clear();
1415
- heap.length = 0;
1416
- completed.length = 0;
1417
- queued.clear();
1418
- emissions.clear();
1419
- guards.clear();
1420
- errors.length = 0;
1421
- reportingFailures = [];
1422
- if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
1423
- },
1424
- debug: () => ({
1425
- nodes: nodes.size,
1426
- sources: records.size - nodes.size,
1427
- subscriptions: cleanups.size,
1428
- pending: pending.size + heap.length
1429
- })
1430
- };
1431
- };
1432
- const assertSynchronous = (value) => {
1433
- if (value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") throw new TypeError("Projection callbacks must be synchronous.");
1434
- };
1435
- const assertScope = (active) => {
1436
- if (!active()) throw new Error("Projection reader or writer is no longer active.");
1437
- };
1438
- //#endregion
1439
- //#region core/src/projection/node.ts
1440
- const createNode = (scheduler, spec, behavior) => {
1441
- scheduler.assertIdle();
1442
- const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
1443
- const order = scheduler.order();
1444
- const node = {
1445
- ...behavior,
1446
- publish: () => {
1447
- if (node.fault) behavior.clear();
1448
- else behavior.publish();
1449
- },
1450
- order,
1451
- name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
1452
- sources: entries.map(([, source]) => source),
1453
- consumers: /* @__PURE__ */ new Set(),
1454
- disposed: false,
1455
- fault: void 0,
1456
- forced: false,
1457
- statusChanged: false,
1458
- evaluate: (build) => {
1459
- let active = true;
1460
- try {
1461
- const scopeActive = () => active;
1462
- const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(scopeActive)]));
1463
- return behavior.evaluate(inputs, build, scopeActive);
1464
- } finally {
1465
- active = false;
1466
- }
1467
- }
1468
- };
1469
- scheduler.initialize(() => {
1470
- const invalid = node.sources.find((source) => source.fault || source.disposed);
1471
- if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
1472
- node.evaluate(true);
1473
- node.publish();
1474
- node.clear();
1475
- });
1476
- const check = () => {
1477
- if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
1478
- if (node.fault) throw node.fault;
1479
- };
1480
- return {
1481
- node,
1482
- check,
1483
- install(handle) {
1484
- scheduler.register(handle, node);
1485
- scheduler.addNode(node);
1486
- },
1487
- rebuild: () => scheduler.rebuild(node),
1488
- dispose: () => scheduler.disposeNode(node)
1489
- };
1490
- };
1491
- //#endregion
1492
- //#region core/src/projection/collection.ts
1493
- const collectionHandles = /* @__PURE__ */ new WeakSet();
1494
- const readonlySet = (values) => {
1495
- const set = new Set(values);
1496
- const view = Object.freeze({
1497
- get size() {
1498
- return set.size;
1499
- },
1500
- has: (value) => set.has(value),
1501
- entries: () => set.entries(),
1502
- keys: () => set.keys(),
1503
- values: () => set.values(),
1504
- [Symbol.iterator]: () => set.values(),
1505
- forEach: (callback, thisArg) => set.forEach((value) => callback.call(thisArg, value, value, view))
1506
- });
1507
- return view;
1508
- };
1509
- const createCollection = (scheduler, spec) => {
1510
- const values = /* @__PURE__ */ new Map();
1511
- let ids = Object.freeze([]);
1512
- let staged = /* @__PURE__ */ new Map();
1513
- let nextIds = ids;
1514
- let change;
1515
- let instance;
1516
- let initialized = false;
1517
- let reset = false;
1518
- let revision = 0;
1519
- let idsRevision = 0;
1520
- let batch;
1521
- let cause;
1522
- let publishedTransitions = Object.freeze([]);
1523
- let all;
1524
- const items = /* @__PURE__ */ new Map();
1525
- const subscribedItems = /* @__PURE__ */ new Set();
1526
- const idsListeners = /* @__PURE__ */ new Set();
1527
- const allListeners = /* @__PURE__ */ new Set();
1528
- const listeners = /* @__PURE__ */ new Set();
1529
- const changedKeys = /* @__PURE__ */ new Set();
1530
- let orderChanged = false;
1531
- const current = Object.freeze({
1532
- get: (key) => {
1533
- owner.check();
1534
- return values.get(key);
1535
- },
1536
- has: (key) => {
1537
- owner.check();
1538
- return values.has(key);
1539
- },
1540
- ids: () => {
1541
- owner.check();
1542
- return ids;
1543
- }
1544
- });
1545
- const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
1546
- const getNext = (key) => {
1547
- const entry = staged.get(key);
1548
- return entry ? entry.present ? entry.value : void 0 : values.get(key);
1549
- };
1550
- const owner = createNode(scheduler, spec, {
1551
- evaluate: (sources, build, active) => {
1552
- const metadata = Object.values(sources).find((value) => value && typeof value === "object" && ("batch" in value && value.batch !== void 0 || "cause" in value && value.cause !== void 0));
1553
- batch = scheduler.batchContext() ?? metadata?.batch;
1554
- cause = metadata?.cause ?? batch?.cause;
1555
- staged = /* @__PURE__ */ new Map();
1556
- nextIds = ids;
1557
- change = void 0;
1558
- changedKeys.clear();
1559
- let explicitOrder;
1560
- let cleared = false;
1561
- const nextHas = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
1562
- const nextGet = (key) => {
1563
- const entry = staged.get(key);
1564
- return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
1565
- };
1566
- const deriveIds = () => {
1567
- const result = cleared ? [] : ids.filter(nextHas);
1568
- for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
1569
- return result;
1570
- };
1571
- const read = (next) => ({
1572
- get: (key) => {
1573
- assertScope(active);
1574
- return next ? nextGet(key) : values.get(key);
1575
- },
1576
- has: (key) => {
1577
- assertScope(active);
1578
- return next ? nextHas(key) : values.has(key);
1579
- },
1580
- ids: () => {
1581
- assertScope(active);
1582
- return next ? Object.freeze(explicitOrder ? explicitOrder.slice() : deriveIds()) : ids;
1583
- }
1584
- });
1585
- const writer = {
1586
- set: (key, value) => {
1587
- assertScope(active);
1588
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1589
- staged.set(key, {
1590
- present: true,
1591
- value
1592
- });
1593
- },
1594
- remove: (key) => {
1595
- assertScope(active);
1596
- staged.set(key, { present: false });
1597
- },
1598
- order: (order) => {
1599
- assertScope(active);
1600
- explicitOrder = order.slice();
1601
- },
1602
- replace: (entries) => {
1603
- assertScope(active);
1604
- staged.clear();
1605
- cleared = true;
1606
- const order = [];
1607
- for (const [key, value] of entries) {
1608
- if (staged.has(key)) throw new TypeError("Duplicate projection key.");
1609
- writer.set(key, value);
1610
- order.push(key);
1611
- }
1612
- explicitOrder = order;
1613
- }
1614
- };
1615
- const input = {
1616
- sources,
1617
- previous: read(false),
1618
- next: read(true),
1619
- writer
1620
- };
1621
- if (build || !instance) {
1622
- profile.materialized.rebuilt();
1623
- instance = void 0;
1624
- cleared = true;
1625
- const built = spec.build(input);
1626
- assertSynchronous(built);
1627
- instance = built;
1628
- } else {
1629
- profile.materialized.updated();
1630
- const result = instance.update(input);
1631
- assertSynchronous(result);
1632
- if (result?.kind === "rebuild") {
1633
- staged.clear();
1634
- cleared = true;
1635
- explicitOrder = void 0;
1636
- instance = void 0;
1637
- const built = spec.build(input);
1638
- assertSynchronous(built);
1639
- instance = built;
1640
- build = true;
1641
- }
1642
- }
1643
- if (cleared) {
1644
- for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
1645
- }
1646
- const added = /* @__PURE__ */ new Set();
1647
- const removed = /* @__PURE__ */ new Set();
1648
- const updated = /* @__PURE__ */ new Set();
1649
- profile.projection("touchedKeys", staged.size);
1650
- for (const [key, entry] of staged) {
1651
- const existed = values.has(key);
1652
- if (entry.present) if (!existed) added.add(key);
1653
- else if (!(spec.isEqual ?? Object.is)(values.get(key), entry.value)) updated.add(key);
1654
- else staged.delete(key);
1655
- else if (existed) removed.add(key);
1656
- else staged.delete(key);
1657
- }
1658
- if (explicitOrder || added.size || removed.size) {
1659
- const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
1660
- if (new Set(order).size !== order.length || order.length !== values.size + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
1661
- nextIds = ids.length === order.length && ids.every((key, i) => key === order[i]) ? ids : Object.freeze(order.slice());
1662
- }
1663
- orderChanged = nextIds !== ids;
1664
- if (added.size || removed.size || updated.size || orderChanged) {
1665
- let commonOrderChanged = false;
1666
- if (orderChanged) {
1667
- const before = ids.filter((key) => !removed.has(key));
1668
- const after = nextIds.filter((key) => !added.has(key));
1669
- commonOrderChanged = before.some((key, i) => after[i] !== key);
1670
- }
1671
- change = Object.freeze({
1672
- kind: "incremental",
1673
- added: readonlySet(added),
1674
- removed: readonlySet(removed),
1675
- updated: readonlySet(updated),
1676
- orderChanged: commonOrderChanged
1677
- });
1678
- [
1679
- ...added,
1680
- ...removed,
1681
- ...updated
1682
- ].forEach((key) => changedKeys.add(key));
1683
- profile.projection("changedKeys", changedKeys.size);
1684
- }
1685
- const transitions = [];
1686
- for (const key of changedKeys) {
1687
- const beforePresent = values.has(key);
1688
- const afterPresent = hasNext(key);
1689
- if (beforePresent === afterPresent) {
1690
- if (!beforePresent) continue;
1691
- if ((spec.isEqual ?? Object.is)(values.get(key), getNext(key))) continue;
1692
- }
1693
- if (!beforePresent) transitions.push({
1694
- key,
1695
- kind: "added",
1696
- before: void 0,
1697
- after: getNext(key)
1698
- });
1699
- else if (!afterPresent) transitions.push({
1700
- key,
1701
- kind: "removed",
1702
- before: values.get(key),
1703
- after: void 0
1704
- });
1705
- else transitions.push({
1706
- key,
1707
- kind: "updated",
1708
- before: values.get(key),
1709
- after: getNext(key)
1710
- });
1711
- }
1712
- publishedTransitions = Object.freeze(transitions);
1713
- reset = build;
1714
- return change !== void 0;
1715
- },
1716
- context: (active) => {
1717
- const transitions = (keys) => {
1718
- assertScope(active);
1719
- const selected = keys ? new Set(keys) : void 0;
1720
- return Object.freeze(publishedTransitions.filter((transition) => !selected || selected.has(transition.key)));
1721
- };
1722
- const input = {
1723
- get: (key) => {
1724
- assertScope(active);
1725
- return getNext(key);
1726
- },
1727
- has: (key) => {
1728
- assertScope(active);
1729
- return hasNext(key);
1730
- },
1731
- ids: () => {
1732
- assertScope(active);
1733
- return nextIds;
1734
- },
1735
- previous: Object.freeze({
1736
- get: (key) => {
1737
- assertScope(active);
1738
- return values.get(key);
1739
- },
1740
- has: (key) => {
1741
- assertScope(active);
1742
- return values.has(key);
1743
- },
1744
- ids: () => {
1745
- assertScope(active);
1746
- return ids;
1747
- }
1748
- }),
1749
- change,
1750
- revision: revision + (change ? 1 : 0),
1751
- reset,
1752
- transitions,
1753
- batch,
1754
- cause
1755
- };
1756
- return Object.freeze(input);
1757
- },
1758
- revision: () => revision,
1759
- reset: () => reset,
1760
- publish: () => {
1761
- for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
1762
- else values.delete(key);
1763
- if (change) {
1764
- if (initialized) revision++;
1765
- if (orderChanged && initialized) idsRevision++;
1766
- all = void 0;
1767
- changedKeys.forEach((key) => {
1768
- const item = items.get(key);
1769
- if (item && initialized) item.revision++;
1770
- });
1771
- }
1772
- ids = nextIds;
1773
- initialized = true;
1774
- },
1775
- emit: (call) => {
1776
- profile.materialized.notification();
1777
- const fault = owner.node.fault !== void 0;
1778
- const resetEvent = Object.freeze({ kind: "reset" });
1779
- Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
1780
- if (orderChanged || !change || fault || owner.node.statusChanged) Array.from(idsListeners).forEach((listener) => call(listener));
1781
- Array.from(allListeners).forEach((listener) => call(listener));
1782
- if (!change || fault || owner.node.statusChanged) for (const item of subscribedItems) Array.from(item.listeners ?? []).forEach((listener) => call(listener));
1783
- else for (const key of changedKeys) Array.from(items.get(key)?.listeners ?? []).forEach((listener) => call(listener));
1784
- },
1785
- clear: () => {
1786
- staged.clear();
1787
- nextIds = ids;
1788
- change = void 0;
1789
- reset = false;
1790
- changedKeys.clear();
1791
- publishedTransitions = Object.freeze([]);
1792
- batch = void 0;
1793
- cause = void 0;
1794
- orderChanged = false;
1795
- },
1796
- release: () => {
1797
- values.clear();
1798
- staged.clear();
1799
- items.clear();
1800
- subscribedItems.clear();
1801
- idsListeners.clear();
1802
- allListeners.clear();
1803
- listeners.clear();
1804
- ids = nextIds = Object.freeze([]);
1805
- all = void 0;
1806
- instance = void 0;
1807
- }
1808
- });
1809
- const handle = {
1810
- current: () => {
1811
- owner.check();
1812
- return current;
1813
- },
1814
- ids: {
1815
- current: () => {
1816
- owner.check();
1817
- return ids;
1818
- },
1819
- revision: () => {
1820
- owner.check();
1821
- return idsRevision;
1822
- },
1823
- subscribe: (listener) => {
1824
- owner.check();
1825
- idsListeners.add(listener);
1826
- return () => {
1827
- idsListeners.delete(listener);
1828
- };
1829
- }
1830
- },
1831
- all: {
1832
- current: () => {
1833
- owner.check();
1834
- if (!all) {
1835
- profile.collectionView.arrayCopied();
1836
- all = Object.freeze(ids.map((key) => values.get(key)));
1837
- }
1838
- return all;
1839
- },
1840
- revision: () => {
1841
- owner.check();
1842
- return revision;
1843
- },
1844
- subscribe: (listener) => {
1845
- owner.check();
1846
- allListeners.add(listener);
1847
- return () => {
1848
- allListeners.delete(listener);
1849
- };
1850
- }
1851
- },
1852
- item: (key) => {
1853
- owner.check();
1854
- const old = items.get(key);
1855
- if (old) return old.readable;
1856
- const readable = Object.freeze({
1857
- current: () => {
1858
- owner.check();
1859
- return values.get(key);
1860
- },
1861
- revision: () => {
1862
- owner.check();
1863
- return item.revision;
1864
- },
1865
- subscribe: (listener) => {
1866
- owner.check();
1867
- const set = item.listeners ??= /* @__PURE__ */ new Set();
1868
- set.add(listener);
1869
- subscribedItems.add(item);
1870
- return () => {
1871
- set.delete(listener);
1872
- if (!set.size) subscribedItems.delete(item);
1873
- };
1377
+ cleanup();
1378
+ } catch (error) {
1379
+ failures.push(error);
1874
1380
  }
1875
1381
  });
1876
- const item = {
1877
- readable,
1878
- revision: 0
1879
- };
1880
- projectionHandles.add(readable);
1881
- items.set(key, item);
1882
- return readable;
1883
- },
1884
- revision: () => {
1885
- owner.check();
1886
- return revision;
1887
- },
1888
- subscribe: (listener) => {
1889
- owner.check();
1890
- listeners.add(listener);
1891
- return () => {
1892
- listeners.delete(listener);
1893
- };
1894
- },
1895
- rebuild: owner.rebuild,
1896
- dispose: owner.dispose
1382
+ cleanups.clear();
1383
+ nodes.forEach((node) => {
1384
+ node.disposed = true;
1385
+ node.release();
1386
+ node.consumers.clear();
1387
+ });
1388
+ records.forEach((source) => {
1389
+ source.disposed = true;
1390
+ source.consumers.clear();
1391
+ source.clear();
1392
+ });
1393
+ nodes.clear();
1394
+ records.clear();
1395
+ pending.clear();
1396
+ heap.length = 0;
1397
+ completed.length = 0;
1398
+ queued.clear();
1399
+ emissions.clear();
1400
+ guards.clear();
1401
+ errors.length = 0;
1402
+ reportingFailures = [];
1403
+ if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
1404
+ }
1897
1405
  };
1898
- projectionHandles.add(handle.ids);
1899
- projectionHandles.add(handle.all);
1900
- collectionHandles.add(handle);
1901
- owner.install(handle);
1902
- return Object.freeze(handle);
1406
+ };
1407
+ const assertSynchronous = (value) => {
1408
+ if (value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") throw new TypeError("Projection callbacks must be synchronous.");
1409
+ };
1410
+ const assertScope = (active) => {
1411
+ if (!active()) throw new Error("Projection reader or writer is no longer active.");
1903
1412
  };
1904
1413
  //#endregion
1905
1414
  //#region core/src/projection/source.ts
1906
1415
  const createSources = (scheduler) => {
1907
1416
  const documents = /* @__PURE__ */ new Map();
1908
1417
  const readables = /* @__PURE__ */ new Map();
1909
- const collections = /* @__PURE__ */ new WeakSet();
1910
1418
  const externalSources = /* @__PURE__ */ new WeakMap();
1419
+ const isCollectionSource = (value) => value.kind === "collection";
1911
1420
  const document = (runtime) => {
1912
1421
  scheduler.assertIdle();
1913
1422
  const state = accessOf(runtime);
@@ -1923,13 +1432,18 @@ const createSources = (scheduler) => {
1923
1432
  targets.forEach((value) => {
1924
1433
  if (!belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
1925
1434
  });
1926
- const old = bindings.find((entry) => Boolean(collection) === collections.has(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => same(value, targets[i])));
1435
+ const old = bindings.find((entry) => Boolean(collection) === isCollectionSource(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => same(value, targets[i])));
1927
1436
  if (old) return old.handle;
1928
- let commits = [];
1929
1437
  let reset = false;
1930
- let orderDirty = false;
1438
+ let orderChanged = false;
1931
1439
  const keys = /* @__PURE__ */ new Set();
1932
- let candidates;
1440
+ let baseline;
1441
+ let baselineIds = Object.freeze([]);
1442
+ let nextBaseline;
1443
+ let nextBaselineIds = Object.freeze([]);
1444
+ const collectionNode = collection ? nodeAt(state.schema, collection.address, state.document) : void 0;
1445
+ const entryNode = collectionNode && (collectionNode.kind === "map" || collectionNode.kind === "table") ? collectionNode.value : void 0;
1446
+ const sameEntry = (left, right) => entryNode ? equalValue(entryNode, left, right) : Object.is(left, right);
1933
1447
  const record = {
1934
1448
  consumers: /* @__PURE__ */ new Set(),
1935
1449
  disposed: false,
@@ -1937,11 +1451,16 @@ const createSources = (scheduler) => {
1937
1451
  revision: runtime.revision,
1938
1452
  reset: () => reset,
1939
1453
  clear: () => {
1940
- commits = [];
1941
- reset = orderDirty = false;
1454
+ if (collection && nextBaseline) {
1455
+ baseline = nextBaseline;
1456
+ baselineIds = nextBaselineIds;
1457
+ }
1458
+ reset = false;
1459
+ orderChanged = false;
1942
1460
  batch = void 0;
1943
1461
  keys.clear();
1944
- candidates = void 0;
1462
+ nextBaseline = void 0;
1463
+ nextBaselineIds = Object.freeze([]);
1945
1464
  },
1946
1465
  context: (active) => {
1947
1466
  assertScope(active);
@@ -1950,22 +1469,109 @@ const createSources = (scheduler) => {
1950
1469
  state,
1951
1470
  active
1952
1471
  };
1953
- const read = collection ? collectionAccess(context, collection.address) : createAccess(context);
1954
- return Object.freeze({
1472
+ const rawRead = collection ? collectionAccess(context, collection.address) : void 0;
1473
+ const snapshots = collection ? /* @__PURE__ */ new Map() : void 0;
1474
+ const read = collection ? Object.freeze({
1475
+ get: (key) => {
1476
+ assertScope(active);
1477
+ if (!snapshots.has(key)) snapshots.set(key, snapshot(rawRead.get(key)));
1478
+ return snapshots.get(key);
1479
+ },
1480
+ has: (key) => {
1481
+ assertScope(active);
1482
+ return rawRead.has(key);
1483
+ },
1484
+ ids: () => {
1485
+ assertScope(active);
1486
+ return rawRead.ids();
1487
+ }
1488
+ }) : createAccess(context);
1489
+ if (!collection) return Object.freeze({
1490
+ kind: "document",
1955
1491
  read,
1956
1492
  revision: runtime.revision(),
1957
- commits: Object.freeze(commits.slice()),
1958
1493
  reset: record.reset(),
1959
- batch,
1960
- cause: batch?.cause,
1961
- ...collection ? { candidates: candidates ??= Object.freeze({
1962
- keys: Object.freeze([...keys]),
1963
- orderDirty
1964
- }) } : {}
1494
+ cause: batch?.cause
1495
+ });
1496
+ if (!baseline) {
1497
+ const collectionRead = read;
1498
+ baseline = /* @__PURE__ */ new Map();
1499
+ baselineIds = Object.freeze(collectionAccess(context, collection.address).ids().slice());
1500
+ for (const key of baselineIds) {
1501
+ const value = collectionRead.get(key);
1502
+ baseline.set(key, value);
1503
+ }
1504
+ }
1505
+ const changed = [...keys];
1506
+ const added = [];
1507
+ const updated = [];
1508
+ const removed = [];
1509
+ const next = new Map(baseline);
1510
+ const collectionRead = read;
1511
+ for (const key of changed) {
1512
+ const beforePresent = baseline.has(key);
1513
+ const afterPresent = collectionRead.has(key);
1514
+ const afterValue = afterPresent ? collectionRead.get(key) : void 0;
1515
+ if (!beforePresent && afterPresent) {
1516
+ added.push({
1517
+ key,
1518
+ after: afterValue
1519
+ });
1520
+ next.set(key, afterValue);
1521
+ } else if (beforePresent && !afterPresent) {
1522
+ removed.push({
1523
+ key,
1524
+ before: baseline.get(key)
1525
+ });
1526
+ next.delete(key);
1527
+ } else if (beforePresent && afterPresent) {
1528
+ const beforeValue = baseline.get(key);
1529
+ if (!sameEntry(beforeValue, afterValue)) updated.push({
1530
+ key,
1531
+ before: beforeValue,
1532
+ after: afterValue
1533
+ });
1534
+ next.set(key, afterValue);
1535
+ }
1536
+ }
1537
+ const ids = record.reset() || orderChanged ? Object.freeze([...collectionRead.ids()]) : baselineIds;
1538
+ if (record.reset()) {
1539
+ next.clear();
1540
+ for (const key of ids) {
1541
+ const value = collectionRead.get(key);
1542
+ next.set(key, value);
1543
+ }
1544
+ }
1545
+ let order;
1546
+ if (orderChanged) {
1547
+ const beforeKeys = new Set(baselineIds);
1548
+ const afterKeys = new Set(ids);
1549
+ const beforeCommon = baselineIds.filter((key) => afterKeys.has(key));
1550
+ const afterCommon = ids.filter((key) => beforeKeys.has(key));
1551
+ if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = {
1552
+ before: Object.freeze([...baselineIds]),
1553
+ after: ids
1554
+ };
1555
+ }
1556
+ const change = record.reset() ? Object.freeze({ kind: "reset" }) : added.length || updated.length || removed.length || order ? Object.freeze({
1557
+ kind: "incremental",
1558
+ added: Object.freeze(added),
1559
+ updated: Object.freeze(updated),
1560
+ removed: Object.freeze(removed),
1561
+ ...order ? { order: Object.freeze(order) } : {}
1562
+ }) : void 0;
1563
+ nextBaseline = next;
1564
+ nextBaselineIds = ids;
1565
+ return Object.freeze({
1566
+ kind: "collection",
1567
+ read,
1568
+ change,
1569
+ revision: runtime.revision(),
1570
+ cause: batch?.cause
1965
1571
  });
1966
1572
  }
1967
1573
  };
1968
- const handle = collection ? {} : {
1574
+ const handle = collection ? { kind: "collection" } : {
1969
1575
  collection: (pick) => {
1970
1576
  scheduler.assertIdle();
1971
1577
  return make({ collection: compilePath(state.schema, "collection", pick) });
@@ -1977,10 +1583,8 @@ const createSources = (scheduler) => {
1977
1583
  }
1978
1584
  };
1979
1585
  Object.freeze(handle);
1980
- if (collection) collections.add(handle);
1981
1586
  scheduler.register(handle, record);
1982
1587
  const receive = (commit) => {
1983
- commits.push(commit);
1984
1588
  reset ||= commit.impact.kind === "reset";
1985
1589
  if (collection) {
1986
1590
  const change = collectionImpact(commit.impact, collection);
@@ -1989,9 +1593,8 @@ const createSources = (scheduler) => {
1989
1593
  change.added.forEach((key) => keys.add(key));
1990
1594
  change.removed.forEach((key) => keys.add(key));
1991
1595
  change.updated.forEach((key) => keys.add(key));
1992
- orderDirty ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
1596
+ orderChanged ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
1993
1597
  }
1994
- candidates = void 0;
1995
1598
  }
1996
1599
  };
1997
1600
  bindings.push({
@@ -2034,13 +1637,9 @@ const createSources = (scheduler) => {
2034
1637
  };
2035
1638
  const valueSource = (initial, equal) => {
2036
1639
  let value = initial;
2037
- let previous = initial;
2038
1640
  let revision = 0;
2039
- let detail;
2040
1641
  let cause;
2041
- let batch;
2042
1642
  let reset = false;
2043
- let pending = false;
2044
1643
  const handle = Object.freeze({});
2045
1644
  const record = {
2046
1645
  consumers: /* @__PURE__ */ new Set(),
@@ -2049,24 +1648,17 @@ const createSources = (scheduler) => {
2049
1648
  revision: () => revision,
2050
1649
  reset: () => reset,
2051
1650
  clear: () => {
2052
- previous = value;
2053
- pending = false;
2054
- detail = void 0;
2055
1651
  cause = void 0;
2056
- batch = void 0;
2057
1652
  reset = false;
2058
1653
  },
2059
1654
  context: (active) => {
2060
1655
  assertScope(active);
2061
1656
  return Object.freeze({
1657
+ kind: "value",
2062
1658
  value,
2063
- previous,
2064
1659
  revision,
2065
- changed: !equal(previous, value),
2066
1660
  reset,
2067
- detail,
2068
- cause,
2069
- batch
1661
+ cause
2070
1662
  });
2071
1663
  }
2072
1664
  };
@@ -2074,18 +1666,14 @@ const createSources = (scheduler) => {
2074
1666
  const accept = (next, metadata) => {
2075
1667
  scheduler.assertIdle();
2076
1668
  const recovering = record.fault !== void 0;
2077
- const eventful = metadata !== void 0 && (metadata.reset === true || metadata.revision !== void 0 && metadata.revision !== revision || metadata.detail !== void 0 || metadata.cause !== void 0 || metadata.batch !== void 0);
1669
+ const eventful = metadata !== void 0 && (metadata.reset === true || metadata.revision !== void 0 && metadata.revision !== revision || metadata.cause !== void 0);
2078
1670
  if (equal(value, next) && !recovering && !eventful) return;
2079
1671
  record.fault = void 0;
2080
- if (!pending) previous = value;
2081
1672
  value = next;
2082
1673
  revision = metadata?.revision ?? revision + 1;
2083
- detail = metadata?.detail;
2084
- cause = metadata?.cause ?? cause;
2085
- batch = scheduler.batchContext() ?? metadata?.batch ?? batch;
1674
+ cause = metadata?.cause ?? scheduler.batchContext()?.cause ?? cause;
2086
1675
  reset ||= metadata?.reset ?? false;
2087
1676
  scheduler.capture(record);
2088
- pending = true;
2089
1677
  };
2090
1678
  return {
2091
1679
  source: handle,
@@ -2112,10 +1700,8 @@ const createSources = (scheduler) => {
2112
1700
  try {
2113
1701
  input.accept(event.value, {
2114
1702
  revision: event.revision,
2115
- reset: event.reset,
2116
- detail: event.detail,
2117
- cause: event.cause,
2118
- batch: event.batch
1703
+ reset: event.reset ?? false,
1704
+ cause: event.cause
2119
1705
  });
2120
1706
  } catch (cause) {
2121
1707
  fail(cause);
@@ -2140,13 +1726,11 @@ const createSources = (scheduler) => {
2140
1726
  let revision = port.revision();
2141
1727
  let change;
2142
1728
  let reset = false;
2143
- let detail;
1729
+ let orderChanged = false;
2144
1730
  let cause;
2145
- let batch;
2146
- let publishedTransitions = Object.freeze([]);
2147
1731
  let transitionKeys = /* @__PURE__ */ new Set();
2148
1732
  let pending = false;
2149
- const handle = Object.freeze({});
1733
+ const handle = Object.freeze({ kind: "collection" });
2150
1734
  const record = {
2151
1735
  consumers: /* @__PURE__ */ new Set(),
2152
1736
  disposed: false,
@@ -2158,10 +1742,8 @@ const createSources = (scheduler) => {
2158
1742
  pending = false;
2159
1743
  change = void 0;
2160
1744
  reset = false;
2161
- detail = void 0;
1745
+ orderChanged = false;
2162
1746
  cause = void 0;
2163
- batch = void 0;
2164
- publishedTransitions = Object.freeze([]);
2165
1747
  transitionKeys.clear();
2166
1748
  },
2167
1749
  context: (active) => {
@@ -2180,101 +1762,65 @@ const createSources = (scheduler) => {
2180
1762
  return read.ids();
2181
1763
  }
2182
1764
  });
2183
- const previousRead = Object.freeze({
2184
- get: (key) => {
2185
- assertScope(active);
2186
- return previous.get(key);
2187
- },
2188
- has: (key) => {
2189
- assertScope(active);
2190
- return previous.has(key);
2191
- },
2192
- ids: () => {
2193
- assertScope(active);
2194
- return previous.ids();
2195
- }
2196
- });
2197
1765
  return Object.freeze({
2198
- ...currentRead,
2199
- previous: previousRead,
1766
+ kind: "collection",
1767
+ read: currentRead,
2200
1768
  change,
2201
1769
  revision,
2202
- reset,
2203
- transitions: (keys) => {
2204
- assertScope(active);
2205
- const selected = keys ? new Set(keys) : void 0;
2206
- return Object.freeze(publishedTransitions.filter((transition) => !selected || selected.has(transition.key)));
2207
- },
2208
- detail,
2209
- cause,
2210
- batch
1770
+ cause
2211
1771
  });
2212
1772
  }
2213
1773
  };
2214
1774
  scheduler.register(handle, record);
2215
- collections.add(handle);
2216
1775
  const fail = (error) => {
2217
1776
  record.fault = new ProjectionError("source", "external collection source", [revision], error);
2218
1777
  scheduler.capture(record);
2219
1778
  };
2220
- const addTransitionKeys = (impact) => {
2221
- if (impact?.kind === "incremental") {
2222
- impact.added.forEach((key) => transitionKeys.add(key));
2223
- impact.removed.forEach((key) => transitionKeys.add(key));
2224
- impact.updated.forEach((key) => transitionKeys.add(key));
2225
- }
2226
- };
2227
- const recomputeTransitions = () => {
2228
- const transitions = [];
1779
+ const deriveChange = () => {
1780
+ if (reset) return Object.freeze({ kind: "reset" });
1781
+ const added = [];
1782
+ const updated = [];
1783
+ const removed = [];
2229
1784
  for (const key of transitionKeys) {
2230
1785
  const beforePresent = previous.has(key);
2231
1786
  const afterPresent = read.has(key);
2232
1787
  const before = beforePresent ? previous.get(key) : void 0;
2233
1788
  const after = afterPresent ? read.get(key) : void 0;
2234
1789
  if (beforePresent === afterPresent && Object.is(before, after)) continue;
2235
- if (!beforePresent) transitions.push({
1790
+ if (!beforePresent && afterPresent) added.push({
2236
1791
  key,
2237
- kind: "added",
2238
- before: void 0,
2239
1792
  after
2240
1793
  });
2241
- else if (!afterPresent) transitions.push({
1794
+ else if (beforePresent && !afterPresent) removed.push({
2242
1795
  key,
2243
- kind: "removed",
2244
- before,
2245
- after: void 0
1796
+ before
2246
1797
  });
2247
- else transitions.push({
1798
+ else if (beforePresent && afterPresent) updated.push({
2248
1799
  key,
2249
- kind: "updated",
2250
1800
  before,
2251
1801
  after
2252
1802
  });
2253
1803
  }
2254
- publishedTransitions = Object.freeze(transitions);
2255
- };
2256
- const deriveChange = () => {
2257
- if (reset) return Object.freeze({ kind: "reset" });
2258
- const added = /* @__PURE__ */ new Set();
2259
- const removed = /* @__PURE__ */ new Set();
2260
- const updated = /* @__PURE__ */ new Set();
2261
- publishedTransitions.forEach((transition) => {
2262
- if (transition.kind === "added") added.add(transition.key);
2263
- else if (transition.kind === "removed") removed.add(transition.key);
2264
- else updated.add(transition.key);
2265
- });
2266
- const beforeIds = previous.ids();
2267
- const afterIds = read.ids();
2268
- const beforeCommon = beforeIds.filter((key) => read.has(key));
2269
- const afterCommon = afterIds.filter((key) => previous.has(key));
2270
- const orderChanged = beforeCommon.length !== afterCommon.length || beforeCommon.some((key, index) => afterCommon[index] !== key);
2271
- if (!added.size && !removed.size && !updated.size && !orderChanged) return void 0;
1804
+ let order;
1805
+ if (orderChanged) {
1806
+ const beforeIds = previous.ids();
1807
+ const afterIds = read.ids();
1808
+ const beforeKeys = new Set(beforeIds);
1809
+ const afterKeys = new Set(afterIds);
1810
+ const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1811
+ const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1812
+ if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = Object.freeze({
1813
+ before: Object.freeze([...beforeIds]),
1814
+ after: Object.freeze([...afterIds])
1815
+ });
1816
+ }
1817
+ if (!added.length && !removed.length && !updated.length && !order) return void 0;
2272
1818
  return Object.freeze({
2273
1819
  kind: "incremental",
2274
- added,
2275
- removed,
2276
- updated,
2277
- orderChanged
1820
+ added: Object.freeze(added),
1821
+ updated: Object.freeze(updated),
1822
+ removed: Object.freeze(removed),
1823
+ ...order ? { order } : {}
2278
1824
  });
2279
1825
  };
2280
1826
  let unsubscribe;
@@ -2285,12 +1831,18 @@ const createSources = (scheduler) => {
2285
1831
  if (!pending) previous = event.previous;
2286
1832
  read = port.current();
2287
1833
  revision = event.revision;
2288
- addTransitionKeys(event.change);
2289
- reset ||= event.reset || event.change?.kind === "reset";
2290
- detail = event.detail;
2291
- cause = event.cause ?? cause;
2292
- batch = scheduler.batchContext() ?? event.batch;
2293
- recomputeTransitions();
1834
+ if (event.impact?.kind === "incremental") {
1835
+ event.impact.added.forEach((key) => transitionKeys.add(key));
1836
+ event.impact.removed.forEach((key) => transitionKeys.add(key));
1837
+ event.impact.updated.forEach((key) => transitionKeys.add(key));
1838
+ orderChanged ||= Boolean(event.impact.added.size || event.impact.removed.size || event.impact.orderChanged);
1839
+ } else if (!event.impact) {
1840
+ previous.ids().forEach((key) => transitionKeys.add(key));
1841
+ read.ids().forEach((key) => transitionKeys.add(key));
1842
+ orderChanged = true;
1843
+ }
1844
+ reset ||= event.impact?.kind === "reset";
1845
+ cause = event.cause ?? scheduler.batchContext()?.cause ?? cause;
2294
1846
  change = deriveChange();
2295
1847
  record.fault = void 0;
2296
1848
  scheduler.capture(record);
@@ -2406,97 +1958,474 @@ const createSources = (scheduler) => {
2406
1958
  },
2407
1959
  assertCollection: (handle) => {
2408
1960
  scheduler.source(handle);
2409
- if (!collections.has(handle) && !collectionHandles.has(handle)) throw new Error("map requires a collection source.");
1961
+ if (!isCollectionSource(handle)) throw new Error("map requires a collection source.");
1962
+ }
1963
+ };
1964
+ };
1965
+ //#endregion
1966
+ //#region core/src/projection/node.ts
1967
+ const createNode = (scheduler, spec, behavior) => {
1968
+ scheduler.assertIdle();
1969
+ const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
1970
+ const order = scheduler.order();
1971
+ const node = {
1972
+ ...behavior,
1973
+ publish: () => {
1974
+ if (node.fault) behavior.clear();
1975
+ else behavior.publish();
1976
+ },
1977
+ order,
1978
+ name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
1979
+ sources: entries.map(([, source]) => source),
1980
+ consumers: /* @__PURE__ */ new Set(),
1981
+ disposed: false,
1982
+ fault: void 0,
1983
+ evaluate: (build) => {
1984
+ let active = true;
1985
+ try {
1986
+ const scopeActive = () => active;
1987
+ const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(scopeActive)]));
1988
+ return behavior.evaluate(inputs, build, scopeActive);
1989
+ } finally {
1990
+ active = false;
1991
+ }
1992
+ }
1993
+ };
1994
+ scheduler.initialize(() => {
1995
+ const invalid = node.sources.find((source) => source.fault || source.disposed);
1996
+ if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
1997
+ node.evaluate(true);
1998
+ node.publish();
1999
+ node.clear();
2000
+ });
2001
+ const check = () => {
2002
+ if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
2003
+ if (node.fault) throw node.fault;
2004
+ };
2005
+ return {
2006
+ node,
2007
+ check,
2008
+ install(handle) {
2009
+ scheduler.register(handle, node);
2010
+ scheduler.addNode(node);
2410
2011
  }
2411
2012
  };
2412
2013
  };
2413
- //#endregion
2414
- //#region core/src/projection/value.ts
2415
- const createValue = (scheduler, spec, options) => {
2014
+ //#endregion
2015
+ //#region core/src/projection/value.ts
2016
+ const createValue = (scheduler, spec, options) => {
2017
+ let instance;
2018
+ let value;
2019
+ let next;
2020
+ let initialized = false;
2021
+ let revision = 0;
2022
+ let changed = false;
2023
+ let reset = false;
2024
+ let cause;
2025
+ const listeners = /* @__PURE__ */ new Set();
2026
+ const owner = createNode(scheduler, spec, {
2027
+ evaluate: (sources, build) => {
2028
+ cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2029
+ let candidate;
2030
+ if (build || !instance) {
2031
+ profile.materialized.rebuilt();
2032
+ instance = void 0;
2033
+ const built = spec.build(sources);
2034
+ assertSynchronous(built);
2035
+ instance = built;
2036
+ candidate = built.value;
2037
+ } else {
2038
+ profile.materialized.updated();
2039
+ const result = instance.update(sources);
2040
+ assertSynchronous(result);
2041
+ if (result.kind === "rebuild") {
2042
+ instance = void 0;
2043
+ const built = spec.build(sources);
2044
+ assertSynchronous(built);
2045
+ instance = built;
2046
+ candidate = built.value;
2047
+ build = true;
2048
+ } else candidate = result.kind === "changed" ? result.value : value;
2049
+ }
2050
+ changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
2051
+ next = changed ? candidate : value;
2052
+ reset = build;
2053
+ return changed;
2054
+ },
2055
+ context: (active) => {
2056
+ assertScope(active);
2057
+ return Object.freeze({
2058
+ kind: "value",
2059
+ value: next,
2060
+ revision: revision + (changed ? 1 : 0),
2061
+ reset,
2062
+ cause
2063
+ });
2064
+ },
2065
+ revision: () => revision,
2066
+ reset: () => reset,
2067
+ publish: () => {
2068
+ if (changed && initialized) revision++;
2069
+ value = next;
2070
+ initialized = true;
2071
+ },
2072
+ emit: (call) => {
2073
+ profile.materialized.notification();
2074
+ Array.from(listeners).forEach((listener) => call(listener));
2075
+ },
2076
+ clear: () => {
2077
+ next = value;
2078
+ changed = false;
2079
+ reset = false;
2080
+ cause = void 0;
2081
+ },
2082
+ release: () => {
2083
+ listeners.clear();
2084
+ instance = void 0;
2085
+ value = next = void 0;
2086
+ }
2087
+ });
2088
+ const handle = Object.freeze({
2089
+ kind: "value",
2090
+ current: () => {
2091
+ owner.check();
2092
+ return value;
2093
+ },
2094
+ revision: () => {
2095
+ owner.check();
2096
+ return revision;
2097
+ },
2098
+ subscribe: (listener) => {
2099
+ owner.check();
2100
+ listeners.add(listener);
2101
+ return () => {
2102
+ listeners.delete(listener);
2103
+ };
2104
+ }
2105
+ });
2106
+ owner.install(handle);
2107
+ return handle;
2108
+ };
2109
+ //#endregion
2110
+ //#region core/src/projection/collection.ts
2111
+ const height = (node) => node?.height ?? 0;
2112
+ const makeTreeNode = (key, value, left, right) => Object.freeze({
2113
+ key,
2114
+ value,
2115
+ left,
2116
+ right,
2117
+ height: Math.max(height(left), height(right)) + 1
2118
+ });
2119
+ const rotateLeft = (node) => {
2120
+ const right = node.right;
2121
+ return makeTreeNode(right.key, right.value, makeTreeNode(node.key, node.value, node.left, right.left), right.right);
2122
+ };
2123
+ const rotateRight = (node) => {
2124
+ const left = node.left;
2125
+ return makeTreeNode(left.key, left.value, left.left, makeTreeNode(node.key, node.value, left.right, node.right));
2126
+ };
2127
+ const rebalance = (node) => {
2128
+ const balance = height(node.left) - height(node.right);
2129
+ if (balance > 1) {
2130
+ if (height(node.left.left) < height(node.left.right)) return rotateRight(makeTreeNode(node.key, node.value, rotateLeft(node.left), node.right));
2131
+ return rotateRight(node);
2132
+ }
2133
+ if (balance < -1) {
2134
+ if (height(node.right.right) < height(node.right.left)) return rotateLeft(makeTreeNode(node.key, node.value, node.left, rotateRight(node.right)));
2135
+ return rotateLeft(node);
2136
+ }
2137
+ return node;
2138
+ };
2139
+ const treeSet = (node, key, value) => {
2140
+ if (!node) return makeTreeNode(key, value);
2141
+ if (key === node.key) return makeTreeNode(key, value, node.left, node.right);
2142
+ if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeSet(node.left, key, value), node.right));
2143
+ return rebalance(makeTreeNode(node.key, node.value, node.left, treeSet(node.right, key, value)));
2144
+ };
2145
+ const treeMin = (node) => node.left ? treeMin(node.left) : node;
2146
+ const treeRemove = (node, key) => {
2147
+ if (!node) return void 0;
2148
+ if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeRemove(node.left, key), node.right));
2149
+ if (key > node.key) return rebalance(makeTreeNode(node.key, node.value, node.left, treeRemove(node.right, key)));
2150
+ if (!node.left) return node.right;
2151
+ if (!node.right) return node.left;
2152
+ const next = treeMin(node.right);
2153
+ return rebalance(makeTreeNode(next.key, next.value, node.left, treeRemove(node.right, next.key)));
2154
+ };
2155
+ const treeGet = (node, key) => {
2156
+ let current = node;
2157
+ while (current) {
2158
+ if (key === current.key) return current.value;
2159
+ current = key < current.key ? current.left : current.right;
2160
+ }
2161
+ };
2162
+ const treeHas = (node, key) => {
2163
+ let current = node;
2164
+ while (current) {
2165
+ if (key === current.key) return true;
2166
+ current = key < current.key ? current.left : current.right;
2167
+ }
2168
+ return false;
2169
+ };
2170
+ const treeFromSorted = (entries, start = 0, end = entries.length) => {
2171
+ if (start >= end) return void 0;
2172
+ const middle = start + end >> 1;
2173
+ const [key, value] = entries[middle];
2174
+ return makeTreeNode(key, value, treeFromSorted(entries, start, middle), treeFromSorted(entries, middle + 1, end));
2175
+ };
2176
+ const createCollection = (scheduler, spec) => {
2177
+ const values = /* @__PURE__ */ new Map();
2178
+ let ids = Object.freeze([]);
2179
+ let staged = /* @__PURE__ */ new Map();
2180
+ let nextIds = ids;
2181
+ let change;
2416
2182
  let instance;
2417
- let value;
2418
- let next;
2419
- let previous;
2420
2183
  let initialized = false;
2421
- let revision = 0;
2422
- let changed = false;
2423
2184
  let reset = false;
2424
- let batch;
2185
+ let revision = 0;
2186
+ let publishedRoot;
2425
2187
  let cause;
2426
2188
  const listeners = /* @__PURE__ */ new Set();
2189
+ const changedKeys = /* @__PURE__ */ new Set();
2190
+ let orderChanged = false;
2191
+ const readPublished = (root, publishedIds) => Object.freeze({
2192
+ get: (key) => {
2193
+ owner.check();
2194
+ return treeGet(root, key);
2195
+ },
2196
+ has: (key) => {
2197
+ owner.check();
2198
+ return treeHas(root, key);
2199
+ },
2200
+ ids: () => {
2201
+ owner.check();
2202
+ return publishedIds;
2203
+ }
2204
+ });
2205
+ const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
2206
+ const getNext = (key) => {
2207
+ const entry = staged.get(key);
2208
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
2209
+ };
2427
2210
  const owner = createNode(scheduler, spec, {
2428
- evaluate: (sources, build) => {
2429
- const metadata = Object.values(sources).find((value) => value && typeof value === "object" && ("batch" in value && value.batch !== void 0 || "cause" in value && value.cause !== void 0));
2430
- batch = scheduler.batchContext() ?? metadata?.batch;
2431
- cause = metadata?.cause ?? batch?.cause;
2432
- let candidate;
2211
+ evaluate: (sources, build, active) => {
2212
+ cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2213
+ staged = /* @__PURE__ */ new Map();
2214
+ nextIds = ids;
2215
+ change = void 0;
2216
+ changedKeys.clear();
2217
+ let explicitOrder;
2218
+ let cleared = false;
2219
+ const nextHas = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
2220
+ const nextGet = (key) => {
2221
+ const entry = staged.get(key);
2222
+ return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
2223
+ };
2224
+ const deriveIds = () => {
2225
+ const result = cleared ? [] : ids.filter(nextHas);
2226
+ for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
2227
+ return result;
2228
+ };
2229
+ const read = (next) => ({
2230
+ get: (key) => {
2231
+ assertScope(active);
2232
+ return next ? nextGet(key) : values.get(key);
2233
+ },
2234
+ has: (key) => {
2235
+ assertScope(active);
2236
+ return next ? nextHas(key) : values.has(key);
2237
+ },
2238
+ ids: () => {
2239
+ assertScope(active);
2240
+ return next ? Object.freeze(explicitOrder ? explicitOrder.slice() : deriveIds()) : ids;
2241
+ }
2242
+ });
2243
+ const input = {
2244
+ sources,
2245
+ previous: read(false),
2246
+ next: read(true),
2247
+ output: {
2248
+ set: (key, value) => {
2249
+ assertScope(active);
2250
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
2251
+ staged.set(key, {
2252
+ present: true,
2253
+ value
2254
+ });
2255
+ },
2256
+ remove: (key) => {
2257
+ assertScope(active);
2258
+ staged.set(key, { present: false });
2259
+ },
2260
+ order: (order) => {
2261
+ assertScope(active);
2262
+ explicitOrder = order.slice();
2263
+ }
2264
+ }
2265
+ };
2433
2266
  if (build || !instance) {
2434
2267
  profile.materialized.rebuilt();
2435
2268
  instance = void 0;
2436
- const built = spec.build(sources);
2269
+ cleared = true;
2270
+ const built = spec.build(input);
2437
2271
  assertSynchronous(built);
2438
2272
  instance = built;
2439
- candidate = built.value;
2440
2273
  } else {
2441
2274
  profile.materialized.updated();
2442
- const result = instance.update(sources);
2275
+ const result = instance.update(input);
2443
2276
  assertSynchronous(result);
2444
- if (result.kind === "rebuild") {
2277
+ if (result?.kind === "rebuild") {
2278
+ staged.clear();
2279
+ cleared = true;
2280
+ explicitOrder = void 0;
2445
2281
  instance = void 0;
2446
- const built = spec.build(sources);
2282
+ const built = spec.build(input);
2447
2283
  assertSynchronous(built);
2448
2284
  instance = built;
2449
- candidate = built.value;
2450
2285
  build = true;
2451
- } else candidate = result.kind === "changed" ? result.value : value;
2286
+ }
2287
+ }
2288
+ if (cleared) {
2289
+ for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
2290
+ }
2291
+ const added = /* @__PURE__ */ new Set();
2292
+ const removed = /* @__PURE__ */ new Set();
2293
+ const updated = /* @__PURE__ */ new Set();
2294
+ profile.projection("touchedKeys", staged.size);
2295
+ for (const [key, entry] of staged) {
2296
+ const existed = values.has(key);
2297
+ if (entry.present) if (!existed) added.add(key);
2298
+ else if (!(spec.isEqual ?? Object.is)(values.get(key), entry.value)) updated.add(key);
2299
+ else staged.delete(key);
2300
+ else if (existed) removed.add(key);
2301
+ else staged.delete(key);
2302
+ }
2303
+ if (explicitOrder || added.size || removed.size) {
2304
+ const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
2305
+ if (new Set(order).size !== order.length || order.length !== values.size + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
2306
+ nextIds = ids.length === order.length && ids.every((key, i) => key === order[i]) ? ids : Object.freeze(order.slice());
2307
+ }
2308
+ orderChanged = nextIds !== ids;
2309
+ [
2310
+ ...added,
2311
+ ...removed,
2312
+ ...updated
2313
+ ].forEach((key) => changedKeys.add(key));
2314
+ profile.projection("changedKeys", changedKeys.size);
2315
+ const addedEntries = [];
2316
+ const updatedEntries = [];
2317
+ const removedEntries = [];
2318
+ for (const key of changedKeys) {
2319
+ const beforePresent = values.has(key);
2320
+ const afterPresent = hasNext(key);
2321
+ if (beforePresent === afterPresent) {
2322
+ if (!beforePresent) continue;
2323
+ if ((spec.isEqual ?? Object.is)(values.get(key), getNext(key))) continue;
2324
+ }
2325
+ if (!beforePresent) addedEntries.push({
2326
+ key,
2327
+ after: getNext(key)
2328
+ });
2329
+ else if (!afterPresent) removedEntries.push({
2330
+ key,
2331
+ before: values.get(key)
2332
+ });
2333
+ else updatedEntries.push({
2334
+ key,
2335
+ before: values.get(key),
2336
+ after: getNext(key)
2337
+ });
2338
+ }
2339
+ if (build) change = Object.freeze({ kind: "reset" });
2340
+ else if (addedEntries.length || updatedEntries.length || removedEntries.length || orderChanged) {
2341
+ let commonOrderChanged = false;
2342
+ if (orderChanged) {
2343
+ const before = ids.filter((key) => !removed.has(key));
2344
+ const after = nextIds.filter((key) => !added.has(key));
2345
+ commonOrderChanged = before.some((key, i) => after[i] !== key);
2346
+ }
2347
+ change = Object.freeze({
2348
+ kind: "incremental",
2349
+ added: Object.freeze(addedEntries),
2350
+ updated: Object.freeze(updatedEntries),
2351
+ removed: Object.freeze(removedEntries),
2352
+ ...commonOrderChanged ? { order: Object.freeze({
2353
+ before: Object.freeze([...ids]),
2354
+ after: Object.freeze([...nextIds])
2355
+ }) } : {}
2356
+ });
2452
2357
  }
2453
- previous = value;
2454
- changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
2455
- next = changed ? candidate : value;
2456
2358
  reset = build;
2457
- return changed;
2359
+ return change !== void 0;
2458
2360
  },
2459
2361
  context: (active) => {
2460
- assertScope(active);
2461
- return Object.freeze({
2462
- value: next,
2463
- previous,
2464
- changed,
2465
- revision: revision + (changed ? 1 : 0),
2466
- reset,
2467
- cause,
2468
- batch
2469
- });
2362
+ const input = {
2363
+ kind: "collection",
2364
+ read: {
2365
+ get: (key) => {
2366
+ assertScope(active);
2367
+ return getNext(key);
2368
+ },
2369
+ has: (key) => {
2370
+ assertScope(active);
2371
+ return hasNext(key);
2372
+ },
2373
+ ids: () => {
2374
+ assertScope(active);
2375
+ return nextIds;
2376
+ }
2377
+ },
2378
+ change,
2379
+ revision: revision + (change ? 1 : 0),
2380
+ cause
2381
+ };
2382
+ return Object.freeze(input);
2470
2383
  },
2471
2384
  revision: () => revision,
2472
2385
  reset: () => reset,
2473
2386
  publish: () => {
2474
- if (changed && initialized) revision++;
2475
- value = next;
2387
+ if (change) if (change.kind === "reset") {
2388
+ const entries = [];
2389
+ for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
2390
+ entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
2391
+ publishedRoot = treeFromSorted(entries);
2392
+ } else for (const [key, entry] of staged) publishedRoot = entry.present ? treeSet(publishedRoot, key, entry.value) : treeRemove(publishedRoot, key);
2393
+ for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
2394
+ else values.delete(key);
2395
+ if (change) {
2396
+ if (initialized) revision++;
2397
+ }
2398
+ ids = nextIds;
2476
2399
  initialized = true;
2477
2400
  },
2478
2401
  emit: (call) => {
2479
2402
  profile.materialized.notification();
2480
- Array.from(listeners).forEach((listener) => call(listener));
2403
+ const resetEvent = Object.freeze({ kind: "reset" });
2404
+ Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
2481
2405
  },
2482
2406
  clear: () => {
2483
- next = value;
2484
- previous = value;
2485
- changed = false;
2407
+ staged.clear();
2408
+ nextIds = ids;
2409
+ change = void 0;
2486
2410
  reset = false;
2487
- batch = void 0;
2411
+ changedKeys.clear();
2488
2412
  cause = void 0;
2413
+ orderChanged = false;
2489
2414
  },
2490
2415
  release: () => {
2416
+ values.clear();
2417
+ staged.clear();
2418
+ publishedRoot = void 0;
2491
2419
  listeners.clear();
2420
+ ids = nextIds = Object.freeze([]);
2492
2421
  instance = void 0;
2493
- value = next = previous = void 0;
2494
2422
  }
2495
2423
  });
2496
- const handle = Object.freeze({
2424
+ const handle = {
2425
+ kind: "collection",
2497
2426
  current: () => {
2498
2427
  owner.check();
2499
- return value;
2428
+ return readPublished(publishedRoot, ids);
2500
2429
  },
2501
2430
  revision: () => {
2502
2431
  owner.check();
@@ -2508,36 +2437,17 @@ const createValue = (scheduler, spec, options) => {
2508
2437
  return () => {
2509
2438
  listeners.delete(listener);
2510
2439
  };
2511
- },
2512
- rebuild: owner.rebuild,
2513
- dispose: owner.dispose
2514
- });
2440
+ }
2441
+ };
2515
2442
  owner.install(handle);
2516
- return handle;
2443
+ return Object.freeze(handle);
2517
2444
  };
2518
2445
  //#endregion
2519
- //#region core/src/projection/runtime.ts
2520
- const createRuntimeExecutor = (options) => {
2446
+ //#region core/src/projection/graph.ts
2447
+ const createProjectionGraph = (options) => {
2521
2448
  const scheduler = createScheduler(options.onError);
2522
2449
  const sources = createSources(scheduler);
2523
- function value(input, computeOrOptions, options) {
2524
- if (typeof computeOrOptions !== "function") return createValue(scheduler, input, computeOrOptions);
2525
- const compute = (input) => {
2526
- const result = computeOrOptions(input);
2527
- assertSynchronous(result);
2528
- return result;
2529
- };
2530
- return createValue(scheduler, {
2531
- sources: input,
2532
- build: (input) => ({
2533
- value: compute(input),
2534
- update: (input) => ({
2535
- kind: "changed",
2536
- value: compute(input)
2537
- })
2538
- })
2539
- }, options);
2540
- }
2450
+ const value = (spec, options) => createValue(scheduler, spec, options);
2541
2451
  const map = (source, mapper, options) => {
2542
2452
  sources.assertCollection(source);
2543
2453
  const calculate = (id, entry) => {
@@ -2548,58 +2458,47 @@ const createRuntimeExecutor = (options) => {
2548
2458
  return createCollection(scheduler, {
2549
2459
  sources: { source },
2550
2460
  isEqual: options?.isEqual,
2551
- build: ({ sources, writer }) => {
2552
- const input = sources.source;
2553
- const read = "read" in input ? input.read : input;
2461
+ build: ({ sources, output }) => {
2462
+ const read = sources.source.read;
2554
2463
  const ids = read.ids();
2555
2464
  profile.collectionView.idsScanned(ids.length);
2556
2465
  for (const id of ids) {
2557
2466
  profile.collectionView.mapped();
2558
- writer.set(id, calculate(id, read.get(id)));
2467
+ output.set(id, calculate(id, read.get(id)));
2559
2468
  }
2560
- writer.order(ids);
2561
- return { update: ({ sources, writer }) => {
2469
+ output.order(ids);
2470
+ return { update: ({ sources, output }) => {
2562
2471
  const input = sources.source;
2563
- if (input.reset) return { kind: "rebuild" };
2564
- const read = "read" in input ? input.read : input;
2565
- let keys;
2566
- let structural;
2567
- if ("candidates" in input) {
2568
- keys = input.candidates.keys;
2569
- structural = input.candidates.orderDirty;
2570
- } else {
2571
- const change = input.change;
2572
- if (!change) return;
2573
- if (change.kind === "reset") return { kind: "rebuild" };
2574
- keys = new Set([
2575
- ...change.added,
2576
- ...change.removed,
2577
- ...change.updated
2578
- ]);
2579
- structural = Boolean(change.added.size || change.removed.size || change.orderChanged);
2472
+ const read = input.read;
2473
+ if (input.change?.kind === "reset") return { kind: "rebuild" };
2474
+ const change = input.change;
2475
+ if (!change) return;
2476
+ for (const entry of change.added) {
2477
+ profile.collectionView.mapped();
2478
+ output.set(entry.key, calculate(entry.key, entry.after));
2580
2479
  }
2581
- for (const key of keys) if (!read.has(key)) writer.remove(key);
2582
- else {
2480
+ for (const entry of change.updated) {
2583
2481
  profile.collectionView.mapped();
2584
- writer.set(key, calculate(key, read.get(key)));
2482
+ output.set(entry.key, calculate(entry.key, entry.after));
2585
2483
  }
2586
- if (structural) {
2484
+ for (const entry of change.removed) output.remove(entry.key);
2485
+ if (change.added.length || change.removed.length || change.order) {
2587
2486
  const ids = read.ids();
2588
2487
  profile.collectionView.idsScanned(ids.length);
2589
- writer.order(ids);
2488
+ output.order(ids);
2590
2489
  }
2591
2490
  } };
2592
2491
  }
2593
2492
  });
2594
2493
  };
2595
- const runtime = {
2494
+ const graph = {
2596
2495
  document: sources.document,
2597
2496
  fromSource: (source, options) => sources.fromSource(source, options),
2598
2497
  fromCollectionSource: (source) => sources.fromCollectionSource(source),
2599
2498
  input: sources.input,
2600
2499
  fromReadable: sources.fromReadable,
2601
2500
  value,
2602
- collection: () => (spec) => createCollection(scheduler, spec),
2501
+ collection: (spec) => createCollection(scheduler, spec),
2603
2502
  map,
2604
2503
  batch: scheduler.batch,
2605
2504
  dispose: () => {
@@ -2610,10 +2509,10 @@ const createRuntimeExecutor = (options) => {
2610
2509
  }
2611
2510
  }
2612
2511
  };
2613
- return Object.freeze(runtime);
2512
+ return Object.freeze(graph);
2614
2513
  };
2615
2514
  //#endregion
2616
- //#region core/src/projection/store.ts
2515
+ //#region core/src/projection/runtime.ts
2617
2516
  let activeTracker;
2618
2517
  const trackKey = (key) => {
2619
2518
  activeTracker?.keys.add(key);
@@ -2666,6 +2565,50 @@ const mapView = (read) => {
2666
2565
  };
2667
2566
  return Object.freeze(view);
2668
2567
  };
2568
+ const lazyMapView = (read) => {
2569
+ const ids = read.ids();
2570
+ const entries = function* () {
2571
+ for (const key of ids) yield [key, read.get(key)];
2572
+ };
2573
+ const values = function* () {
2574
+ for (const key of ids) yield read.get(key);
2575
+ };
2576
+ const view = {
2577
+ get: (key) => {
2578
+ trackKey(key);
2579
+ return read.get(key);
2580
+ },
2581
+ has: (key) => {
2582
+ trackKey(key);
2583
+ return read.has(key);
2584
+ },
2585
+ get size() {
2586
+ trackStructure();
2587
+ return ids.length;
2588
+ },
2589
+ keys: () => {
2590
+ trackStructure();
2591
+ return ids[Symbol.iterator]();
2592
+ },
2593
+ values: () => {
2594
+ trackAll();
2595
+ return values();
2596
+ },
2597
+ entries: () => {
2598
+ trackAll();
2599
+ return entries();
2600
+ },
2601
+ forEach: (callback, thisArg) => {
2602
+ trackAll();
2603
+ for (const key of ids) callback.call(thisArg, read.get(key), key, view);
2604
+ },
2605
+ [Symbol.iterator]: () => {
2606
+ trackAll();
2607
+ return entries();
2608
+ }
2609
+ };
2610
+ return Object.freeze(view);
2611
+ };
2669
2612
  const isMapLike = (value) => value instanceof Map || value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.keys === "function" && typeof value.entries === "function";
2670
2613
  const mapRead = (value) => ({
2671
2614
  get: (key) => value.get(key),
@@ -2673,22 +2616,35 @@ const mapRead = (value) => ({
2673
2616
  ids: () => Object.freeze([...value.keys()])
2674
2617
  });
2675
2618
  const mapChange = (previous, current) => {
2676
- const added = /* @__PURE__ */ new Set();
2677
- const removed = /* @__PURE__ */ new Set();
2678
- const updated = /* @__PURE__ */ new Set();
2679
- for (const key of previous.keys()) if (!current.has(key)) removed.add(key);
2680
- else if (!Object.is(previous.get(key), current.get(key))) updated.add(key);
2681
- for (const key of current.keys()) if (!previous.has(key)) added.add(key);
2619
+ const added = [];
2620
+ const removed = [];
2621
+ const updated = [];
2622
+ for (const key of previous.keys()) if (!current.has(key)) removed.push({
2623
+ key,
2624
+ before: previous.get(key)
2625
+ });
2626
+ else if (!Object.is(previous.get(key), current.get(key))) updated.push({
2627
+ key,
2628
+ before: previous.get(key),
2629
+ after: current.get(key)
2630
+ });
2631
+ for (const key of current.keys()) if (!previous.has(key)) added.push({
2632
+ key,
2633
+ after: current.get(key)
2634
+ });
2682
2635
  const before = [...previous.keys()].filter((key) => current.has(key));
2683
2636
  const after = [...current.keys()].filter((key) => previous.has(key));
2684
2637
  const orderChanged = before.length !== after.length || before.some((key, index) => key !== after[index]);
2685
- if (!added.size && !removed.size && !updated.size && !orderChanged) return void 0;
2638
+ if (!added.length && !removed.length && !updated.length && !orderChanged) return void 0;
2686
2639
  return {
2687
2640
  kind: "incremental",
2688
- added,
2689
- removed,
2690
- updated,
2691
- orderChanged
2641
+ added: Object.freeze(added),
2642
+ removed: Object.freeze(removed),
2643
+ updated: Object.freeze(updated),
2644
+ ...orderChanged ? { order: Object.freeze({
2645
+ before: Object.freeze([...previous.keys()]),
2646
+ after: Object.freeze([...current.keys()])
2647
+ }) } : {}
2692
2648
  };
2693
2649
  };
2694
2650
  const sameSelection = (left, right) => {
@@ -2698,45 +2654,71 @@ const sameSelection = (left, right) => {
2698
2654
  };
2699
2655
  const selectionAffects = (selection, change) => {
2700
2656
  if (!change || change.kind === "reset") return true;
2701
- if (selection.all && (change.added.size || change.removed.size || change.updated.size || change.orderChanged)) return true;
2702
- if (selection.structure && (change.added.size || change.removed.size || change.orderChanged)) return true;
2703
- for (const key of selection.keys) if (change.added.has(key) || change.removed.has(key) || change.updated.has(key)) return true;
2657
+ if (selection.all && (change.added.length || change.removed.length || change.updated.length || change.order)) return true;
2658
+ if (selection.structure && (change.added.length || change.removed.length || change.order)) return true;
2659
+ for (const key of selection.keys) if (change.added.some((entry) => entry.key === key) || change.removed.some((entry) => entry.key === key) || change.updated.some((entry) => entry.key === key)) return true;
2704
2660
  return false;
2705
2661
  };
2706
2662
  const currentValue = (event) => {
2707
2663
  if (!event || typeof event !== "object") return event;
2708
- if ("value" in event) return event.value;
2709
- if ("read" in event) return snapshot(event.read);
2710
- if ("ids" in event && typeof event.ids === "function") return mapView(event);
2664
+ if ("kind" in event && event.kind === "value") return event.value;
2665
+ if ("kind" in event && event.kind === "document") return snapshot(event.read);
2666
+ if ("kind" in event && event.kind === "collection") return mapView(event.read);
2711
2667
  return event;
2712
2668
  };
2713
2669
  const sourceOf = (instance) => instance;
2714
2670
  const createProjectionRuntime = (options) => {
2715
- const runtime = createRuntimeExecutor({ onError: options?.onError ?? (() => void 0) });
2716
- const instances = /* @__PURE__ */ new WeakMap();
2717
- const inputs = /* @__PURE__ */ new WeakMap();
2718
- const collections = /* @__PURE__ */ new WeakMap();
2671
+ const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
2672
+ const materialized = /* @__PURE__ */ new WeakMap();
2719
2673
  const materialize = (projection) => {
2720
- const old = instances.get(projection);
2721
- if (old) return old;
2674
+ const old = materialized.get(projection);
2675
+ if (old) return old.node;
2722
2676
  const definition = definitionOf(projection);
2723
2677
  let instance;
2678
+ let setter;
2724
2679
  switch (definition.kind) {
2725
2680
  case "input": {
2726
2681
  const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
2727
- inputs.set(projection, input);
2728
- instance = runtime.value({ input: input.source }, ({ input }) => input.value);
2682
+ setter = input.set;
2683
+ instance = runtime.value({
2684
+ sources: { input: input.source },
2685
+ build: (values) => ({
2686
+ value: values.input.value,
2687
+ update: (next) => ({
2688
+ kind: "changed",
2689
+ value: next.input.value
2690
+ })
2691
+ })
2692
+ });
2729
2693
  break;
2730
2694
  }
2731
2695
  case "readable": {
2732
2696
  const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
2733
- instance = runtime.value({ source }, ({ source }) => source.value);
2697
+ instance = runtime.value({
2698
+ sources: { source },
2699
+ build: (values) => ({
2700
+ value: values.source.value,
2701
+ update: (next) => ({
2702
+ kind: "changed",
2703
+ value: next.source.value
2704
+ })
2705
+ })
2706
+ });
2734
2707
  break;
2735
2708
  }
2736
2709
  case "source-value":
2737
2710
  {
2738
2711
  const source = runtime.fromSource(definition.source);
2739
- instance = runtime.value({ source }, ({ source }) => source.value);
2712
+ instance = runtime.value({
2713
+ sources: { source },
2714
+ build: (values) => ({
2715
+ value: values.source.value,
2716
+ update: (next) => ({
2717
+ kind: "changed",
2718
+ value: next.source.value
2719
+ })
2720
+ })
2721
+ });
2740
2722
  }
2741
2723
  break;
2742
2724
  case "source-collection": {
@@ -2747,26 +2729,51 @@ const createProjectionRuntime = (options) => {
2747
2729
  case "document": {
2748
2730
  const document = runtime.document(definition.document);
2749
2731
  if (!definition.selector) {
2750
- instance = runtime.value({ document }, ({ document }) => snapshot(document.read));
2732
+ instance = runtime.value({
2733
+ sources: { document },
2734
+ build: (values) => ({
2735
+ value: snapshot(values.document.read),
2736
+ update: (next) => ({
2737
+ kind: "changed",
2738
+ value: snapshot(next.document.read)
2739
+ })
2740
+ })
2741
+ });
2751
2742
  break;
2752
2743
  }
2753
2744
  const state = accessOf(definition.document);
2754
2745
  try {
2755
2746
  const collection = document.collection(definition.selector);
2756
- instance = runtime.map(collection, (_id, entry) => snapshot(entry));
2747
+ instance = runtime.map(collection, (_id, entry) => entry);
2757
2748
  break;
2758
2749
  } catch {
2759
2750
  const selected = document.targets(definition.selector);
2760
2751
  const address = compilePath(state.schema, "value", definition.selector).address;
2761
- instance = runtime.value({ selected }, () => {
2762
- return read$1(snapshot(state.document), address, state.schema);
2752
+ instance = runtime.value({
2753
+ sources: { selected },
2754
+ build: () => ({
2755
+ value: read$1(snapshot(state.document), address, state.schema),
2756
+ update: () => ({
2757
+ kind: "changed",
2758
+ value: read$1(snapshot(state.document), address, state.schema)
2759
+ })
2760
+ })
2763
2761
  });
2764
2762
  }
2765
2763
  break;
2766
2764
  }
2767
2765
  case "derive": {
2768
2766
  const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency))]));
2769
- instance = runtime.value(sources, (values) => definition.compute(...Object.values(values).map(currentValue)), { isEqual: definition.isEqual });
2767
+ instance = runtime.value({
2768
+ sources,
2769
+ build: (values) => ({
2770
+ value: definition.compute(...Object.values(values).map(currentValue)),
2771
+ update: (next) => ({
2772
+ kind: "changed",
2773
+ value: definition.compute(...Object.values(next).map(currentValue))
2774
+ })
2775
+ })
2776
+ }, { isEqual: definition.isEqual });
2770
2777
  break;
2771
2778
  }
2772
2779
  case "incremental-value": {
@@ -2779,7 +2786,7 @@ const createProjectionRuntime = (options) => {
2779
2786
  }
2780
2787
  case "incremental-collection": {
2781
2788
  const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2782
- instance = runtime.collection()({
2789
+ instance = runtime.collection({
2783
2790
  sources,
2784
2791
  build: definition.build,
2785
2792
  isEqual: definition.isEqual,
@@ -2788,32 +2795,37 @@ const createProjectionRuntime = (options) => {
2788
2795
  break;
2789
2796
  }
2790
2797
  }
2791
- instances.set(projection, instance);
2798
+ materialized.set(projection, {
2799
+ node: instance,
2800
+ set: setter
2801
+ });
2792
2802
  return instance;
2793
2803
  };
2794
2804
  const get = (projection) => {
2795
2805
  const instance = materialize(projection);
2796
2806
  const current = instance.current();
2797
- if (current && typeof current === "object" && "ids" in current && typeof current.ids === "function") {
2798
- const existing = collections.get(instance);
2807
+ if (instance.kind === "collection") {
2808
+ const record = materialized.get(projection) ?? materialized.get(instance);
2809
+ const existing = record?.collection;
2799
2810
  const revision = instance.revision();
2800
2811
  if (existing?.revision === revision) return existing.value;
2801
- const value = mapView(current);
2802
- collections.set(instance, {
2812
+ const value = lazyMapView(current);
2813
+ if (record) record.collection = {
2803
2814
  revision,
2804
2815
  value
2805
- });
2816
+ };
2806
2817
  return value;
2807
2818
  }
2808
2819
  if (isMapLike(current)) {
2809
- const existing = collections.get(instance);
2820
+ const record = materialized.get(projection) ?? materialized.get(instance);
2821
+ const existing = record?.collection;
2810
2822
  const revision = instance.revision();
2811
2823
  if (existing?.revision === revision) return existing.value;
2812
2824
  const value = mapView(mapRead(current));
2813
- collections.set(instance, {
2825
+ if (record) record.collection = {
2814
2826
  revision,
2815
2827
  value
2816
- });
2828
+ };
2817
2829
  return value;
2818
2830
  }
2819
2831
  return current;
@@ -2931,8 +2943,8 @@ const createProjectionRuntime = (options) => {
2931
2943
  readable,
2932
2944
  set: ((input, value) => {
2933
2945
  materialize(input);
2934
- const target = inputs.get(input);
2935
- if (!target) throw new TypeError("Projection is not an input.");
2946
+ const target = materialized.get(input);
2947
+ if (!target?.set) throw new TypeError("Projection is not an input.");
2936
2948
  target.set(value);
2937
2949
  }),
2938
2950
  batch: runtime.batch,