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