doxum 0.1.16 → 0.1.18

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.
Files changed (37) hide show
  1. package/README.md +31 -4
  2. package/dist/advanced-DgRnyRAc.js +233 -0
  3. package/dist/advanced-DgRnyRAc.js.map +1 -0
  4. package/dist/advanced-Dgu8-bPS.cjs +274 -0
  5. package/dist/advanced-Dgu8-bPS.cjs.map +1 -0
  6. package/dist/advanced-GTwIWcn6.d.cts +138 -0
  7. package/dist/advanced-c0F3gpLG.d.ts +138 -0
  8. package/dist/advanced.cjs +2 -165
  9. package/dist/advanced.d.cts +2 -42
  10. package/dist/advanced.d.ts +2 -42
  11. package/dist/advanced.js +1 -164
  12. package/dist/index.cjs +1195 -874
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +26 -4
  15. package/dist/index.d.ts +26 -4
  16. package/dist/index.js +1192 -871
  17. package/dist/index.js.map +1 -1
  18. package/dist/react.cjs +5 -10
  19. package/dist/react.cjs.map +1 -1
  20. package/dist/react.d.cts +6 -8
  21. package/dist/react.d.ts +6 -8
  22. package/dist/react.js +6 -10
  23. package/dist/react.js.map +1 -1
  24. package/package.json +3 -2
  25. package/skills/doxum-runtime/SKILL.md +2 -1
  26. package/skills/doxum-runtime/references/patterns.en.md +4 -4
  27. package/skills/doxum-runtime/references/patterns.zh-CN.md +2 -2
  28. package/skills/doxum-runtime/references/projections.en.md +19 -4
  29. package/skills/doxum-runtime/references/projections.zh-CN.md +15 -4
  30. package/dist/advanced.cjs.map +0 -1
  31. package/dist/advanced.js.map +0 -1
  32. package/dist/definition-C__VJqA4.d.cts +0 -136
  33. package/dist/definition-Dc1Udkop.d.ts +0 -136
  34. package/dist/definition-Z6TagMup.cjs +0 -107
  35. package/dist/definition-Z6TagMup.cjs.map +0 -1
  36. package/dist/definition-c6XQXzvK.js +0 -72
  37. package/dist/definition-c6XQXzvK.js.map +0 -1
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 observe, i as input, n as definitionOf, o as ownDefinition, r as derive, s as ownerOf, t as incremental } from "./advanced-DgRnyRAc.js";
6
6
  //#region core/src/schema.ts
7
7
  const node = (value) => Object.freeze(value);
