doxum 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3,7 +3,7 @@ const require_issue = require("./issue-DhrNdQNg.cjs");
3
3
  const require_scope = require("./scope-Ezs-Y3TY.cjs");
4
4
  const require_notification = require("./notification-CDSUfiip.cjs");
5
5
  const require_driver = require("./driver-BCyfqxsl.cjs");
6
- const require_advanced = require("./advanced-Dgu8-bPS.cjs");
6
+ const require_advanced = require("./advanced-CMMNk49G.cjs");
7
7
  //#region core/src/schema.ts
8
8
  const node = (value) => Object.freeze(value);
9
9
  const field = (validator) => node({
@@ -1104,333 +1104,6 @@ const asReadable = (runtime) => {
1104
1104
  //#region core/src/projection/select.ts
1105
1105
  const read = (runtime, selector) => require_notification.readWith(runtime, selector);
1106
1106
  //#endregion
1107
- //#region core/src/projection/contract.ts
1108
- var ProjectionError = class extends Error {
1109
- constructor(phase, identity, revisions, cause) {
1110
- super(`Projection ${phase} failed: ${identity}`, { cause });
1111
- this.phase = phase;
1112
- this.identity = identity;
1113
- this.revisions = revisions;
1114
- this.name = "ProjectionError";
1115
- }
1116
- };
1117
- var ProjectionDisposedError = class extends Error {
1118
- constructor() {
1119
- super("Projection has been disposed.");
1120
- this.name = "ProjectionDisposedError";
1121
- }
1122
- };
1123
- //#endregion
1124
- //#region core/src/projection/scheduler.ts
1125
- const projectionHandles = /* @__PURE__ */ new WeakSet();
1126
- const createScheduler = (onError) => {
1127
- let disposed = false;
1128
- let depth = 0;
1129
- let batchSequence = 0;
1130
- let activeBatch;
1131
- let phase = "idle";
1132
- let sequence = 0;
1133
- const records = /* @__PURE__ */ new Map();
1134
- const nodes = /* @__PURE__ */ new Set();
1135
- const pending = /* @__PURE__ */ new Set();
1136
- const heap = [];
1137
- const queued = /* @__PURE__ */ new Set();
1138
- const completed = [];
1139
- const emissions = /* @__PURE__ */ new Set();
1140
- const errors = [];
1141
- let reportingFailures = [];
1142
- const guards = /* @__PURE__ */ new Set();
1143
- const cleanups = /* @__PURE__ */ new Set();
1144
- const assertActive = () => {
1145
- if (disposed) throw new ProjectionDisposedError();
1146
- };
1147
- const assertIdle = () => {
1148
- assertActive();
1149
- if (phase !== "idle") throw new Error("Projection cannot be re-entered during processing or notification.");
1150
- };
1151
- const lock = (value) => guards.forEach((guard) => guard(value));
1152
- const enqueue = (node) => {
1153
- if (node.disposed || queued.has(node)) return;
1154
- queued.add(node);
1155
- require_issue.profile.projection("scheduledNodes");
1156
- let i = heap.length;
1157
- heap.push(node);
1158
- while (i > 0) {
1159
- const p = i - 1 >> 1;
1160
- if (heap[p].order < node.order) break;
1161
- heap[i] = heap[p];
1162
- i = p;
1163
- }
1164
- heap[i] = node;
1165
- };
1166
- const pop = () => {
1167
- const first = heap[0];
1168
- const last = heap.pop();
1169
- if (heap.length) {
1170
- let i = 0;
1171
- while (i * 2 + 1 < heap.length) {
1172
- let c = i * 2 + 1;
1173
- if (c + 1 < heap.length && heap[c + 1].order < heap[c].order) c++;
1174
- if (last.order < heap[c].order) break;
1175
- heap[i] = heap[c];
1176
- i = c;
1177
- }
1178
- heap[i] = last;
1179
- }
1180
- return first;
1181
- };
1182
- const errorFor = (node, cause, kind = "processor") => new ProjectionError(kind, node.name, node.sources.map((source) => source.revision()), cause);
1183
- const capture = (source) => {
1184
- if (disposed) return;
1185
- if (phase !== "idle") {
1186
- source.fault = new ProjectionError("source", "reentrant source", [source.revision()], /* @__PURE__ */ new Error("Source changed while projection was running."));
1187
- errors.push(source.fault);
1188
- throw source.fault;
1189
- }
1190
- pending.add(source);
1191
- require_issue.profile.projection("sourceEvents");
1192
- if (source.fault) errors.push(source.fault);
1193
- };
1194
- const settle = () => {
1195
- if (disposed || depth || phase !== "idle") return;
1196
- phase = "compute";
1197
- lock(true);
1198
- try {
1199
- pending.forEach((source) => {
1200
- if (source.shouldSettle?.() ?? true) source.consumers.forEach(enqueue);
1201
- });
1202
- while (heap.length) {
1203
- const node = pop();
1204
- if (node.disposed) continue;
1205
- const wasFaulted = node.fault !== void 0;
1206
- require_issue.profile.projection("processedNodes");
1207
- let changed = false;
1208
- const blocked = node.sources.find((source) => source.fault || source.disposed);
1209
- if (blocked) node.fault = errorFor(node, blocked.fault ?? new ProjectionDisposedError(), "blocked");
1210
- else {
1211
- const build = wasFaulted || node.sources.some((source) => source.reset());
1212
- try {
1213
- changed = node.evaluate(build);
1214
- const failure = node.sources.find((source) => source.fault)?.fault;
1215
- if (failure) throw failure;
1216
- node.fault = void 0;
1217
- } catch (cause) {
1218
- const error = errorFor(node, cause);
1219
- errors.push(error);
1220
- node.fault = error;
1221
- if (!build) try {
1222
- changed = node.evaluate(true);
1223
- const failure = node.sources.find((source) => source.fault)?.fault;
1224
- if (failure) throw failure;
1225
- node.fault = void 0;
1226
- } catch (cause) {
1227
- node.fault = errorFor(node, cause);
1228
- errors.push(node.fault);
1229
- }
1230
- }
1231
- }
1232
- completed.push(node);
1233
- if (changed || wasFaulted || node.fault) {
1234
- require_issue.profile.projection("publishedNodes");
1235
- emissions.add(node);
1236
- node.consumers.forEach(enqueue);
1237
- }
1238
- }
1239
- completed.forEach((node) => node.publish());
1240
- } finally {
1241
- lock(false);
1242
- phase = "idle";
1243
- }
1244
- };
1245
- const flush = () => {
1246
- if (disposed || depth) return [];
1247
- if (pending.size || completed.length) require_issue.profile.projection("flushes");
1248
- phase = "notify";
1249
- lock(true);
1250
- const reportFailures = [];
1251
- try {
1252
- emissions.forEach((node) => node.emit((listener) => {
1253
- try {
1254
- listener();
1255
- } catch (cause) {
1256
- errors.push(errorFor(node, cause, "listener"));
1257
- }
1258
- }));
1259
- for (const error of errors.slice()) try {
1260
- onError(error);
1261
- } catch (cause) {
1262
- reportFailures.push(cause);
1263
- }
1264
- return Object.freeze([...errors.map((error) => Object.freeze({
1265
- phase: error.phase === "listener" ? "listener" : "processor",
1266
- error
1267
- })), ...reportFailures.map((error) => Object.freeze({
1268
- phase: "listener",
1269
- error
1270
- }))]);
1271
- } finally {
1272
- pending.forEach((source) => source.clear());
1273
- completed.forEach((node) => node.clear());
1274
- pending.clear();
1275
- queued.clear();
1276
- completed.length = 0;
1277
- emissions.clear();
1278
- errors.length = 0;
1279
- reportingFailures = reportFailures;
1280
- lock(false);
1281
- phase = "idle";
1282
- }
1283
- };
1284
- const run = () => {
1285
- settle();
1286
- const result = flush();
1287
- const failures = reportingFailures;
1288
- reportingFailures = [];
1289
- if (failures.length) throw new AggregateError(failures, "Projection error reporter failed.");
1290
- return result;
1291
- };
1292
- const batch = (optionsOrCallback, maybeCallback) => {
1293
- assertIdle();
1294
- const options = typeof optionsOrCallback === "function" ? void 0 : optionsOrCallback;
1295
- const callback = typeof optionsOrCallback === "function" ? optionsOrCallback : maybeCallback;
1296
- if (!callback) throw new TypeError("Projection batch requires a callback.");
1297
- if (depth === 0) activeBatch = Object.freeze({
1298
- id: ++batchSequence,
1299
- ...options?.cause === void 0 ? {} : { cause: options.cause }
1300
- });
1301
- depth++;
1302
- let failed = false;
1303
- try {
1304
- const value = callback();
1305
- assertSynchronous(value);
1306
- return value;
1307
- } catch (error) {
1308
- failed = true;
1309
- throw error;
1310
- } finally {
1311
- depth--;
1312
- if (!depth) try {
1313
- if (failed) try {
1314
- run();
1315
- } catch {}
1316
- else run();
1317
- } finally {
1318
- activeBatch = void 0;
1319
- }
1320
- }
1321
- };
1322
- return {
1323
- assertActive,
1324
- assertIdle,
1325
- capture,
1326
- settle,
1327
- flush,
1328
- run,
1329
- records,
1330
- guards,
1331
- cleanups,
1332
- get active() {
1333
- return !disposed;
1334
- },
1335
- register(handle, record) {
1336
- assertIdle();
1337
- records.set(handle, record);
1338
- projectionHandles.add(handle);
1339
- },
1340
- unregisterSource(handle) {
1341
- const record = records.get(handle);
1342
- if (record) {
1343
- assertIdle();
1344
- if (record.consumers.size) throw new Error("Projection source is still used by a node.");
1345
- record.disposed = true;
1346
- pending.delete(record);
1347
- record.clear();
1348
- records.delete(handle);
1349
- }
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
- },
1366
- source(handle) {
1367
- assertActive();
1368
- const source = records.get(handle);
1369
- if (!source || source.disposed) throw new Error("Unknown, disposed, or foreign projection source.");
1370
- return source;
1371
- },
1372
- addNode(node) {
1373
- nodes.add(node);
1374
- node.sources.forEach((source) => source.consumers.add(node));
1375
- },
1376
- order: () => sequence++,
1377
- initialize(run) {
1378
- assertIdle();
1379
- phase = "compute";
1380
- lock(true);
1381
- try {
1382
- return run();
1383
- } finally {
1384
- lock(false);
1385
- phase = "idle";
1386
- }
1387
- },
1388
- batch,
1389
- batchContext: () => activeBatch,
1390
- dispose() {
1391
- if (disposed) return;
1392
- assertIdle();
1393
- disposed = true;
1394
- const failures = [];
1395
- cleanups.forEach((cleanup) => {
1396
- try {
1397
- cleanup();
1398
- } catch (error) {
1399
- failures.push(error);
1400
- }
1401
- });
1402
- cleanups.clear();
1403
- nodes.forEach((node) => {
1404
- node.disposed = true;
1405
- node.release();
1406
- node.consumers.clear();
1407
- });
1408
- records.forEach((source) => {
1409
- source.disposed = true;
1410
- source.consumers.clear();
1411
- source.clear();
1412
- });
1413
- nodes.clear();
1414
- records.clear();
1415
- pending.clear();
1416
- heap.length = 0;
1417
- completed.length = 0;
1418
- queued.clear();
1419
- emissions.clear();
1420
- guards.clear();
1421
- errors.length = 0;
1422
- reportingFailures = [];
1423
- if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
1424
- }
1425
- };
1426
- };
1427
- const assertSynchronous = (value) => {
1428
- if (value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") throw new TypeError("Projection callbacks must be synchronous.");
1429
- };
1430
- const assertScope = (active) => {
1431
- if (!active()) throw new Error("Projection reader or writer is no longer active.");
1432
- };
1433
- //#endregion
1434
1107
  //#region core/src/projection/source.ts
1435
1108
  const createSources = (scheduler) => {
1436
1109
  const documents = /* @__PURE__ */ new Map();
@@ -1483,7 +1156,7 @@ const createSources = (scheduler) => {
1483
1156
  nextBaselineIds = Object.freeze([]);
1484
1157
  },
1485
1158
  context: (active) => {
1486
- assertScope(active);
1159
+ require_advanced.assertScope(active);
1487
1160
  if (state.disposed) throw new Error("Document has been disposed.");
1488
1161
  const context = {
1489
1162
  state,
@@ -1493,16 +1166,16 @@ const createSources = (scheduler) => {
1493
1166
  const snapshots = collection ? /* @__PURE__ */ new Map() : void 0;
1494
1167
  const read = collection ? Object.freeze({
1495
1168
  get: (key) => {
1496
- assertScope(active);
1169
+ require_advanced.assertScope(active);
1497
1170
  if (!snapshots.has(key)) snapshots.set(key, require_scope.snapshot(rawRead.get(key)));
1498
1171
  return snapshots.get(key);
1499
1172
  },
1500
1173
  has: (key) => {
1501
- assertScope(active);
1174
+ require_advanced.assertScope(active);
1502
1175
  return rawRead.has(key);
1503
1176
  },
1504
1177
  ids: () => {
1505
- assertScope(active);
1178
+ require_advanced.assertScope(active);
1506
1179
  return rawRead.ids();
1507
1180
  }
1508
1181
  }) : require_scope.createAccess(context);
@@ -1642,7 +1315,7 @@ const createSources = (scheduler) => {
1642
1315
  flush: scheduler.flush,
1643
1316
  dispose: () => {
1644
1317
  bindings.forEach(({ record }) => {
1645
- record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1318
+ record.fault = new require_advanced.ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1646
1319
  scheduler.capture(record);
1647
1320
  });
1648
1321
  scheduler.run();
@@ -1672,7 +1345,7 @@ const createSources = (scheduler) => {
1672
1345
  reset = false;
1673
1346
  },
1674
1347
  context: (active) => {
1675
- assertScope(active);
1348
+ require_advanced.assertScope(active);
1676
1349
  return Object.freeze({
1677
1350
  kind: "value",
1678
1351
  value,
@@ -1765,18 +1438,18 @@ const createSources = (scheduler) => {
1765
1438
  cause = void 0;
1766
1439
  },
1767
1440
  context: (active) => {
1768
- assertScope(active);
1441
+ require_advanced.assertScope(active);
1769
1442
  const read = Object.freeze({
1770
1443
  get: (key) => {
1771
- assertScope(active);
1444
+ require_advanced.assertScope(active);
1772
1445
  return values.get(key);
1773
1446
  },
1774
1447
  has: (key) => {
1775
- assertScope(active);
1448
+ require_advanced.assertScope(active);
1776
1449
  return values.has(key);
1777
1450
  },
1778
1451
  ids: () => {
1779
- assertScope(active);
1452
+ require_advanced.assertScope(active);
1780
1453
  return Object.freeze([...values.keys()]);
1781
1454
  }
1782
1455
  });
@@ -1798,17 +1471,17 @@ const createSources = (scheduler) => {
1798
1471
  let active = true;
1799
1472
  const draft = Object.freeze({
1800
1473
  get: (key) => {
1801
- assertScope(() => active);
1474
+ require_advanced.assertScope(() => active);
1802
1475
  const entry = staged.get(key);
1803
1476
  return entry ? entry.present ? entry.value : void 0 : values.get(key);
1804
1477
  },
1805
1478
  has: (key) => {
1806
- assertScope(() => active);
1479
+ require_advanced.assertScope(() => active);
1807
1480
  const entry = staged.get(key);
1808
1481
  return entry ? entry.present : values.has(key);
1809
1482
  },
1810
1483
  set: (key, value) => {
1811
- assertScope(() => active);
1484
+ require_advanced.assertScope(() => active);
1812
1485
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1813
1486
  staged.set(key, {
1814
1487
  present: true,
@@ -1816,13 +1489,13 @@ const createSources = (scheduler) => {
1816
1489
  });
1817
1490
  },
1818
1491
  remove: (key) => {
1819
- assertScope(() => active);
1492
+ require_advanced.assertScope(() => active);
1820
1493
  if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1821
1494
  staged.set(key, { present: false });
1822
1495
  }
1823
1496
  });
1824
1497
  try {
1825
- assertSynchronous(run(draft));
1498
+ require_advanced.assertSynchronous(run(draft));
1826
1499
  } finally {
1827
1500
  active = false;
1828
1501
  }
@@ -1854,7 +1527,7 @@ const createSources = (scheduler) => {
1854
1527
  const input = valueSource(port.current(), equal);
1855
1528
  const fail = (cause) => {
1856
1529
  const record = scheduler.source(input.source);
1857
- record.fault = new ProjectionError("source", "external source", [record.revision()], cause);
1530
+ record.fault = new require_advanced.ProjectionError("source", "external source", [record.revision()], cause);
1858
1531
  scheduler.capture(record);
1859
1532
  };
1860
1533
  let unsubscribe;
@@ -1910,18 +1583,18 @@ const createSources = (scheduler) => {
1910
1583
  transitionKeys.clear();
1911
1584
  },
1912
1585
  context: (active) => {
1913
- assertScope(active);
1586
+ require_advanced.assertScope(active);
1914
1587
  const currentRead = Object.freeze({
1915
1588
  get: (key) => {
1916
- assertScope(active);
1589
+ require_advanced.assertScope(active);
1917
1590
  return read.get(key);
1918
1591
  },
1919
1592
  has: (key) => {
1920
- assertScope(active);
1593
+ require_advanced.assertScope(active);
1921
1594
  return read.has(key);
1922
1595
  },
1923
1596
  ids: () => {
1924
- assertScope(active);
1597
+ require_advanced.assertScope(active);
1925
1598
  return read.ids();
1926
1599
  }
1927
1600
  });
@@ -1936,7 +1609,7 @@ const createSources = (scheduler) => {
1936
1609
  };
1937
1610
  scheduler.register(handle, record);
1938
1611
  const fail = (error) => {
1939
- record.fault = new ProjectionError("source", "external collection source", [revision], error);
1612
+ record.fault = new require_advanced.ProjectionError("source", "external collection source", [revision], error);
1940
1613
  scheduler.capture(record);
1941
1614
  };
1942
1615
  const deriveChange = () => {
@@ -2042,7 +1715,7 @@ const createSources = (scheduler) => {
2042
1715
  collectionInput,
2043
1716
  fromReadable: (readable, options) => {
2044
1717
  scheduler.assertIdle();
2045
- if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
1718
+ if (require_advanced.projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
2046
1719
  const equal = options?.isEqual ?? Object.is;
2047
1720
  let bindings = readables.get(readable);
2048
1721
  const old = bindings?.get(equal);
@@ -2068,7 +1741,7 @@ const createSources = (scheduler) => {
2068
1741
  } catch (cause) {
2069
1742
  for (const member of members.values()) {
2070
1743
  const record = scheduler.source(member.source);
2071
- record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
1744
+ record.fault = new require_advanced.ProjectionError("source", "external readable", [record.revision()], cause);
2072
1745
  scheduler.capture(record);
2073
1746
  }
2074
1747
  if (settle) scheduler.run();
@@ -2078,7 +1751,7 @@ const createSources = (scheduler) => {
2078
1751
  member.accept(value);
2079
1752
  } catch (cause) {
2080
1753
  const record = scheduler.source(member.source);
2081
- record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
1754
+ record.fault = new require_advanced.ProjectionError("source", "external readable", [record.revision()], cause);
2082
1755
  scheduler.capture(record);
2083
1756
  }
2084
1757
  if (settle) scheduler.run();
@@ -2096,7 +1769,7 @@ const createSources = (scheduler) => {
2096
1769
  dispose: () => {
2097
1770
  for (const member of members.values()) {
2098
1771
  const record = scheduler.source(member.source);
2099
- record.fault = new ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
1772
+ record.fault = new require_advanced.ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
2100
1773
  scheduler.capture(record);
2101
1774
  }
2102
1775
  scheduler.run();
@@ -2133,6 +1806,7 @@ const createNode = (scheduler, spec, behavior) => {
2133
1806
  const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
2134
1807
  const order = scheduler.order();
2135
1808
  const node = {
1809
+ owner: void 0,
2136
1810
  ...behavior,
2137
1811
  publish: () => {
2138
1812
  if (node.fault) behavior.clear();
@@ -2155,15 +1829,16 @@ const createNode = (scheduler, spec, behavior) => {
2155
1829
  }
2156
1830
  }
2157
1831
  };
1832
+ node.owner = node;
2158
1833
  scheduler.initialize(() => {
2159
1834
  const invalid = node.sources.find((source) => source.fault || source.disposed);
2160
- if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
1835
+ if (invalid) throw invalid.fault ?? new require_advanced.ProjectionDisposedError();
2161
1836
  node.evaluate(true);
2162
1837
  node.publish();
2163
1838
  node.clear();
2164
1839
  });
2165
1840
  const check = () => {
2166
- if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
1841
+ if (node.disposed || !scheduler.active) throw new require_advanced.ProjectionDisposedError();
2167
1842
  if (node.fault) throw node.fault;
2168
1843
  };
2169
1844
  return {
@@ -2195,17 +1870,17 @@ const createValue = (scheduler, spec, options) => {
2195
1870
  require_issue.profile.materialized.rebuilt();
2196
1871
  instance = void 0;
2197
1872
  const built = spec.build(sources);
2198
- assertSynchronous(built);
1873
+ require_advanced.assertSynchronous(built);
2199
1874
  instance = built;
2200
1875
  candidate = built.value;
2201
1876
  } else {
2202
1877
  require_issue.profile.materialized.updated();
2203
1878
  const result = instance.update(sources);
2204
- assertSynchronous(result);
1879
+ require_advanced.assertSynchronous(result);
2205
1880
  if (result.kind === "rebuild") {
2206
1881
  instance = void 0;
2207
1882
  const built = spec.build(sources);
2208
- assertSynchronous(built);
1883
+ require_advanced.assertSynchronous(built);
2209
1884
  instance = built;
2210
1885
  candidate = built.value;
2211
1886
  build = true;
@@ -2217,7 +1892,7 @@ const createValue = (scheduler, spec, options) => {
2217
1892
  return changed;
2218
1893
  },
2219
1894
  context: (active) => {
2220
- assertScope(active);
1895
+ require_advanced.assertScope(active);
2221
1896
  return Object.freeze({
2222
1897
  kind: "value",
2223
1898
  value: next,
@@ -2271,7 +1946,7 @@ const createValue = (scheduler, spec, options) => {
2271
1946
  return handle;
2272
1947
  };
2273
1948
  //#endregion
2274
- //#region core/src/projection/collection.ts
1949
+ //#region core/src/projection/collection-state.ts
2275
1950
  const height = (node) => node?.height ?? 0;
2276
1951
  const makeTreeNode = (key, value, left, right) => Object.freeze({
2277
1952
  key,
@@ -2337,214 +2012,197 @@ const treeFromSorted = (entries, start = 0, end = entries.length) => {
2337
2012
  const [key, value] = entries[middle];
2338
2013
  return makeTreeNode(key, value, treeFromSorted(entries, start, middle), treeFromSorted(entries, middle + 1, end));
2339
2014
  };
2340
- const createCollection = (scheduler, spec) => {
2015
+ /**
2016
+ * The one keyed publication kernel used by ordinary collection nodes and
2017
+ * processor-group output leaves. It owns staged intent, exact transitions and
2018
+ * the persistent published snapshot; callers only provide the processor
2019
+ * callback and the equality policy.
2020
+ */
2021
+ const createCollectionState = () => {
2341
2022
  const values = /* @__PURE__ */ new Map();
2342
2023
  let ids = Object.freeze([]);
2343
2024
  let staged = /* @__PURE__ */ new Map();
2344
2025
  let nextIds = ids;
2345
2026
  let change;
2346
- let instance;
2347
2027
  let initialized = false;
2348
2028
  let reset = false;
2349
2029
  let revision = 0;
2350
2030
  let publishedRoot;
2351
- let cause;
2352
- const listeners = /* @__PURE__ */ new Set();
2353
- const changedKeys = /* @__PURE__ */ new Set();
2031
+ let changedKeys = /* @__PURE__ */ new Set();
2354
2032
  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);
2033
+ let explicitOrder;
2034
+ let cleared = false;
2035
+ const listeners = /* @__PURE__ */ new Set();
2036
+ const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
2370
2037
  const getNext = (key) => {
2371
2038
  const entry = staged.get(key);
2372
- return entry ? entry.present ? entry.value : void 0 : values.get(key);
2039
+ return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
2373
2040
  };
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);
2041
+ const deriveIds = () => {
2042
+ const result = cleared ? [] : ids.filter(hasNext);
2043
+ for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
2044
+ return result;
2045
+ };
2046
+ const begin = (active, nextReset) => {
2047
+ staged = /* @__PURE__ */ new Map();
2048
+ nextIds = ids;
2049
+ change = void 0;
2050
+ changedKeys = /* @__PURE__ */ new Set();
2051
+ explicitOrder = void 0;
2052
+ orderChanged = false;
2053
+ cleared = nextReset;
2054
+ const read = (next) => ({
2055
+ get: (key) => {
2056
+ require_advanced.assertScope(active);
2057
+ return next ? getNext(key) : values.get(key);
2058
+ },
2059
+ has: (key) => {
2060
+ require_advanced.assertScope(active);
2061
+ return next ? hasNext(key) : values.has(key);
2062
+ },
2063
+ ids: () => {
2064
+ require_advanced.assertScope(active);
2065
+ return next ? Object.freeze(explicitOrder ? [...explicitOrder] : [...deriveIds()]) : ids;
2066
+ }
2067
+ });
2068
+ return {
2069
+ previous: read(false),
2070
+ next: read(true),
2071
+ output: {
2072
+ set: (key, value) => {
2073
+ require_advanced.assertScope(active);
2074
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
2075
+ staged.set(key, {
2076
+ present: true,
2077
+ value
2078
+ });
2397
2079
  },
2398
- has: (key) => {
2399
- assertScope(active);
2400
- return next ? nextHas(key) : values.has(key);
2080
+ remove: (key) => {
2081
+ require_advanced.assertScope(active);
2082
+ staged.set(key, { present: false });
2401
2083
  },
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
- }
2084
+ order: (order) => {
2085
+ require_advanced.assertScope(active);
2086
+ explicitOrder = Object.freeze([...order]);
2428
2087
  }
2429
- };
2430
- if (build || !instance) {
2431
- require_issue.profile.materialized.rebuilt();
2432
- instance = void 0;
2433
- cleared = true;
2434
- const built = spec.build(input);
2435
- assertSynchronous(built);
2436
- instance = built;
2437
- } else {
2438
- require_issue.profile.materialized.updated();
2439
- const result = instance.update(input);
2440
- assertSynchronous(result);
2441
- if (result?.kind === "rebuild") {
2442
- staged.clear();
2443
- cleared = true;
2444
- explicitOrder = void 0;
2445
- instance = void 0;
2446
- const built = spec.build(input);
2447
- assertSynchronous(built);
2448
- instance = built;
2449
- build = true;
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
2088
  }
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());
2089
+ };
2090
+ };
2091
+ const seal = (nextReset, isEqual) => {
2092
+ if (cleared) {
2093
+ for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
2094
+ }
2095
+ require_issue.profile.projection("touchedKeys", staged.size);
2096
+ const added = /* @__PURE__ */ new Set();
2097
+ const removed = /* @__PURE__ */ new Set();
2098
+ const updated = /* @__PURE__ */ new Set();
2099
+ for (const [key, entry] of staged) {
2100
+ const existed = values.has(key);
2101
+ if (entry.present) {
2102
+ if (!existed) added.add(key);
2103
+ else if (!isEqual(values.get(key), entry.value)) updated.add(key);
2104
+ else if (!nextReset) staged.delete(key);
2105
+ } else if (existed) removed.add(key);
2106
+ else staged.delete(key);
2107
+ }
2108
+ if (explicitOrder || added.size || removed.size) {
2109
+ const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
2110
+ 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.");
2111
+ nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
2112
+ } else nextIds = ids;
2113
+ orderChanged = nextIds !== ids;
2114
+ for (const key of added) changedKeys.add(key);
2115
+ for (const key of removed) changedKeys.add(key);
2116
+ for (const key of updated) changedKeys.add(key);
2117
+ require_issue.profile.projection("changedKeys", changedKeys.size);
2118
+ const addedEntries = [];
2119
+ const updatedEntries = [];
2120
+ const removedEntries = [];
2121
+ for (const key of changedKeys) {
2122
+ const beforePresent = values.has(key);
2123
+ const afterPresent = hasNext(key);
2124
+ if (beforePresent === afterPresent) {
2125
+ if (!beforePresent || isEqual(values.get(key), getNext(key))) continue;
2471
2126
  }
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
- });
2127
+ if (!beforePresent) addedEntries.push({
2128
+ key,
2129
+ after: getNext(key)
2130
+ });
2131
+ else if (!afterPresent) removedEntries.push({
2132
+ key,
2133
+ before: values.get(key)
2134
+ });
2135
+ else updatedEntries.push({
2136
+ key,
2137
+ before: values.get(key),
2138
+ after: getNext(key)
2139
+ });
2140
+ }
2141
+ if (nextReset) change = Object.freeze({ kind: "reset" });
2142
+ else if (addedEntries.length || updatedEntries.length || removedEntries.length || orderChanged) {
2143
+ let commonOrderChanged = false;
2144
+ if (orderChanged) {
2145
+ const before = ids.filter((key) => !removed.has(key));
2146
+ const after = nextIds.filter((key) => !added.has(key));
2147
+ commonOrderChanged = before.some((key, index) => after[index] !== key);
2502
2148
  }
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
- });
2149
+ change = Object.freeze({
2150
+ kind: "incremental",
2151
+ added: Object.freeze(addedEntries),
2152
+ updated: Object.freeze(updatedEntries),
2153
+ removed: Object.freeze(removedEntries),
2154
+ ...commonOrderChanged ? { order: Object.freeze({
2155
+ before: Object.freeze([...ids]),
2156
+ after: Object.freeze([...nextIds])
2157
+ }) } : {}
2158
+ });
2159
+ }
2160
+ reset = nextReset;
2161
+ return change !== void 0;
2162
+ };
2163
+ const context = (active, cause) => Object.freeze({
2164
+ kind: "collection",
2165
+ read: {
2166
+ get: (key) => {
2167
+ require_advanced.assertScope(active);
2168
+ return getNext(key);
2169
+ },
2170
+ has: (key) => {
2171
+ require_advanced.assertScope(active);
2172
+ return hasNext(key);
2173
+ },
2174
+ ids: () => {
2175
+ require_advanced.assertScope(active);
2176
+ return nextIds;
2521
2177
  }
2522
- reset = build;
2523
- return change !== void 0;
2524
- },
2525
- context: (active) => {
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);
2547
2178
  },
2179
+ change,
2180
+ revision: revision + (change ? 1 : 0),
2181
+ cause
2182
+ });
2183
+ const current = (check) => {
2184
+ const root = publishedRoot;
2185
+ const publishedIds = ids;
2186
+ return Object.freeze({
2187
+ get: (key) => {
2188
+ check();
2189
+ return treeGet(root, key);
2190
+ },
2191
+ has: (key) => {
2192
+ check();
2193
+ return treeHas(root, key);
2194
+ },
2195
+ ids: () => {
2196
+ check();
2197
+ return publishedIds;
2198
+ }
2199
+ });
2200
+ };
2201
+ return {
2202
+ begin,
2203
+ seal,
2204
+ context,
2205
+ current,
2548
2206
  revision: () => revision,
2549
2207
  reset: () => reset,
2550
2208
  publish: () => {
@@ -2556,14 +2214,11 @@ const createCollection = (scheduler, spec) => {
2556
2214
  } else for (const [key, entry] of staged) publishedRoot = entry.present ? treeSet(publishedRoot, key, entry.value) : treeRemove(publishedRoot, key);
2557
2215
  for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
2558
2216
  else values.delete(key);
2559
- if (change) {
2560
- if (initialized) revision++;
2561
- }
2217
+ if (change && initialized) revision++;
2562
2218
  ids = nextIds;
2563
2219
  initialized = true;
2564
2220
  },
2565
2221
  emit: (call) => {
2566
- require_issue.profile.materialized.notification();
2567
2222
  const resetEvent = Object.freeze({ kind: "reset" });
2568
2223
  Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
2569
2224
  },
@@ -2573,8 +2228,9 @@ const createCollection = (scheduler, spec) => {
2573
2228
  change = void 0;
2574
2229
  reset = false;
2575
2230
  changedKeys.clear();
2576
- cause = void 0;
2231
+ explicitOrder = void 0;
2577
2232
  orderChanged = false;
2233
+ cleared = false;
2578
2234
  },
2579
2235
  release: () => {
2580
2236
  values.clear();
@@ -2582,6 +2238,66 @@ const createCollection = (scheduler, spec) => {
2582
2238
  publishedRoot = void 0;
2583
2239
  listeners.clear();
2584
2240
  ids = nextIds = Object.freeze([]);
2241
+ change = void 0;
2242
+ initialized = false;
2243
+ revision = 0;
2244
+ },
2245
+ subscribe: (listener) => {
2246
+ listeners.add(listener);
2247
+ },
2248
+ unsubscribe: (listener) => {
2249
+ listeners.delete(listener);
2250
+ }
2251
+ };
2252
+ };
2253
+ //#endregion
2254
+ //#region core/src/projection/collection.ts
2255
+ const createCollection = (scheduler, spec) => {
2256
+ const state = createCollectionState();
2257
+ let instance;
2258
+ let cause;
2259
+ const owner = createNode(scheduler, spec, {
2260
+ evaluate: (sources, build, active) => {
2261
+ cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2262
+ const input = state.begin(active, build);
2263
+ if (build || !instance) {
2264
+ require_issue.profile.materialized.rebuilt();
2265
+ instance = void 0;
2266
+ const built = spec.build({
2267
+ sources,
2268
+ ...input
2269
+ });
2270
+ require_advanced.assertSynchronous(built);
2271
+ instance = built;
2272
+ } else {
2273
+ require_issue.profile.materialized.updated();
2274
+ const result = instance.update({
2275
+ sources,
2276
+ ...input
2277
+ });
2278
+ require_advanced.assertSynchronous(result);
2279
+ if (result?.kind === "rebuild") {
2280
+ const rebuildInput = state.begin(active, true);
2281
+ instance = void 0;
2282
+ const built = spec.build({
2283
+ sources,
2284
+ ...rebuildInput
2285
+ });
2286
+ require_advanced.assertSynchronous(built);
2287
+ instance = built;
2288
+ build = true;
2289
+ }
2290
+ }
2291
+ return state.seal(build, spec.isEqual ?? Object.is);
2292
+ },
2293
+ context: (active) => state.context(active, cause),
2294
+ revision: state.revision,
2295
+ reset: state.reset,
2296
+ publish: state.publish,
2297
+ emit: state.emit,
2298
+ clear: state.clear,
2299
+ release: () => {
2300
+ state.release();
2585
2301
  instance = void 0;
2586
2302
  }
2587
2303
  });
@@ -2589,34 +2305,186 @@ const createCollection = (scheduler, spec) => {
2589
2305
  kind: "collection",
2590
2306
  current: () => {
2591
2307
  owner.check();
2592
- return readPublished(publishedRoot, ids);
2308
+ return state.current(owner.check);
2593
2309
  },
2594
2310
  revision: () => {
2595
2311
  owner.check();
2596
- return revision;
2312
+ return state.revision();
2597
2313
  },
2598
2314
  subscribe: (listener) => {
2599
2315
  owner.check();
2600
- listeners.add(listener);
2601
- return () => {
2602
- listeners.delete(listener);
2603
- };
2316
+ state.subscribe(listener);
2317
+ return () => state.unsubscribe(listener);
2604
2318
  }
2605
2319
  };
2606
2320
  owner.install(handle);
2607
2321
  return Object.freeze(handle);
2608
2322
  };
2609
2323
  //#endregion
2324
+ //#region core/src/projection/group.ts
2325
+ /**
2326
+ * Materializes one processor callback with several keyed output leaves. The
2327
+ * scheduler sees one node and the output leaves remain ordinary source records
2328
+ * for dependency, revision and listener matching.
2329
+ */
2330
+ const createCollectionGroup = (scheduler, spec) => {
2331
+ scheduler.assertIdle();
2332
+ const sources = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
2333
+ const states = spec.outputs.map(() => createCollectionState());
2334
+ let instance;
2335
+ let cause;
2336
+ let changedOutputs = Object.freeze([]);
2337
+ let node;
2338
+ const order = scheduler.order();
2339
+ const outputs = states.map((state, index) => {
2340
+ const output = {
2341
+ owner: void 0,
2342
+ consumers: /* @__PURE__ */ new Set(),
2343
+ context: (active) => state.context(active, cause),
2344
+ revision: state.revision,
2345
+ reset: state.reset,
2346
+ fault: void 0,
2347
+ disposed: false,
2348
+ clear: state.clear,
2349
+ emit: state.emit
2350
+ };
2351
+ Object.defineProperty(output, "fault", {
2352
+ configurable: false,
2353
+ enumerable: true,
2354
+ get: () => node.fault,
2355
+ set: (value) => {
2356
+ node.fault = value;
2357
+ }
2358
+ });
2359
+ return output;
2360
+ });
2361
+ const check = () => {
2362
+ if (node.disposed || !scheduler.active) throw new require_advanced.ProjectionDisposedError();
2363
+ if (node.fault) throw node.fault;
2364
+ };
2365
+ const evaluate = (inputSources, build, active) => {
2366
+ cause = Object.values(inputSources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2367
+ const makeInput = (reset) => {
2368
+ const evaluations = states.map((state) => state.begin(active, reset));
2369
+ return {
2370
+ sources: inputSources,
2371
+ previous: evaluations.map((evaluation) => evaluation.previous),
2372
+ next: evaluations.map((evaluation) => evaluation.next),
2373
+ outputs: evaluations.map((evaluation) => evaluation.output)
2374
+ };
2375
+ };
2376
+ let input = makeInput(build);
2377
+ if (build || !instance) {
2378
+ require_issue.profile.materialized.rebuilt();
2379
+ instance = void 0;
2380
+ const built = spec.build(input);
2381
+ require_advanced.assertSynchronous(built);
2382
+ instance = built;
2383
+ } else {
2384
+ require_issue.profile.materialized.updated();
2385
+ const result = instance.update(input);
2386
+ require_advanced.assertSynchronous(result);
2387
+ if (result?.kind === "rebuild") {
2388
+ input = makeInput(true);
2389
+ instance = void 0;
2390
+ const built = spec.build(input);
2391
+ require_advanced.assertSynchronous(built);
2392
+ instance = built;
2393
+ build = true;
2394
+ }
2395
+ }
2396
+ changedOutputs = Object.freeze(states.flatMap((state, index) => state.seal(build, spec.outputs[index].isEqual ?? Object.is) ? [outputs[index]] : []));
2397
+ return changedOutputs.length > 0;
2398
+ };
2399
+ node = {
2400
+ owner: void 0,
2401
+ order,
2402
+ name: `processor-group (${order})`,
2403
+ sources: sources.map(([, source]) => source),
2404
+ consumers: /* @__PURE__ */ new Set(),
2405
+ outputs,
2406
+ disposed: false,
2407
+ fault: void 0,
2408
+ context: (active) => {
2409
+ require_advanced.assertScope(active);
2410
+ return Object.freeze({ kind: "group" });
2411
+ },
2412
+ revision: () => Math.max(0, ...states.map((state) => state.revision())),
2413
+ reset: () => states.some((state) => state.reset()),
2414
+ evaluate: (build) => {
2415
+ let active = true;
2416
+ try {
2417
+ const scopeActive = () => active;
2418
+ return evaluate(Object.fromEntries(sources.map(([key, source]) => [key, source.context(scopeActive)])), build, scopeActive);
2419
+ } finally {
2420
+ active = false;
2421
+ }
2422
+ },
2423
+ changedOutputs: () => changedOutputs,
2424
+ publish: () => {
2425
+ if (node.fault) states.forEach((state) => state.clear());
2426
+ else states.forEach((state) => state.publish());
2427
+ },
2428
+ emit: () => void 0,
2429
+ clear: () => {
2430
+ states.forEach((state) => state.clear());
2431
+ changedOutputs = Object.freeze([]);
2432
+ cause = void 0;
2433
+ },
2434
+ release: () => {
2435
+ states.forEach((state) => state.release());
2436
+ outputs.forEach((output) => {
2437
+ output.disposed = true;
2438
+ output.consumers.clear();
2439
+ });
2440
+ instance = void 0;
2441
+ }
2442
+ };
2443
+ node.owner = node;
2444
+ outputs.forEach((output) => {
2445
+ output.owner = node;
2446
+ });
2447
+ scheduler.initialize(() => {
2448
+ const invalid = node.sources.find((source) => source.fault || source.disposed);
2449
+ if (invalid) throw invalid.fault ?? new require_advanced.ProjectionDisposedError();
2450
+ node.evaluate(true);
2451
+ node.publish();
2452
+ node.clear();
2453
+ });
2454
+ const handles = outputs.map((output, index) => {
2455
+ const handle = {
2456
+ kind: "collection",
2457
+ current: () => {
2458
+ check();
2459
+ return states[index].current(check);
2460
+ },
2461
+ revision: () => {
2462
+ check();
2463
+ return states[index].revision();
2464
+ },
2465
+ subscribe: (listener) => {
2466
+ check();
2467
+ states[index].subscribe(listener);
2468
+ return () => states[index].unsubscribe(listener);
2469
+ }
2470
+ };
2471
+ scheduler.register(handle, output);
2472
+ return Object.freeze(handle);
2473
+ });
2474
+ scheduler.addNode(node);
2475
+ return Object.freeze(handles);
2476
+ };
2477
+ //#endregion
2610
2478
  //#region core/src/projection/graph.ts
2611
2479
  const createProjectionGraph = (options) => {
2612
- const scheduler = createScheduler(options.onError);
2480
+ const scheduler = require_advanced.createScheduler(options.onError);
2613
2481
  const sources = createSources(scheduler);
2614
2482
  const value = (spec, options) => createValue(scheduler, spec, options);
2615
2483
  const map = (source, mapper, options) => {
2616
2484
  sources.assertCollection(source);
2617
2485
  const calculate = (id, entry) => {
2618
2486
  const value = mapper(id, entry);
2619
- assertSynchronous(value);
2487
+ require_advanced.assertSynchronous(value);
2620
2488
  return value;
2621
2489
  };
2622
2490
  return createCollection(scheduler, {
@@ -2664,6 +2532,7 @@ const createProjectionGraph = (options) => {
2664
2532
  fromReadable: sources.fromReadable,
2665
2533
  value,
2666
2534
  collection: (spec) => createCollection(scheduler, spec),
2535
+ group: (spec) => createCollectionGroup(scheduler, spec),
2667
2536
  map,
2668
2537
  batch: scheduler.batch,
2669
2538
  assertIdle: scheduler.assertIdle,
@@ -2838,17 +2707,42 @@ const sourceOf = (instance) => instance;
2838
2707
  const createProjectionRuntime = (options) => {
2839
2708
  const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
2840
2709
  const materialized = /* @__PURE__ */ new WeakMap();
2710
+ const groups = /* @__PURE__ */ new WeakMap();
2841
2711
  let disposed = false;
2842
2712
  const scopes = /* @__PURE__ */ new Set();
2843
- const materialize = (projection, requester) => {
2713
+ let materialize;
2714
+ const materializeGroup = (definition, requester) => {
2715
+ const existing = groups.get(definition);
2716
+ if (existing) return existing;
2717
+ if (definition.projections.map((projection) => require_advanced.ownerOf(projection)).filter((owner) => owner !== void 0).some((owner) => owner !== requester)) throw new TypeError("Projection group belongs to another scope.");
2718
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, requester))]));
2719
+ const nodes = runtime.group({
2720
+ sources,
2721
+ outputs: definition.outputs,
2722
+ build: definition.build
2723
+ });
2724
+ const group = Object.freeze({
2725
+ definition,
2726
+ nodes
2727
+ });
2728
+ groups.set(definition, group);
2729
+ for (let index = 0; index < definition.projections.length; index++) materialized.set(definition.projections[index], {
2730
+ kind: "projection",
2731
+ node: nodes[index]
2732
+ });
2733
+ if (requester) requester.materialized.push(definition);
2734
+ return group;
2735
+ };
2736
+ materialize = (projection, requester) => {
2844
2737
  const owner = require_advanced.ownerOf(projection);
2845
2738
  if (owner) {
2846
2739
  if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
2847
- if (!requester.active) throw new ProjectionDisposedError();
2740
+ if (!requester.active) throw new require_advanced.ProjectionDisposedError();
2848
2741
  }
2849
2742
  const old = materialized.get(projection);
2850
2743
  if (old) return old.node;
2851
2744
  const definition = require_advanced.definitionOf(projection);
2745
+ if (definition.kind === "incremental-group-output") return materializeGroup(definition.group, requester).nodes[definition.output];
2852
2746
  let instance;
2853
2747
  let write;
2854
2748
  switch (definition.kind) {
@@ -3156,12 +3050,12 @@ const createProjectionRuntime = (options) => {
3156
3050
  };
3157
3051
  scopes.add(state);
3158
3052
  const assertActive = () => {
3159
- if (!state.active) throw new ProjectionDisposedError();
3053
+ if (!state.active) throw new require_advanced.ProjectionDisposedError();
3160
3054
  };
3161
3055
  const own = (projection) => {
3162
3056
  assertActive();
3163
3057
  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) : [];
3058
+ const dependencies = definition.kind === "derive" ? definition.dependencies : definition.kind === "incremental-value" || definition.kind === "incremental-collection" ? Object.values(definition.dependencies) : definition.kind === "incremental-group-output" ? Object.values(definition.group.dependencies) : [];
3165
3059
  for (const dependency of dependencies) {
3166
3060
  const dependencyOwner = require_advanced.ownerOf(dependency);
3167
3061
  if (dependencyOwner && dependencyOwner !== state) throw new TypeError("A scope cannot depend on another scope.");
@@ -3171,7 +3065,16 @@ const createProjectionRuntime = (options) => {
3171
3065
  };
3172
3066
  const scopedInput = Object.assign((initial, equality) => own(require_advanced.input(initial, equality)), { collection: (initial) => own(require_advanced.input.collection(initial)) });
3173
3067
  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)) });
3068
+ const ownOutputTree = (tree) => {
3069
+ if (require_advanced.isProjection(tree)) return own(tree);
3070
+ if (!tree || typeof tree !== "object") throw new TypeError("Incremental group output tree must be an object.");
3071
+ for (const value of Object.values(tree)) ownOutputTree(value);
3072
+ return tree;
3073
+ };
3074
+ const scopedIncremental = Object.assign((dependencies, processor) => own(require_advanced.incremental(dependencies, processor)), {
3075
+ collection: (dependencies, processor) => own(require_advanced.incremental.collection(dependencies, processor)),
3076
+ group: (dependencies, defineOutputs, processor) => ownOutputTree(require_advanced.incremental.group(dependencies, defineOutputs, processor))
3077
+ });
3175
3078
  function scopeReadable(projection, selector, equality) {
3176
3079
  assertActive();
3177
3080
  materialize(projection, state);
@@ -3228,6 +3131,13 @@ const createProjectionRuntime = (options) => {
3228
3131
  state.active = false;
3229
3132
  state.subscriptions.forEach((unsubscribe) => unsubscribe());
3230
3133
  for (const definition of state.materialized.reverse()) {
3134
+ const group = groups.get(definition);
3135
+ if (group) {
3136
+ runtime.releaseNode(group.nodes[0]);
3137
+ groups.delete(definition);
3138
+ group.definition.projections.forEach((projection) => materialized.delete(projection));
3139
+ continue;
3140
+ }
3231
3141
  const record = materialized.get(definition);
3232
3142
  if (!record) continue;
3233
3143
  runtime.releaseNode(record.node);
@@ -3266,8 +3176,8 @@ const createProjectionRuntime = (options) => {
3266
3176
  exports.DocumentDisposedError = require_notification.DocumentDisposedError;
3267
3177
  exports.DocumentReentrancyError = require_notification.DocumentReentrancyError;
3268
3178
  exports.ParseError = require_scope.ParseError;
3269
- exports.ProjectionDisposedError = ProjectionDisposedError;
3270
- exports.ProjectionError = ProjectionError;
3179
+ exports.ProjectionDisposedError = require_advanced.ProjectionDisposedError;
3180
+ exports.ProjectionError = require_advanced.ProjectionError;
3271
3181
  exports.TransactionRejected = require_notification.TransactionRejected;
3272
3182
  exports.asReadable = asReadable;
3273
3183
  exports.createDocument = createDocument;