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