8
8
  const field = (validator) => node({
@@ -1195,7 +1195,9 @@ const createScheduler = (onError) => {
1195
1195
  phase = "compute";
1196
1196
  lock(true);
1197
1197
  try {
1198
- pending.forEach((source) => source.consumers.forEach(enqueue));
1198
+ pending.forEach((source) => {
1199
+ if (source.shouldSettle?.() ?? true) source.consumers.forEach(enqueue);
1200
+ });
1199
1201
  while (heap.length) {
1200
1202
  const node = pop();
1201
1203
  if (node.disposed) continue;
@@ -1205,7 +1207,7 @@ const createScheduler = (onError) => {
1205
1207
  const blocked = node.sources.find((source) => source.fault || source.disposed);
1206
1208
  if (blocked) node.fault = errorFor(node, blocked.fault ?? new ProjectionDisposedError(), "blocked");
1207
1209
  else {
1208
- const build = node.forced || wasFaulted || node.sources.some((source) => source.reset());
1210
+ const build = wasFaulted || node.sources.some((source) => source.reset());
1209
1211
  try {
1210
1212
  changed = node.evaluate(build);
1211
1213
  const failure = node.sources.find((source) => source.fault)?.fault;
@@ -1226,8 +1228,6 @@ const createScheduler = (onError) => {
1226
1228
  }
1227
1229
  }
1228
1230
  }
1229
- node.forced = false;
1230
- node.statusChanged = wasFaulted !== (node.fault !== void 0);
1231
1231
  completed.push(node);
1232
1232
  if (changed || wasFaulted || node.fault) {
1233
1233
  profile.projection("publishedNodes");
@@ -1339,12 +1339,29 @@ const createScheduler = (onError) => {
1339
1339
  unregisterSource(handle) {
1340
1340
  const record = records.get(handle);
1341
1341
  if (record) {
1342
+ assertIdle();
1343
+ if (record.consumers.size) throw new Error("Projection source is still used by a node.");
1342
1344
  record.disposed = true;
1343
1345
  pending.delete(record);
1344
1346
  record.clear();
1345
1347
  records.delete(handle);
1346
1348
  }
1347
1349
  },
1350
+ releaseNode(handle) {
1351
+ assertIdle();
1352
+ const node = records.get(handle);
1353
+ if (!node || !nodes.has(node)) throw new Error("Unknown projection node.");
1354
+ if (node.consumers.size) throw new Error("Projection node is still used by another node.");
1355
+ node.disposed = true;
1356
+ node.sources.forEach((source) => source.consumers.delete(node));
1357
+ node.release();
1358
+ node.clear();
1359
+ nodes.delete(node);
1360
+ records.delete(handle);
1361
+ pending.delete(node);
1362
+ queued.delete(node);
1363
+ emissions.delete(node);
1364
+ },
1348
1365
  source(handle) {
1349
1366
  assertActive();
1350
1367
  const source = records.get(handle);
@@ -1367,23 +1384,6 @@ const createScheduler = (onError) => {
1367
1384
  phase = "idle";
1368
1385
  }
1369
1386
  },
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
1387
  batch,
1388
1388
  batchContext: () => activeBatch,
1389
1389
  dispose() {
@@ -1420,13 +1420,7 @@ const createScheduler = (onError) => {
1420
1420
  errors.length = 0;
1421
1421
  reportingFailures = [];
1422
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
- })
1423
+ }
1430
1424
  };
1431
1425
  };
1432
1426
  const assertSynchronous = (value) => {
@@ -1436,562 +1430,190 @@ const assertScope = (active) => {
1436
1430
  if (!active()) throw new Error("Projection reader or writer is no longer active.");
1437
1431
  };
1438
1432
  //#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
- }
1433
+ //#region core/src/projection/source.ts
1434
+ const createSources = (scheduler) => {
1435
+ const documents = /* @__PURE__ */ new Map();
1436
+ const readables = /* @__PURE__ */ new Map();
1437
+ const externalSources = /* @__PURE__ */ new WeakMap();
1438
+ const isCollectionSource = (value) => value.kind === "collection";
1439
+ const document = (runtime) => {
1440
+ scheduler.assertIdle();
1441
+ const state = accessOf(runtime);
1442
+ if (state.disposed) throw new Error("Document has been disposed.");
1443
+ const existing = documents.get(state);
1444
+ if (existing) return existing;
1445
+ const bindings = [];
1446
+ let batch;
1447
+ const make = (input) => {
1448
+ const collection = "collection" in input ? input.collection : void 0;
1449
+ const targets = "targets" in input ? input.targets : [input.collection];
1450
+ if (state.disposed) throw new Error("Document has been disposed.");
1451
+ targets.forEach((value) => {
1452
+ if (!belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
1584
1453
  });
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();
1454
+ const old = bindings.find((entry) => Boolean(collection) === isCollectionSource(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => same(value, targets[i])));
1455
+ if (old) return old.handle;
1456
+ let reset = false;
1457
+ let orderChanged = false;
1458
+ const keys = /* @__PURE__ */ new Set();
1459
+ let baseline;
1460
+ let baselineIds = Object.freeze([]);
1461
+ let nextBaseline;
1462
+ let nextBaselineIds = Object.freeze([]);
1463
+ const collectionNode = collection ? nodeAt(state.schema, collection.address, state.document) : void 0;
1464
+ const entryNode = collectionNode && (collectionNode.kind === "map" || collectionNode.kind === "table") ? collectionNode.value : void 0;
1465
+ const sameEntry = (left, right) => entryNode ? equalValue(entryNode, left, right) : Object.is(left, right);
1466
+ const record = {
1467
+ consumers: /* @__PURE__ */ new Set(),
1468
+ disposed: false,
1469
+ fault: void 0,
1470
+ revision: runtime.revision,
1471
+ reset: () => reset,
1472
+ clear: () => {
1473
+ if (collection && nextBaseline) {
1474
+ baseline = nextBaseline;
1475
+ baselineIds = nextBaselineIds;
1476
+ }
1477
+ reset = false;
1478
+ orderChanged = false;
1479
+ batch = void 0;
1480
+ keys.clear();
1481
+ nextBaseline = void 0;
1482
+ nextBaselineIds = Object.freeze([]);
1601
1483
  },
1602
- replace: (entries) => {
1484
+ context: (active) => {
1603
1485
  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);
1486
+ if (state.disposed) throw new Error("Document has been disposed.");
1487
+ const context = {
1488
+ state,
1489
+ active
1490
+ };
1491
+ const rawRead = collection ? collectionAccess(context, collection.address) : void 0;
1492
+ const snapshots = collection ? /* @__PURE__ */ new Map() : void 0;
1493
+ const read = collection ? Object.freeze({
1494
+ get: (key) => {
1495
+ assertScope(active);
1496
+ if (!snapshots.has(key)) snapshots.set(key, snapshot(rawRead.get(key)));
1497
+ return snapshots.get(key);
1498
+ },
1499
+ has: (key) => {
1500
+ assertScope(active);
1501
+ return rawRead.has(key);
1502
+ },
1503
+ ids: () => {
1504
+ assertScope(active);
1505
+ return rawRead.ids();
1506
+ }
1507
+ }) : createAccess(context);
1508
+ if (!collection) return Object.freeze({
1509
+ kind: "document",
1510
+ read,
1511
+ revision: runtime.revision(),
1512
+ reset: record.reset(),
1513
+ cause: batch?.cause
1514
+ });
1515
+ if (!baseline) {
1516
+ const collectionRead = read;
1517
+ baseline = /* @__PURE__ */ new Map();
1518
+ baselineIds = Object.freeze(collectionAccess(context, collection.address).ids().slice());
1519
+ for (const key of baselineIds) {
1520
+ const value = collectionRead.get(key);
1521
+ baseline.set(key, value);
1522
+ }
1611
1523
  }
1612
- explicitOrder = order;
1524
+ const changed = [...keys];
1525
+ const added = [];
1526
+ const updated = [];
1527
+ const removed = [];
1528
+ const next = new Map(baseline);
1529
+ const collectionRead = read;
1530
+ for (const key of changed) {
1531
+ const beforePresent = baseline.has(key);
1532
+ const afterPresent = collectionRead.has(key);
1533
+ const afterValue = afterPresent ? collectionRead.get(key) : void 0;
1534
+ if (!beforePresent && afterPresent) {
1535
+ added.push({
1536
+ key,
1537
+ after: afterValue
1538
+ });
1539
+ next.set(key, afterValue);
1540
+ } else if (beforePresent && !afterPresent) {
1541
+ removed.push({
1542
+ key,
1543
+ before: baseline.get(key)
1544
+ });
1545
+ next.delete(key);
1546
+ } else if (beforePresent && afterPresent) {
1547
+ const beforeValue = baseline.get(key);
1548
+ if (!sameEntry(beforeValue, afterValue)) updated.push({
1549
+ key,
1550
+ before: beforeValue,
1551
+ after: afterValue
1552
+ });
1553
+ next.set(key, afterValue);
1554
+ }
1555
+ }
1556
+ const ids = record.reset() || orderChanged ? Object.freeze([...collectionRead.ids()]) : baselineIds;
1557
+ if (record.reset()) {
1558
+ next.clear();
1559
+ for (const key of ids) {
1560
+ const value = collectionRead.get(key);
1561
+ next.set(key, value);
1562
+ }
1563
+ }
1564
+ let order;
1565
+ if (orderChanged) {
1566
+ const beforeKeys = new Set(baselineIds);
1567
+ const afterKeys = new Set(ids);
1568
+ const beforeCommon = baselineIds.filter((key) => afterKeys.has(key));
1569
+ const afterCommon = ids.filter((key) => beforeKeys.has(key));
1570
+ if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = {
1571
+ before: Object.freeze([...baselineIds]),
1572
+ after: ids
1573
+ };
1574
+ }
1575
+ const change = record.reset() ? Object.freeze({ kind: "reset" }) : added.length || updated.length || removed.length || order ? Object.freeze({
1576
+ kind: "incremental",
1577
+ added: Object.freeze(added),
1578
+ updated: Object.freeze(updated),
1579
+ removed: Object.freeze(removed),
1580
+ ...order ? { order: Object.freeze(order) } : {}
1581
+ }) : void 0;
1582
+ nextBaseline = next;
1583
+ nextBaselineIds = ids;
1584
+ return Object.freeze({
1585
+ kind: "collection",
1586
+ read,
1587
+ change,
1588
+ revision: runtime.revision(),
1589
+ cause: batch?.cause
1590
+ });
1613
1591
  }
1614
1592
  };
1615
- const input = {
1616
- sources,
1617
- previous: read(false),
1618
- next: read(true),
1619
- writer
1593
+ const handle = collection ? { kind: "collection" } : {
1594
+ collection: (pick) => {
1595
+ scheduler.assertIdle();
1596
+ return make({ collection: compilePath(state.schema, "collection", pick) });
1597
+ },
1598
+ targets: (...selected) => {
1599
+ scheduler.assertIdle();
1600
+ if (!selected.length) throw new TypeError("Expected at least one target.");
1601
+ return make({ targets: selected.map((pick) => compilePath(state.schema, "value", pick)) });
1602
+ }
1620
1603
  };
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
- };
1874
- }
1875
- });
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
1897
- };
1898
- projectionHandles.add(handle.ids);
1899
- projectionHandles.add(handle.all);
1900
- collectionHandles.add(handle);
1901
- owner.install(handle);
1902
- return Object.freeze(handle);
1903
- };
1904
- //#endregion
1905
- //#region core/src/projection/source.ts
1906
- const createSources = (scheduler) => {
1907
- const documents = /* @__PURE__ */ new Map();
1908
- const readables = /* @__PURE__ */ new Map();
1909
- const collections = /* @__PURE__ */ new WeakSet();
1910
- const externalSources = /* @__PURE__ */ new WeakMap();
1911
- const document = (runtime) => {
1912
- scheduler.assertIdle();
1913
- const state = accessOf(runtime);
1914
- if (state.disposed) throw new Error("Document has been disposed.");
1915
- const existing = documents.get(state);
1916
- if (existing) return existing;
1917
- const bindings = [];
1918
- let batch;
1919
- const make = (input) => {
1920
- const collection = "collection" in input ? input.collection : void 0;
1921
- const targets = "targets" in input ? input.targets : [input.collection];
1922
- if (state.disposed) throw new Error("Document has been disposed.");
1923
- targets.forEach((value) => {
1924
- if (!belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
1925
- });
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])));
1927
- if (old) return old.handle;
1928
- let commits = [];
1929
- let reset = false;
1930
- let orderDirty = false;
1931
- const keys = /* @__PURE__ */ new Set();
1932
- let candidates;
1933
- const record = {
1934
- consumers: /* @__PURE__ */ new Set(),
1935
- disposed: false,
1936
- fault: void 0,
1937
- revision: runtime.revision,
1938
- reset: () => reset,
1939
- clear: () => {
1940
- commits = [];
1941
- reset = orderDirty = false;
1942
- batch = void 0;
1943
- keys.clear();
1944
- candidates = void 0;
1945
- },
1946
- context: (active) => {
1947
- assertScope(active);
1948
- if (state.disposed) throw new Error("Document has been disposed.");
1949
- const context = {
1950
- state,
1951
- active
1952
- };
1953
- const read = collection ? collectionAccess(context, collection.address) : createAccess(context);
1954
- return Object.freeze({
1955
- read,
1956
- revision: runtime.revision(),
1957
- commits: Object.freeze(commits.slice()),
1958
- reset: record.reset(),
1959
- batch,
1960
- cause: batch?.cause,
1961
- ...collection ? { candidates: candidates ??= Object.freeze({
1962
- keys: Object.freeze([...keys]),
1963
- orderDirty
1964
- }) } : {}
1965
- });
1966
- }
1967
- };
1968
- const handle = collection ? {} : {
1969
- collection: (pick) => {
1970
- scheduler.assertIdle();
1971
- return make({ collection: compilePath(state.schema, "collection", pick) });
1972
- },
1973
- targets: (...selected) => {
1974
- scheduler.assertIdle();
1975
- if (!selected.length) throw new TypeError("Expected at least one target.");
1976
- return make({ targets: selected.map((pick) => compilePath(state.schema, "value", pick)) });
1977
- }
1978
- };
1979
- Object.freeze(handle);
1980
- if (collection) collections.add(handle);
1981
- scheduler.register(handle, record);
1982
- const receive = (commit) => {
1983
- commits.push(commit);
1984
- reset ||= commit.impact.kind === "reset";
1985
- if (collection) {
1986
- const change = collectionImpact(commit.impact, collection);
1987
- if (change.kind === "reset") reset = true;
1988
- else {
1989
- change.added.forEach((key) => keys.add(key));
1990
- change.removed.forEach((key) => keys.add(key));
1991
- change.updated.forEach((key) => keys.add(key));
1992
- orderDirty ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
1993
- }
1994
- candidates = void 0;
1604
+ Object.freeze(handle);
1605
+ scheduler.register(handle, record);
1606
+ const receive = (commit) => {
1607
+ reset ||= commit.impact.kind === "reset";
1608
+ if (collection) {
1609
+ const change = collectionImpact(commit.impact, collection);
1610
+ if (change.kind === "reset") reset = true;
1611
+ else {
1612
+ change.added.forEach((key) => keys.add(key));
1613
+ change.removed.forEach((key) => keys.add(key));
1614
+ change.updated.forEach((key) => keys.add(key));
1615
+ orderChanged ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
1616
+ }
1995
1617
  }
1996
1618
  };
1997
1619
  bindings.push({
@@ -2034,13 +1656,9 @@ const createSources = (scheduler) => {
2034
1656
  };
2035
1657
  const valueSource = (initial, equal) => {
2036
1658
  let value = initial;
2037
- let previous = initial;
2038
1659
  let revision = 0;
2039
- let detail;
2040
1660
  let cause;
2041
- let batch;
2042
1661
  let reset = false;
2043
- let pending = false;
2044
1662
  const handle = Object.freeze({});
2045
1663
  const record = {
2046
1664
  consumers: /* @__PURE__ */ new Set(),
@@ -2049,24 +1667,17 @@ const createSources = (scheduler) => {
2049
1667
  revision: () => revision,
2050
1668
  reset: () => reset,
2051
1669
  clear: () => {
2052
- previous = value;
2053
- pending = false;
2054
- detail = void 0;
2055
1670
  cause = void 0;
2056
- batch = void 0;
2057
1671
  reset = false;
2058
1672
  },
2059
1673
  context: (active) => {
2060
1674
  assertScope(active);
2061
1675
  return Object.freeze({
1676
+ kind: "value",
2062
1677
  value,
2063
- previous,
2064
1678
  revision,
2065
- changed: !equal(previous, value),
2066
1679
  reset,
2067
- detail,
2068
- cause,
2069
- batch
1680
+ cause
2070
1681
  });
2071
1682
  }
2072
1683
  };
@@ -2074,18 +1685,14 @@ const createSources = (scheduler) => {
2074
1685
  const accept = (next, metadata) => {
2075
1686
  scheduler.assertIdle();
2076
1687
  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);
1688
+ const eventful = metadata !== void 0 && (metadata.reset === true || metadata.revision !== void 0 && metadata.revision !== revision || metadata.cause !== void 0);
2078
1689
  if (equal(value, next) && !recovering && !eventful) return;
2079
1690
  record.fault = void 0;
2080
- if (!pending) previous = value;
2081
1691
  value = next;
2082
1692
  revision = metadata?.revision ?? revision + 1;
2083
- detail = metadata?.detail;
2084
- cause = metadata?.cause ?? cause;
2085
- batch = scheduler.batchContext() ?? metadata?.batch ?? batch;
1693
+ cause = metadata?.cause ?? scheduler.batchContext()?.cause ?? cause;
2086
1694
  reset ||= metadata?.reset ?? false;
2087
1695
  scheduler.capture(record);
2088
- pending = true;
2089
1696
  };
2090
1697
  return {
2091
1698
  source: handle,
@@ -2096,6 +1703,149 @@ const createSources = (scheduler) => {
2096
1703
  }
2097
1704
  };
2098
1705
  };
1706
+ const collectionInput = (initial) => {
1707
+ const values = new Map(initial);
1708
+ const touched = /* @__PURE__ */ new Map();
1709
+ let beforeIds;
1710
+ let revision = 0;
1711
+ let cause;
1712
+ const handle = Object.freeze({ kind: "collection" });
1713
+ const change = () => {
1714
+ const added = [];
1715
+ const updated = [];
1716
+ const removed = [];
1717
+ for (const [key, before] of touched) {
1718
+ const afterPresent = values.has(key);
1719
+ if (!before.present && afterPresent) added.push({
1720
+ key,
1721
+ after: values.get(key)
1722
+ });
1723
+ else if (before.present && !afterPresent) removed.push({
1724
+ key,
1725
+ before: before.value
1726
+ });
1727
+ else if (before.present && afterPresent && !Object.is(before.value, values.get(key))) updated.push({
1728
+ key,
1729
+ before: before.value,
1730
+ after: values.get(key)
1731
+ });
1732
+ }
1733
+ let order;
1734
+ if (beforeIds) {
1735
+ const afterIds = [...values.keys()];
1736
+ const beforeKeys = new Set(beforeIds);
1737
+ const afterKeys = new Set(afterIds);
1738
+ const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1739
+ const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1740
+ if (beforeCommon.some((key, index) => key !== afterCommon[index])) order = Object.freeze({
1741
+ before: beforeIds,
1742
+ after: Object.freeze(afterIds)
1743
+ });
1744
+ }
1745
+ if (!added.length && !updated.length && !removed.length && !order) return void 0;
1746
+ return Object.freeze({
1747
+ kind: "incremental",
1748
+ added: Object.freeze(added),
1749
+ updated: Object.freeze(updated),
1750
+ removed: Object.freeze(removed),
1751
+ ...order ? { order } : {}
1752
+ });
1753
+ };
1754
+ const record = {
1755
+ consumers: /* @__PURE__ */ new Set(),
1756
+ disposed: false,
1757
+ fault: void 0,
1758
+ revision: () => revision,
1759
+ reset: () => false,
1760
+ shouldSettle: () => change() !== void 0,
1761
+ clear: () => {
1762
+ touched.clear();
1763
+ beforeIds = void 0;
1764
+ cause = void 0;
1765
+ },
1766
+ context: (active) => {
1767
+ assertScope(active);
1768
+ const read = Object.freeze({
1769
+ get: (key) => {
1770
+ assertScope(active);
1771
+ return values.get(key);
1772
+ },
1773
+ has: (key) => {
1774
+ assertScope(active);
1775
+ return values.has(key);
1776
+ },
1777
+ ids: () => {
1778
+ assertScope(active);
1779
+ return Object.freeze([...values.keys()]);
1780
+ }
1781
+ });
1782
+ return Object.freeze({
1783
+ kind: "collection",
1784
+ read,
1785
+ change: change(),
1786
+ revision,
1787
+ cause
1788
+ });
1789
+ }
1790
+ };
1791
+ scheduler.register(handle, record);
1792
+ return {
1793
+ source: handle,
1794
+ update(run) {
1795
+ scheduler.assertIdle();
1796
+ const staged = /* @__PURE__ */ new Map();
1797
+ let active = true;
1798
+ const draft = Object.freeze({
1799
+ get: (key) => {
1800
+ assertScope(() => active);
1801
+ const entry = staged.get(key);
1802
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
1803
+ },
1804
+ has: (key) => {
1805
+ assertScope(() => active);
1806
+ const entry = staged.get(key);
1807
+ return entry ? entry.present : values.has(key);
1808
+ },
1809
+ set: (key, value) => {
1810
+ assertScope(() => active);
1811
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1812
+ staged.set(key, {
1813
+ present: true,
1814
+ value
1815
+ });
1816
+ },
1817
+ remove: (key) => {
1818
+ assertScope(() => active);
1819
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1820
+ staged.set(key, { present: false });
1821
+ }
1822
+ });
1823
+ try {
1824
+ assertSynchronous(run(draft));
1825
+ } finally {
1826
+ active = false;
1827
+ }
1828
+ let changed = false;
1829
+ for (const [key, entry] of staged) {
1830
+ const present = values.has(key);
1831
+ if (entry.present ? present && Object.is(values.get(key), entry.value) : !present) continue;
1832
+ if (!touched.has(key)) touched.set(key, {
1833
+ present,
1834
+ value: values.get(key)
1835
+ });
1836
+ if (!beforeIds && present !== entry.present) beforeIds = Object.freeze([...values.keys()]);
1837
+ if (entry.present) values.set(key, entry.value);
1838
+ else values.delete(key);
1839
+ changed = true;
1840
+ }
1841
+ if (!changed) return;
1842
+ revision++;
1843
+ cause = scheduler.batchContext()?.cause ?? cause;
1844
+ scheduler.capture(record);
1845
+ scheduler.run();
1846
+ }
1847
+ };
1848
+ };
2099
1849
  const externalValueSource = (port, equal) => {
2100
1850
  scheduler.assertIdle();
2101
1851
  const old = externalSources.get(port);
@@ -2112,10 +1862,8 @@ const createSources = (scheduler) => {
2112
1862
  try {
2113
1863
  input.accept(event.value, {
2114
1864
  revision: event.revision,
2115
- reset: event.reset,
2116
- detail: event.detail,
2117
- cause: event.cause,
2118
- batch: event.batch
1865
+ reset: event.reset ?? false,
1866
+ cause: event.cause
2119
1867
  });
2120
1868
  } catch (cause) {
2121
1869
  fail(cause);
@@ -2140,13 +1888,11 @@ const createSources = (scheduler) => {
2140
1888
  let revision = port.revision();
2141
1889
  let change;
2142
1890
  let reset = false;
2143
- let detail;
1891
+ let orderChanged = false;
2144
1892
  let cause;
2145
- let batch;
2146
- let publishedTransitions = Object.freeze([]);
2147
1893
  let transitionKeys = /* @__PURE__ */ new Set();
2148
1894
  let pending = false;
2149
- const handle = Object.freeze({});
1895
+ const handle = Object.freeze({ kind: "collection" });
2150
1896
  const record = {
2151
1897
  consumers: /* @__PURE__ */ new Set(),
2152
1898
  disposed: false,
@@ -2158,10 +1904,8 @@ const createSources = (scheduler) => {
2158
1904
  pending = false;
2159
1905
  change = void 0;
2160
1906
  reset = false;
2161
- detail = void 0;
1907
+ orderChanged = false;
2162
1908
  cause = void 0;
2163
- batch = void 0;
2164
- publishedTransitions = Object.freeze([]);
2165
1909
  transitionKeys.clear();
2166
1910
  },
2167
1911
  context: (active) => {
@@ -2180,101 +1924,65 @@ const createSources = (scheduler) => {
2180
1924
  return read.ids();
2181
1925
  }
2182
1926
  });
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
1927
  return Object.freeze({
2198
- ...currentRead,
2199
- previous: previousRead,
1928
+ kind: "collection",
1929
+ read: currentRead,
2200
1930
  change,
2201
1931
  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
1932
+ cause
2211
1933
  });
2212
1934
  }
2213
1935
  };
2214
1936
  scheduler.register(handle, record);
2215
- collections.add(handle);
2216
1937
  const fail = (error) => {
2217
1938
  record.fault = new ProjectionError("source", "external collection source", [revision], error);
2218
1939
  scheduler.capture(record);
2219
1940
  };
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 = [];
1941
+ const deriveChange = () => {
1942
+ if (reset) return Object.freeze({ kind: "reset" });
1943
+ const added = [];
1944
+ const updated = [];
1945
+ const removed = [];
2229
1946
  for (const key of transitionKeys) {
2230
1947
  const beforePresent = previous.has(key);
2231
1948
  const afterPresent = read.has(key);
2232
1949
  const before = beforePresent ? previous.get(key) : void 0;
2233
1950
  const after = afterPresent ? read.get(key) : void 0;
2234
1951
  if (beforePresent === afterPresent && Object.is(before, after)) continue;
2235
- if (!beforePresent) transitions.push({
1952
+ if (!beforePresent && afterPresent) added.push({
2236
1953
  key,
2237
- kind: "added",
2238
- before: void 0,
2239
1954
  after
2240
1955
  });
2241
- else if (!afterPresent) transitions.push({
1956
+ else if (beforePresent && !afterPresent) removed.push({
2242
1957
  key,
2243
- kind: "removed",
2244
- before,
2245
- after: void 0
1958
+ before
2246
1959
  });
2247
- else transitions.push({
1960
+ else if (beforePresent && afterPresent) updated.push({
2248
1961
  key,
2249
- kind: "updated",
2250
1962
  before,
2251
1963
  after
2252
1964
  });
2253
1965
  }
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;
1966
+ let order;
1967
+ if (orderChanged) {
1968
+ const beforeIds = previous.ids();
1969
+ const afterIds = read.ids();
1970
+ const beforeKeys = new Set(beforeIds);
1971
+ const afterKeys = new Set(afterIds);
1972
+ const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1973
+ const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1974
+ if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = Object.freeze({
1975
+ before: Object.freeze([...beforeIds]),
1976
+ after: Object.freeze([...afterIds])
1977
+ });
1978
+ }
1979
+ if (!added.length && !removed.length && !updated.length && !order) return void 0;
2272
1980
  return Object.freeze({
2273
1981
  kind: "incremental",
2274
- added,
2275
- removed,
2276
- updated,
2277
- orderChanged
1982
+ added: Object.freeze(added),
1983
+ updated: Object.freeze(updated),
1984
+ removed: Object.freeze(removed),
1985
+ ...order ? { order } : {}
2278
1986
  });
2279
1987
  };
2280
1988
  let unsubscribe;
@@ -2285,12 +1993,18 @@ const createSources = (scheduler) => {
2285
1993
  if (!pending) previous = event.previous;
2286
1994
  read = port.current();
2287
1995
  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();
1996
+ if (event.impact?.kind === "incremental") {
1997
+ event.impact.added.forEach((key) => transitionKeys.add(key));
1998
+ event.impact.removed.forEach((key) => transitionKeys.add(key));
1999
+ event.impact.updated.forEach((key) => transitionKeys.add(key));
2000
+ orderChanged ||= Boolean(event.impact.added.size || event.impact.removed.size || event.impact.orderChanged);
2001
+ } else if (!event.impact) {
2002
+ previous.ids().forEach((key) => transitionKeys.add(key));
2003
+ read.ids().forEach((key) => transitionKeys.add(key));
2004
+ orderChanged = true;
2005
+ }
2006
+ reset ||= event.impact?.kind === "reset";
2007
+ cause = event.cause ?? scheduler.batchContext()?.cause ?? cause;
2294
2008
  change = deriveChange();
2295
2009
  record.fault = void 0;
2296
2010
  scheduler.capture(record);
@@ -2324,6 +2038,7 @@ const createSources = (scheduler) => {
2324
2038
  set
2325
2039
  });
2326
2040
  },
2041
+ collectionInput,
2327
2042
  fromReadable: (readable, options) => {
2328
2043
  scheduler.assertIdle();
2329
2044
  if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
@@ -2396,107 +2111,484 @@ const createSources = (scheduler) => {
2396
2111
  } finally {
2397
2112
  scheduler.unregisterSource(input.source);
2398
2113
  }
2399
- throw error;
2400
- }
2401
- scheduler.cleanups.add(unsubscribe);
2402
- if (detach) scheduler.cleanups.add(detach);
2403
- readables.set(readable, bindings);
2404
- scheduler.run();
2405
- return input.source;
2406
- },
2407
- assertCollection: (handle) => {
2408
- scheduler.source(handle);
2409
- if (!collections.has(handle) && !collectionHandles.has(handle)) throw new Error("map requires a collection source.");
2410
- }
2411
- };
2412
- };
2413
- //#endregion
2414
- //#region core/src/projection/value.ts
2415
- const createValue = (scheduler, spec, options) => {
2416
- let instance;
2417
- let value;
2418
- let next;
2419
- let previous;
2420
- let initialized = false;
2421
- let revision = 0;
2422
- let changed = false;
2423
- let reset = false;
2424
- let batch;
2425
- let cause;
2426
- const listeners = /* @__PURE__ */ new Set();
2427
- 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;
2114
+ throw error;
2115
+ }
2116
+ scheduler.cleanups.add(unsubscribe);
2117
+ if (detach) scheduler.cleanups.add(detach);
2118
+ readables.set(readable, bindings);
2119
+ scheduler.run();
2120
+ return input.source;
2121
+ },
2122
+ assertCollection: (handle) => {
2123
+ scheduler.source(handle);
2124
+ if (!isCollectionSource(handle)) throw new Error("map requires a collection source.");
2125
+ }
2126
+ };
2127
+ };
2128
+ //#endregion
2129
+ //#region core/src/projection/node.ts
2130
+ const createNode = (scheduler, spec, behavior) => {
2131
+ scheduler.assertIdle();
2132
+ const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
2133
+ const order = scheduler.order();
2134
+ const node = {
2135
+ ...behavior,
2136
+ publish: () => {
2137
+ if (node.fault) behavior.clear();
2138
+ else behavior.publish();
2139
+ },
2140
+ order,
2141
+ name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
2142
+ sources: entries.map(([, source]) => source),
2143
+ consumers: /* @__PURE__ */ new Set(),
2144
+ disposed: false,
2145
+ fault: void 0,
2146
+ evaluate: (build) => {
2147
+ let active = true;
2148
+ try {
2149
+ const scopeActive = () => active;
2150
+ const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(scopeActive)]));
2151
+ return behavior.evaluate(inputs, build, scopeActive);
2152
+ } finally {
2153
+ active = false;
2154
+ }
2155
+ }
2156
+ };
2157
+ scheduler.initialize(() => {
2158
+ const invalid = node.sources.find((source) => source.fault || source.disposed);
2159
+ if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
2160
+ node.evaluate(true);
2161
+ node.publish();
2162
+ node.clear();
2163
+ });
2164
+ const check = () => {
2165
+ if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
2166
+ if (node.fault) throw node.fault;
2167
+ };
2168
+ return {
2169
+ node,
2170
+ check,
2171
+ install(handle) {
2172
+ scheduler.register(handle, node);
2173
+ scheduler.addNode(node);
2174
+ }
2175
+ };
2176
+ };
2177
+ //#endregion
2178
+ //#region core/src/projection/value.ts
2179
+ const createValue = (scheduler, spec, options) => {
2180
+ let instance;
2181
+ let value;
2182
+ let next;
2183
+ let initialized = false;
2184
+ let revision = 0;
2185
+ let changed = false;
2186
+ let reset = false;
2187
+ let cause;
2188
+ const listeners = /* @__PURE__ */ new Set();
2189
+ const owner = createNode(scheduler, spec, {
2190
+ evaluate: (sources, build) => {
2191
+ cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2192
+ let candidate;
2193
+ if (build || !instance) {
2194
+ profile.materialized.rebuilt();
2195
+ instance = void 0;
2196
+ const built = spec.build(sources);
2197
+ assertSynchronous(built);
2198
+ instance = built;
2199
+ candidate = built.value;
2200
+ } else {
2201
+ profile.materialized.updated();
2202
+ const result = instance.update(sources);
2203
+ assertSynchronous(result);
2204
+ if (result.kind === "rebuild") {
2205
+ instance = void 0;
2206
+ const built = spec.build(sources);
2207
+ assertSynchronous(built);
2208
+ instance = built;
2209
+ candidate = built.value;
2210
+ build = true;
2211
+ } else candidate = result.kind === "changed" ? result.value : value;
2212
+ }
2213
+ changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
2214
+ next = changed ? candidate : value;
2215
+ reset = build;
2216
+ return changed;
2217
+ },
2218
+ context: (active) => {
2219
+ assertScope(active);
2220
+ return Object.freeze({
2221
+ kind: "value",
2222
+ value: next,
2223
+ revision: revision + (changed ? 1 : 0),
2224
+ reset,
2225
+ cause
2226
+ });
2227
+ },
2228
+ revision: () => revision,
2229
+ reset: () => reset,
2230
+ publish: () => {
2231
+ if (changed && initialized) revision++;
2232
+ value = next;
2233
+ initialized = true;
2234
+ },
2235
+ emit: (call) => {
2236
+ profile.materialized.notification();
2237
+ Array.from(listeners).forEach((listener) => call(listener));
2238
+ },
2239
+ clear: () => {
2240
+ next = value;
2241
+ changed = false;
2242
+ reset = false;
2243
+ cause = void 0;
2244
+ },
2245
+ release: () => {
2246
+ listeners.clear();
2247
+ instance = void 0;
2248
+ value = next = void 0;
2249
+ }
2250
+ });
2251
+ const handle = Object.freeze({
2252
+ kind: "value",
2253
+ current: () => {
2254
+ owner.check();
2255
+ return value;
2256
+ },
2257
+ revision: () => {
2258
+ owner.check();
2259
+ return revision;
2260
+ },
2261
+ subscribe: (listener) => {
2262
+ owner.check();
2263
+ listeners.add(listener);
2264
+ return () => {
2265
+ listeners.delete(listener);
2266
+ };
2267
+ }
2268
+ });
2269
+ owner.install(handle);
2270
+ return handle;
2271
+ };
2272
+ //#endregion
2273
+ //#region core/src/projection/collection.ts
2274
+ const height = (node) => node?.height ?? 0;
2275
+ const makeTreeNode = (key, value, left, right) => Object.freeze({
2276
+ key,
2277
+ value,
2278
+ left,
2279
+ right,
2280
+ height: Math.max(height(left), height(right)) + 1
2281
+ });
2282
+ const rotateLeft = (node) => {
2283
+ const right = node.right;
2284
+ return makeTreeNode(right.key, right.value, makeTreeNode(node.key, node.value, node.left, right.left), right.right);
2285
+ };
2286
+ const rotateRight = (node) => {
2287
+ const left = node.left;
2288
+ return makeTreeNode(left.key, left.value, left.left, makeTreeNode(node.key, node.value, left.right, node.right));
2289
+ };
2290
+ const rebalance = (node) => {
2291
+ const balance = height(node.left) - height(node.right);
2292
+ if (balance > 1) {
2293
+ if (height(node.left.left) < height(node.left.right)) return rotateRight(makeTreeNode(node.key, node.value, rotateLeft(node.left), node.right));
2294
+ return rotateRight(node);
2295
+ }
2296
+ if (balance < -1) {
2297
+ if (height(node.right.right) < height(node.right.left)) return rotateLeft(makeTreeNode(node.key, node.value, node.left, rotateRight(node.right)));
2298
+ return rotateLeft(node);
2299
+ }
2300
+ return node;
2301
+ };
2302
+ const treeSet = (node, key, value) => {
2303
+ if (!node) return makeTreeNode(key, value);
2304
+ if (key === node.key) return makeTreeNode(key, value, node.left, node.right);
2305
+ if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeSet(node.left, key, value), node.right));
2306
+ return rebalance(makeTreeNode(node.key, node.value, node.left, treeSet(node.right, key, value)));
2307
+ };
2308
+ const treeMin = (node) => node.left ? treeMin(node.left) : node;
2309
+ const treeRemove = (node, key) => {
2310
+ if (!node) return void 0;
2311
+ if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeRemove(node.left, key), node.right));
2312
+ if (key > node.key) return rebalance(makeTreeNode(node.key, node.value, node.left, treeRemove(node.right, key)));
2313
+ if (!node.left) return node.right;
2314
+ if (!node.right) return node.left;
2315
+ const next = treeMin(node.right);
2316
+ return rebalance(makeTreeNode(next.key, next.value, node.left, treeRemove(node.right, next.key)));
2317
+ };
2318
+ const treeGet = (node, key) => {
2319
+ let current = node;
2320
+ while (current) {
2321
+ if (key === current.key) return current.value;
2322
+ current = key < current.key ? current.left : current.right;
2323
+ }
2324
+ };
2325
+ const treeHas = (node, key) => {
2326
+ let current = node;
2327
+ while (current) {
2328
+ if (key === current.key) return true;
2329
+ current = key < current.key ? current.left : current.right;
2330
+ }
2331
+ return false;
2332
+ };
2333
+ const treeFromSorted = (entries, start = 0, end = entries.length) => {
2334
+ if (start >= end) return void 0;
2335
+ const middle = start + end >> 1;
2336
+ const [key, value] = entries[middle];
2337
+ return makeTreeNode(key, value, treeFromSorted(entries, start, middle), treeFromSorted(entries, middle + 1, end));
2338
+ };
2339
+ const createCollection = (scheduler, spec) => {
2340
+ const values = /* @__PURE__ */ new Map();
2341
+ let ids = Object.freeze([]);
2342
+ let staged = /* @__PURE__ */ new Map();
2343
+ let nextIds = ids;
2344
+ let change;
2345
+ let instance;
2346
+ let initialized = false;
2347
+ let reset = false;
2348
+ let revision = 0;
2349
+ let publishedRoot;
2350
+ let cause;
2351
+ const listeners = /* @__PURE__ */ new Set();
2352
+ const changedKeys = /* @__PURE__ */ new Set();
2353
+ let orderChanged = false;
2354
+ const readPublished = (root, publishedIds) => Object.freeze({
2355
+ get: (key) => {
2356
+ owner.check();
2357
+ return treeGet(root, key);
2358
+ },
2359
+ has: (key) => {
2360
+ owner.check();
2361
+ return treeHas(root, key);
2362
+ },
2363
+ ids: () => {
2364
+ owner.check();
2365
+ return publishedIds;
2366
+ }
2367
+ });
2368
+ const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
2369
+ const getNext = (key) => {
2370
+ const entry = staged.get(key);
2371
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
2372
+ };
2373
+ const owner = createNode(scheduler, spec, {
2374
+ evaluate: (sources, build, active) => {
2375
+ cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2376
+ staged = /* @__PURE__ */ new Map();
2377
+ nextIds = ids;
2378
+ change = void 0;
2379
+ changedKeys.clear();
2380
+ let explicitOrder;
2381
+ let cleared = false;
2382
+ const nextHas = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
2383
+ const nextGet = (key) => {
2384
+ const entry = staged.get(key);
2385
+ return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
2386
+ };
2387
+ const deriveIds = () => {
2388
+ const result = cleared ? [] : ids.filter(nextHas);
2389
+ for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
2390
+ return result;
2391
+ };
2392
+ const read = (next) => ({
2393
+ get: (key) => {
2394
+ assertScope(active);
2395
+ return next ? nextGet(key) : values.get(key);
2396
+ },
2397
+ has: (key) => {
2398
+ assertScope(active);
2399
+ return next ? nextHas(key) : values.has(key);
2400
+ },
2401
+ ids: () => {
2402
+ assertScope(active);
2403
+ return next ? Object.freeze(explicitOrder ? explicitOrder.slice() : deriveIds()) : ids;
2404
+ }
2405
+ });
2406
+ const input = {
2407
+ sources,
2408
+ previous: read(false),
2409
+ next: read(true),
2410
+ output: {
2411
+ set: (key, value) => {
2412
+ assertScope(active);
2413
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
2414
+ staged.set(key, {
2415
+ present: true,
2416
+ value
2417
+ });
2418
+ },
2419
+ remove: (key) => {
2420
+ assertScope(active);
2421
+ staged.set(key, { present: false });
2422
+ },
2423
+ order: (order) => {
2424
+ assertScope(active);
2425
+ explicitOrder = order.slice();
2426
+ }
2427
+ }
2428
+ };
2433
2429
  if (build || !instance) {
2434
2430
  profile.materialized.rebuilt();
2435
2431
  instance = void 0;
2436
- const built = spec.build(sources);
2432
+ cleared = true;
2433
+ const built = spec.build(input);
2437
2434
  assertSynchronous(built);
2438
2435
  instance = built;
2439
- candidate = built.value;
2440
2436
  } else {
2441
2437
  profile.materialized.updated();
2442
- const result = instance.update(sources);
2438
+ const result = instance.update(input);
2443
2439
  assertSynchronous(result);
2444
- if (result.kind === "rebuild") {
2440
+ if (result?.kind === "rebuild") {
2441
+ staged.clear();
2442
+ cleared = true;
2443
+ explicitOrder = void 0;
2445
2444
  instance = void 0;
2446
- const built = spec.build(sources);
2445
+ const built = spec.build(input);
2447
2446
  assertSynchronous(built);
2448
2447
  instance = built;
2449
- candidate = built.value;
2450
2448
  build = true;
2451
- } else candidate = result.kind === "changed" ? result.value : value;
2449
+ }
2450
+ }
2451
+ if (cleared) {
2452
+ for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
2453
+ }
2454
+ const added = /* @__PURE__ */ new Set();
2455
+ const removed = /* @__PURE__ */ new Set();
2456
+ const updated = /* @__PURE__ */ new Set();
2457
+ profile.projection("touchedKeys", staged.size);
2458
+ for (const [key, entry] of staged) {
2459
+ const existed = values.has(key);
2460
+ if (entry.present) if (!existed) added.add(key);
2461
+ else if (!(spec.isEqual ?? Object.is)(values.get(key), entry.value)) updated.add(key);
2462
+ else staged.delete(key);
2463
+ else if (existed) removed.add(key);
2464
+ else staged.delete(key);
2465
+ }
2466
+ if (explicitOrder || added.size || removed.size) {
2467
+ const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
2468
+ 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.");
2469
+ nextIds = ids.length === order.length && ids.every((key, i) => key === order[i]) ? ids : Object.freeze(order.slice());
2470
+ }
2471
+ orderChanged = nextIds !== ids;
2472
+ [
2473
+ ...added,
2474
+ ...removed,
2475
+ ...updated
2476
+ ].forEach((key) => changedKeys.add(key));
2477
+ profile.projection("changedKeys", changedKeys.size);
2478
+ const addedEntries = [];
2479
+ const updatedEntries = [];
2480
+ const removedEntries = [];
2481
+ for (const key of changedKeys) {
2482
+ const beforePresent = values.has(key);
2483
+ const afterPresent = hasNext(key);
2484
+ if (beforePresent === afterPresent) {
2485
+ if (!beforePresent) continue;
2486
+ if ((spec.isEqual ?? Object.is)(values.get(key), getNext(key))) continue;
2487
+ }
2488
+ if (!beforePresent) addedEntries.push({
2489
+ key,
2490
+ after: getNext(key)
2491
+ });
2492
+ else if (!afterPresent) removedEntries.push({
2493
+ key,
2494
+ before: values.get(key)
2495
+ });
2496
+ else updatedEntries.push({
2497
+ key,
2498
+ before: values.get(key),
2499
+ after: getNext(key)
2500
+ });
2501
+ }
2502
+ if (build) change = Object.freeze({ kind: "reset" });
2503
+ else if (addedEntries.length || updatedEntries.length || removedEntries.length || orderChanged) {
2504
+ let commonOrderChanged = false;
2505
+ if (orderChanged) {
2506
+ const before = ids.filter((key) => !removed.has(key));
2507
+ const after = nextIds.filter((key) => !added.has(key));
2508
+ commonOrderChanged = before.some((key, i) => after[i] !== key);
2509
+ }
2510
+ change = Object.freeze({
2511
+ kind: "incremental",
2512
+ added: Object.freeze(addedEntries),
2513
+ updated: Object.freeze(updatedEntries),
2514
+ removed: Object.freeze(removedEntries),
2515
+ ...commonOrderChanged ? { order: Object.freeze({
2516
+ before: Object.freeze([...ids]),
2517
+ after: Object.freeze([...nextIds])
2518
+ }) } : {}
2519
+ });
2452
2520
  }
2453
- previous = value;
2454
- changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
2455
- next = changed ? candidate : value;
2456
2521
  reset = build;
2457
- return changed;
2522
+ return change !== void 0;
2458
2523
  },
2459
2524
  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
- });
2525
+ const input = {
2526
+ kind: "collection",
2527
+ read: {
2528
+ get: (key) => {
2529
+ assertScope(active);
2530
+ return getNext(key);
2531
+ },
2532
+ has: (key) => {
2533
+ assertScope(active);
2534
+ return hasNext(key);
2535
+ },
2536
+ ids: () => {
2537
+ assertScope(active);
2538
+ return nextIds;
2539
+ }
2540
+ },
2541
+ change,
2542
+ revision: revision + (change ? 1 : 0),
2543
+ cause
2544
+ };
2545
+ return Object.freeze(input);
2470
2546
  },
2471
2547
  revision: () => revision,
2472
2548
  reset: () => reset,
2473
2549
  publish: () => {
2474
- if (changed && initialized) revision++;
2475
- value = next;
2550
+ if (change) if (change.kind === "reset") {
2551
+ const entries = [];
2552
+ for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
2553
+ entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
2554
+ publishedRoot = treeFromSorted(entries);
2555
+ } else for (const [key, entry] of staged) publishedRoot = entry.present ? treeSet(publishedRoot, key, entry.value) : treeRemove(publishedRoot, key);
2556
+ for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
2557
+ else values.delete(key);
2558
+ if (change) {
2559
+ if (initialized) revision++;
2560
+ }
2561
+ ids = nextIds;
2476
2562
  initialized = true;
2477
2563
  },
2478
2564
  emit: (call) => {
2479
2565
  profile.materialized.notification();
2480
- Array.from(listeners).forEach((listener) => call(listener));
2566
+ const resetEvent = Object.freeze({ kind: "reset" });
2567
+ Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
2481
2568
  },
2482
2569
  clear: () => {
2483
- next = value;
2484
- previous = value;
2485
- changed = false;
2570
+ staged.clear();
2571
+ nextIds = ids;
2572
+ change = void 0;
2486
2573
  reset = false;
2487
- batch = void 0;
2574
+ changedKeys.clear();
2488
2575
  cause = void 0;
2576
+ orderChanged = false;
2489
2577
  },
2490
2578
  release: () => {
2579
+ values.clear();
2580
+ staged.clear();
2581
+ publishedRoot = void 0;
2491
2582
  listeners.clear();
2583
+ ids = nextIds = Object.freeze([]);
2492
2584
  instance = void 0;
2493
- value = next = previous = void 0;
2494
2585
  }
2495
2586
  });
2496
- const handle = Object.freeze({
2587
+ const handle = {
2588
+ kind: "collection",
2497
2589
  current: () => {
2498
2590
  owner.check();
2499
- return value;
2591
+ return readPublished(publishedRoot, ids);
2500
2592
  },
2501
2593
  revision: () => {
2502
2594
  owner.check();
@@ -2508,36 +2600,17 @@ const createValue = (scheduler, spec, options) => {
2508
2600
  return () => {
2509
2601
  listeners.delete(listener);
2510
2602
  };
2511
- },
2512
- rebuild: owner.rebuild,
2513
- dispose: owner.dispose
2514
- });
2603
+ }
2604
+ };
2515
2605
  owner.install(handle);
2516
- return handle;
2606
+ return Object.freeze(handle);
2517
2607
  };
2518
2608
  //#endregion
2519
- //#region core/src/projection/runtime.ts
2520
- const createRuntimeExecutor = (options) => {
2609
+ //#region core/src/projection/graph.ts
2610
+ const createProjectionGraph = (options) => {
2521
2611
  const scheduler = createScheduler(options.onError);
2522
2612
  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
- }
2613
+ const value = (spec, options) => createValue(scheduler, spec, options);
2541
2614
  const map = (source, mapper, options) => {
2542
2615
  sources.assertCollection(source);
2543
2616
  const calculate = (id, entry) => {
@@ -2548,60 +2621,53 @@ const createRuntimeExecutor = (options) => {
2548
2621
  return createCollection(scheduler, {
2549
2622
  sources: { source },
2550
2623
  isEqual: options?.isEqual,
2551
- build: ({ sources, writer }) => {
2552
- const input = sources.source;
2553
- const read = "read" in input ? input.read : input;
2624
+ build: ({ sources, output }) => {
2625
+ const read = sources.source.read;
2554
2626
  const ids = read.ids();
2555
2627
  profile.collectionView.idsScanned(ids.length);
2556
2628
  for (const id of ids) {
2557
2629
  profile.collectionView.mapped();
2558
- writer.set(id, calculate(id, read.get(id)));
2630
+ output.set(id, calculate(id, read.get(id)));
2559
2631
  }
2560
- writer.order(ids);
2561
- return { update: ({ sources, writer }) => {
2632
+ output.order(ids);
2633
+ return { update: ({ sources, output }) => {
2562
2634
  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);
2635
+ const read = input.read;
2636
+ if (input.change?.kind === "reset") return { kind: "rebuild" };
2637
+ const change = input.change;
2638
+ if (!change) return;
2639
+ for (const entry of change.added) {
2640
+ profile.collectionView.mapped();
2641
+ output.set(entry.key, calculate(entry.key, entry.after));
2580
2642
  }
2581
- for (const key of keys) if (!read.has(key)) writer.remove(key);
2582
- else {
2643
+ for (const entry of change.updated) {
2583
2644
  profile.collectionView.mapped();
2584
- writer.set(key, calculate(key, read.get(key)));
2645
+ output.set(entry.key, calculate(entry.key, entry.after));
2585
2646
  }
2586
- if (structural) {
2647
+ for (const entry of change.removed) output.remove(entry.key);
2648
+ if (change.added.length || change.removed.length || change.order) {
2587
2649
  const ids = read.ids();
2588
2650
  profile.collectionView.idsScanned(ids.length);
2589
- writer.order(ids);
2651
+ output.order(ids);
2590
2652
  }
2591
2653
  } };
2592
2654
  }
2593
2655
  });
2594
2656
  };
2595
- const runtime = {
2657
+ const graph = {
2596
2658
  document: sources.document,
2597
2659
  fromSource: (source, options) => sources.fromSource(source, options),
2598
2660
  fromCollectionSource: (source) => sources.fromCollectionSource(source),
2599
2661
  input: sources.input,
2662
+ collectionInput: sources.collectionInput,
2600
2663
  fromReadable: sources.fromReadable,
2601
2664
  value,
2602
- collection: () => (spec) => createCollection(scheduler, spec),
2665
+ collection: (spec) => createCollection(scheduler, spec),
2603
2666
  map,
2604
2667
  batch: scheduler.batch,
2668
+ assertIdle: scheduler.assertIdle,
2669
+ releaseNode: scheduler.releaseNode,
2670
+ releaseInput: scheduler.unregisterSource,
2605
2671
  dispose: () => {
2606
2672
  try {
2607
2673
  scheduler.dispose();
@@ -2610,10 +2676,10 @@ const createRuntimeExecutor = (options) => {
2610
2676
  }
2611
2677
  }
2612
2678
  };
2613
- return Object.freeze(runtime);
2679
+ return Object.freeze(graph);
2614
2680
  };
2615
2681
  //#endregion
2616
- //#region core/src/projection/store.ts
2682
+ //#region core/src/projection/runtime.ts
2617
2683
  let activeTracker;
2618
2684
  const trackKey = (key) => {
2619
2685
  activeTracker?.keys.add(key);
@@ -2666,6 +2732,50 @@ const mapView = (read) => {
2666
2732
  };
2667
2733
  return Object.freeze(view);
2668
2734
  };
2735
+ const lazyMapView = (read) => {
2736
+ const ids = read.ids();
2737
+ const entries = function* () {
2738
+ for (const key of ids) yield [key, read.get(key)];
2739
+ };
2740
+ const values = function* () {
2741
+ for (const key of ids) yield read.get(key);
2742
+ };
2743
+ const view = {
2744
+ get: (key) => {
2745
+ trackKey(key);
2746
+ return read.get(key);
2747
+ },
2748
+ has: (key) => {
2749
+ trackKey(key);
2750
+ return read.has(key);
2751
+ },
2752
+ get size() {
2753
+ trackStructure();
2754
+ return ids.length;
2755
+ },
2756
+ keys: () => {
2757
+ trackStructure();
2758
+ return ids[Symbol.iterator]();
2759
+ },
2760
+ values: () => {
2761
+ trackAll();
2762
+ return values();
2763
+ },
2764
+ entries: () => {
2765
+ trackAll();
2766
+ return entries();
2767
+ },
2768
+ forEach: (callback, thisArg) => {
2769
+ trackAll();
2770
+ for (const key of ids) callback.call(thisArg, read.get(key), key, view);
2771
+ },
2772
+ [Symbol.iterator]: () => {
2773
+ trackAll();
2774
+ return entries();
2775
+ }
2776
+ };
2777
+ return Object.freeze(view);
2778
+ };
2669
2779
  const isMapLike = (value) => value instanceof Map || value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.keys === "function" && typeof value.entries === "function";
2670
2780
  const mapRead = (value) => ({
2671
2781
  get: (key) => value.get(key),
@@ -2673,22 +2783,35 @@ const mapRead = (value) => ({
2673
2783
  ids: () => Object.freeze([...value.keys()])
2674
2784
  });
2675
2785
  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);
2786
+ const added = [];
2787
+ const removed = [];
2788
+ const updated = [];
2789
+ for (const key of previous.keys()) if (!current.has(key)) removed.push({
2790
+ key,
2791
+ before: previous.get(key)
2792
+ });
2793
+ else if (!Object.is(previous.get(key), current.get(key))) updated.push({
2794
+ key,
2795
+ before: previous.get(key),
2796
+ after: current.get(key)
2797
+ });
2798
+ for (const key of current.keys()) if (!previous.has(key)) added.push({
2799
+ key,
2800
+ after: current.get(key)
2801
+ });
2682
2802
  const before = [...previous.keys()].filter((key) => current.has(key));
2683
2803
  const after = [...current.keys()].filter((key) => previous.has(key));
2684
2804
  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;
2805
+ if (!added.length && !removed.length && !updated.length && !orderChanged) return void 0;
2686
2806
  return {
2687
2807
  kind: "incremental",
2688
- added,
2689
- removed,
2690
- updated,
2691
- orderChanged
2808
+ added: Object.freeze(added),
2809
+ removed: Object.freeze(removed),
2810
+ updated: Object.freeze(updated),
2811
+ ...orderChanged ? { order: Object.freeze({
2812
+ before: Object.freeze([...previous.keys()]),
2813
+ after: Object.freeze([...current.keys()])
2814
+ }) } : {}
2692
2815
  };
2693
2816
  };
2694
2817
  const sameSelection = (left, right) => {
@@ -2698,45 +2821,92 @@ const sameSelection = (left, right) => {
2698
2821
  };
2699
2822
  const selectionAffects = (selection, change) => {
2700
2823
  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;
2824
+ if (selection.all && (change.added.length || change.removed.length || change.updated.length || change.order)) return true;
2825
+ if (selection.structure && (change.added.length || change.removed.length || change.order)) return true;
2826
+ 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
2827
  return false;
2705
2828
  };
2706
2829
  const currentValue = (event) => {
2707
2830
  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);
2831
+ if ("kind" in event && event.kind === "value") return event.value;
2832
+ if ("kind" in event && event.kind === "document") return snapshot(event.read);
2833
+ if ("kind" in event && event.kind === "collection") return mapView(event.read);
2711
2834
  return event;
2712
2835
  };
2713
2836
  const sourceOf = (instance) => instance;
2714
2837
  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();
2719
- const materialize = (projection) => {
2720
- const old = instances.get(projection);
2721
- if (old) return old;
2838
+ const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
2839
+ const materialized = /* @__PURE__ */ new WeakMap();
2840
+ let disposed = false;
2841
+ const scopes = /* @__PURE__ */ new Set();
2842
+ const materialize = (projection, requester) => {
2843
+ const owner = ownerOf(projection);
2844
+ if (owner) {
2845
+ if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
2846
+ if (!requester.active) throw new ProjectionDisposedError();
2847
+ }
2848
+ const old = materialized.get(projection);
2849
+ if (old) return old.node;
2722
2850
  const definition = definitionOf(projection);
2723
2851
  let instance;
2852
+ let write;
2724
2853
  switch (definition.kind) {
2725
2854
  case "input": {
2726
2855
  const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
2727
- inputs.set(projection, input);
2728
- instance = runtime.value({ input: input.source }, ({ input }) => input.value);
2856
+ write = {
2857
+ kind: "value-input",
2858
+ source: input.source,
2859
+ set: input.set
2860
+ };
2861
+ instance = runtime.value({
2862
+ sources: { input: input.source },
2863
+ build: (values) => ({
2864
+ value: values.input.value,
2865
+ update: (next) => ({
2866
+ kind: "changed",
2867
+ value: next.input.value
2868
+ })
2869
+ })
2870
+ });
2871
+ break;
2872
+ }
2873
+ case "collection-input": {
2874
+ const source = runtime.collectionInput(definition.initial);
2875
+ write = {
2876
+ kind: "collection-input",
2877
+ source: source.source,
2878
+ update: source.update
2879
+ };
2880
+ instance = runtime.map(source.source, (_key, value) => value);
2729
2881
  break;
2730
2882
  }
2731
2883
  case "readable": {
2732
2884
  const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
2733
- instance = runtime.value({ source }, ({ source }) => source.value);
2885
+ instance = runtime.value({
2886
+ sources: { source },
2887
+ build: (values) => ({
2888
+ value: values.source.value,
2889
+ update: (next) => ({
2890
+ kind: "changed",
2891
+ value: next.source.value
2892
+ })
2893
+ })
2894
+ });
2734
2895
  break;
2735
2896
  }
2736
2897
  case "source-value":
2737
2898
  {
2738
2899
  const source = runtime.fromSource(definition.source);
2739
- instance = runtime.value({ source }, ({ source }) => source.value);
2900
+ instance = runtime.value({
2901
+ sources: { source },
2902
+ build: (values) => ({
2903
+ value: values.source.value,
2904
+ update: (next) => ({
2905
+ kind: "changed",
2906
+ value: next.source.value
2907
+ })
2908
+ })
2909
+ });
2740
2910
  }
2741
2911
  break;
2742
2912
  case "source-collection": {
@@ -2747,30 +2917,55 @@ const createProjectionRuntime = (options) => {
2747
2917
  case "document": {
2748
2918
  const document = runtime.document(definition.document);
2749
2919
  if (!definition.selector) {
2750
- instance = runtime.value({ document }, ({ document }) => snapshot(document.read));
2920
+ instance = runtime.value({
2921
+ sources: { document },
2922
+ build: (values) => ({
2923
+ value: snapshot(values.document.read),
2924
+ update: (next) => ({
2925
+ kind: "changed",
2926
+ value: snapshot(next.document.read)
2927
+ })
2928
+ })
2929
+ });
2751
2930
  break;
2752
2931
  }
2753
2932
  const state = accessOf(definition.document);
2754
2933
  try {
2755
2934
  const collection = document.collection(definition.selector);
2756
- instance = runtime.map(collection, (_id, entry) => snapshot(entry));
2935
+ instance = runtime.map(collection, (_id, entry) => entry);
2757
2936
  break;
2758
2937
  } catch {
2759
2938
  const selected = document.targets(definition.selector);
2760
2939
  const address = compilePath(state.schema, "value", definition.selector).address;
2761
- instance = runtime.value({ selected }, () => {
2762
- return read$1(snapshot(state.document), address, state.schema);
2940
+ instance = runtime.value({
2941
+ sources: { selected },
2942
+ build: () => ({
2943
+ value: read$1(snapshot(state.document), address, state.schema),
2944
+ update: () => ({
2945
+ kind: "changed",
2946
+ value: read$1(snapshot(state.document), address, state.schema)
2947
+ })
2948
+ })
2763
2949
  });
2764
2950
  }
2765
2951
  break;
2766
2952
  }
2767
2953
  case "derive": {
2768
- 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 });
2954
+ const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency, owner))]));
2955
+ instance = runtime.value({
2956
+ sources,
2957
+ build: (values) => ({
2958
+ value: definition.compute(...Object.values(values).map(currentValue)),
2959
+ update: (next) => ({
2960
+ kind: "changed",
2961
+ value: definition.compute(...Object.values(next).map(currentValue))
2962
+ })
2963
+ })
2964
+ }, { isEqual: definition.isEqual });
2770
2965
  break;
2771
2966
  }
2772
2967
  case "incremental-value": {
2773
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2968
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2774
2969
  instance = runtime.value({
2775
2970
  sources,
2776
2971
  build: definition.build
@@ -2778,8 +2973,8 @@ const createProjectionRuntime = (options) => {
2778
2973
  break;
2779
2974
  }
2780
2975
  case "incremental-collection": {
2781
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2782
- instance = runtime.collection()({
2976
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2977
+ instance = runtime.collection({
2783
2978
  sources,
2784
2979
  build: definition.build,
2785
2980
  isEqual: definition.isEqual,
@@ -2788,41 +2983,51 @@ const createProjectionRuntime = (options) => {
2788
2983
  break;
2789
2984
  }
2790
2985
  }
2791
- instances.set(projection, instance);
2986
+ const record = write ? {
2987
+ node: instance,
2988
+ ...write
2989
+ } : {
2990
+ kind: "projection",
2991
+ node: instance
2992
+ };
2993
+ materialized.set(projection, record);
2994
+ if (owner) owner.materialized.push(projection);
2792
2995
  return instance;
2793
2996
  };
2794
- const get = (projection) => {
2795
- const instance = materialize(projection);
2997
+ const get = (projection, requester) => {
2998
+ const instance = materialize(projection, requester);
2796
2999
  const current = instance.current();
2797
- if (current && typeof current === "object" && "ids" in current && typeof current.ids === "function") {
2798
- const existing = collections.get(instance);
3000
+ if (instance.kind === "collection") {
3001
+ const record = materialized.get(projection) ?? materialized.get(instance);
3002
+ const existing = record?.collection;
2799
3003
  const revision = instance.revision();
2800
3004
  if (existing?.revision === revision) return existing.value;
2801
- const value = mapView(current);
2802
- collections.set(instance, {
3005
+ const value = lazyMapView(current);
3006
+ if (record) record.collection = {
2803
3007
  revision,
2804
3008
  value
2805
- });
3009
+ };
2806
3010
  return value;
2807
3011
  }
2808
3012
  if (isMapLike(current)) {
2809
- const existing = collections.get(instance);
3013
+ const record = materialized.get(projection) ?? materialized.get(instance);
3014
+ const existing = record?.collection;
2810
3015
  const revision = instance.revision();
2811
3016
  if (existing?.revision === revision) return existing.value;
2812
3017
  const value = mapView(mapRead(current));
2813
- collections.set(instance, {
3018
+ if (record) record.collection = {
2814
3019
  revision,
2815
3020
  value
2816
- });
3021
+ };
2817
3022
  return value;
2818
3023
  }
2819
3024
  return current;
2820
3025
  };
2821
- function readable(projection, selector, equality = Object.is) {
3026
+ const makeReadable = (projection, selector, equality = Object.is, requester) => {
2822
3027
  if (!selector) return Object.freeze({
2823
- current: () => get(projection),
2824
- revision: () => materialize(projection).revision(),
2825
- subscribe: (listener) => materialize(projection).subscribe(listener)
3028
+ current: () => get(projection, requester),
3029
+ revision: () => materialize(projection, requester).revision(),
3030
+ subscribe: (listener) => materialize(projection, requester).subscribe(listener)
2826
3031
  });
2827
3032
  let initialized = false;
2828
3033
  let value;
@@ -2837,7 +3042,7 @@ const createProjectionRuntime = (options) => {
2837
3042
  let unsubscribeSource;
2838
3043
  const listeners = /* @__PURE__ */ new Set();
2839
3044
  const updateCollectionSnapshot = () => {
2840
- const current = get(projection);
3045
+ const current = get(projection, requester);
2841
3046
  previousCollection = isMapLike(current) ? current : void 0;
2842
3047
  };
2843
3048
  const evaluate = () => {
@@ -2851,7 +3056,7 @@ const createProjectionRuntime = (options) => {
2851
3056
  let source;
2852
3057
  let next;
2853
3058
  try {
2854
- source = get(projection);
3059
+ source = get(projection, requester);
2855
3060
  next = selector(source);
2856
3061
  } finally {
2857
3062
  activeTracker = previousTracker;
@@ -2868,30 +3073,30 @@ const createProjectionRuntime = (options) => {
2868
3073
  }
2869
3074
  selection = nextSelection;
2870
3075
  initialized = true;
2871
- sourceRevision = materialize(projection).revision();
3076
+ sourceRevision = materialize(projection, requester).revision();
2872
3077
  updateCollectionSnapshot();
2873
3078
  return changed;
2874
3079
  };
2875
3080
  const ensureCurrent = () => {
2876
- const revision = materialize(projection).revision();
3081
+ const revision = materialize(projection, requester).revision();
2877
3082
  if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
2878
3083
  return value;
2879
3084
  };
2880
3085
  const onProjectionChange = (incoming) => {
2881
3086
  let change = incoming;
2882
3087
  if (!change && previousCollection) {
2883
- const current = get(projection);
3088
+ const current = get(projection, requester);
2884
3089
  if (isMapLike(current)) {
2885
3090
  change = mapChange(previousCollection, current);
2886
3091
  previousCollection = current;
2887
3092
  if (!change) {
2888
- sourceRevision = materialize(projection).revision();
3093
+ sourceRevision = materialize(projection, requester).revision();
2889
3094
  return;
2890
3095
  }
2891
3096
  } else previousCollection = void 0;
2892
3097
  }
2893
3098
  if (!selectionAffects(selection, change)) {
2894
- sourceRevision = materialize(projection).revision();
3099
+ sourceRevision = materialize(projection, requester).revision();
2895
3100
  updateCollectionSnapshot();
2896
3101
  return;
2897
3102
  }
@@ -2899,12 +3104,12 @@ const createProjectionRuntime = (options) => {
2899
3104
  const changed = evaluate();
2900
3105
  if (unsubscribeSource && !sameSelection(previousSelection, selection)) {
2901
3106
  unsubscribeSource();
2902
- unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
3107
+ unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
2903
3108
  }
2904
3109
  if (changed) Array.from(listeners).forEach((listener) => listener());
2905
3110
  };
2906
3111
  const installSource = () => {
2907
- if (!unsubscribeSource) unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
3112
+ if (!unsubscribeSource) unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
2908
3113
  };
2909
3114
  return Object.freeze({
2910
3115
  current: ensureCurrent,
@@ -2925,18 +3130,134 @@ const createProjectionRuntime = (options) => {
2925
3130
  };
2926
3131
  }
2927
3132
  });
3133
+ };
3134
+ function readable(projection, selector, equality) {
3135
+ return makeReadable(projection, selector, equality);
2928
3136
  }
3137
+ const setInput = (target, value, requester) => {
3138
+ materialize(target, requester);
3139
+ const record = materialized.get(target);
3140
+ if (record?.kind !== "value-input") throw new TypeError("Projection is not a value input.");
3141
+ record.set(value);
3142
+ };
3143
+ const updateInput = (target, run, requester) => {
3144
+ materialize(target, requester);
3145
+ const record = materialized.get(target);
3146
+ if (record?.kind !== "collection-input") throw new TypeError("Projection is not a collection input.");
3147
+ record.update(run);
3148
+ };
3149
+ const createScope = () => {
3150
+ runtime.assertIdle();
3151
+ const state = {
3152
+ active: true,
3153
+ materialized: [],
3154
+ subscriptions: /* @__PURE__ */ new Set()
3155
+ };
3156
+ scopes.add(state);
3157
+ const assertActive = () => {
3158
+ if (!state.active) throw new ProjectionDisposedError();
3159
+ };
3160
+ const own = (projection) => {
3161
+ assertActive();
3162
+ const definition = definitionOf(projection);
3163
+ const dependencies = definition.kind === "derive" ? definition.dependencies : definition.kind === "incremental-value" || definition.kind === "incremental-collection" ? Object.values(definition.dependencies) : [];
3164
+ for (const dependency of dependencies) {
3165
+ const dependencyOwner = ownerOf(dependency);
3166
+ if (dependencyOwner && dependencyOwner !== state) throw new TypeError("A scope cannot depend on another scope.");
3167
+ }
3168
+ ownDefinition(projection, state);
3169
+ return projection;
3170
+ };
3171
+ const scopedInput = Object.assign((initial, equality) => own(input(initial, equality)), { collection: (initial) => own(input.collection(initial)) });
3172
+ const scopedDerive = (dependencies, compute, equality) => own(derive(dependencies, compute, equality));
3173
+ const scopedIncremental = Object.assign((dependencies, processor) => own(incremental(dependencies, processor)), { collection: (dependencies, processor) => own(incremental.collection(dependencies, processor)) });
3174
+ function scopeReadable(projection, selector, equality) {
3175
+ assertActive();
3176
+ materialize(projection, state);
3177
+ const source = makeReadable(projection, selector, equality, state);
3178
+ return Object.freeze({
3179
+ current: () => {
3180
+ assertActive();
3181
+ return source.current();
3182
+ },
3183
+ revision: () => {
3184
+ assertActive();
3185
+ return source.revision();
3186
+ },
3187
+ subscribe: (listener) => {
3188
+ assertActive();
3189
+ const stop = source.subscribe(listener);
3190
+ let subscribed = true;
3191
+ const unsubscribe = () => {
3192
+ if (!subscribed) return;
3193
+ subscribed = false;
3194
+ state.subscriptions.delete(unsubscribe);
3195
+ stop();
3196
+ };
3197
+ state.subscriptions.add(unsubscribe);
3198
+ return unsubscribe;
3199
+ }
3200
+ });
3201
+ }
3202
+ function scopeBatch(optionsOrCallback, maybeCallback) {
3203
+ assertActive();
3204
+ return typeof optionsOrCallback === "function" ? runtime.batch(optionsOrCallback) : runtime.batch(optionsOrCallback, maybeCallback);
3205
+ }
3206
+ return Object.freeze({
3207
+ input: scopedInput,
3208
+ derive: scopedDerive,
3209
+ incremental: scopedIncremental,
3210
+ get: (projection) => {
3211
+ assertActive();
3212
+ return get(projection, state);
3213
+ },
3214
+ readable: scopeReadable,
3215
+ set: (target, value) => {
3216
+ assertActive();
3217
+ setInput(target, value, state);
3218
+ },
3219
+ update: (target, run) => {
3220
+ assertActive();
3221
+ updateInput(target, run, state);
3222
+ },
3223
+ batch: scopeBatch,
3224
+ dispose: () => {
3225
+ if (!state.active) return;
3226
+ runtime.assertIdle();
3227
+ state.active = false;
3228
+ state.subscriptions.forEach((unsubscribe) => unsubscribe());
3229
+ for (const definition of state.materialized.reverse()) {
3230
+ const record = materialized.get(definition);
3231
+ if (!record) continue;
3232
+ runtime.releaseNode(record.node);
3233
+ if (record.kind !== "projection") runtime.releaseInput(record.source);
3234
+ materialized.delete(definition);
3235
+ }
3236
+ state.materialized.length = 0;
3237
+ scopes.delete(state);
3238
+ }
3239
+ });
3240
+ };
2929
3241
  const store = {
2930
3242
  get,
2931
3243
  readable,
2932
- set: ((input, value) => {
2933
- materialize(input);
2934
- const target = inputs.get(input);
2935
- if (!target) throw new TypeError("Projection is not an input.");
2936
- target.set(value);
2937
- }),
3244
+ set: setInput,
3245
+ update: updateInput,
2938
3246
  batch: runtime.batch,
2939
- dispose: runtime.dispose
3247
+ scope: createScope,
3248
+ dispose: () => {
3249
+ if (disposed) return;
3250
+ runtime.assertIdle();
3251
+ disposed = true;
3252
+ for (const scope of scopes) {
3253
+ scope.active = false;
3254
+ scope.subscriptions.forEach((unsubscribe) => unsubscribe());
3255
+ scope.subscriptions.clear();
3256
+ scope.materialized.length = 0;
3257
+ }
3258
+ scopes.clear();
3259
+ runtime.dispose();
3260
+ }
2940
3261
  };
2941
3262
  return Object.freeze(store);
2942
3263
  };