doxum 0.1.17 → 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_definition = require("./definition-De6rhTlZ.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,314 +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) => source.consumers.forEach(enqueue));
1200
- while (heap.length) {
1201
- const node = pop();
1202
- if (node.disposed) continue;
1203
- const wasFaulted = node.fault !== void 0;
1204
- require_issue.profile.projection("processedNodes");
1205
- let changed = false;
1206
- const blocked = node.sources.find((source) => source.fault || source.disposed);
1207
- if (blocked) node.fault = errorFor(node, blocked.fault ?? new ProjectionDisposedError(), "blocked");
1208
- else {
1209
- const build = wasFaulted || node.sources.some((source) => source.reset());
1210
- try {
1211
- changed = node.evaluate(build);
1212
- const failure = node.sources.find((source) => source.fault)?.fault;
1213
- if (failure) throw failure;
1214
- node.fault = void 0;
1215
- } catch (cause) {
1216
- const error = errorFor(node, cause);
1217
- errors.push(error);
1218
- node.fault = error;
1219
- if (!build) try {
1220
- changed = node.evaluate(true);
1221
- const failure = node.sources.find((source) => source.fault)?.fault;
1222
- if (failure) throw failure;
1223
- node.fault = void 0;
1224
- } catch (cause) {
1225
- node.fault = errorFor(node, cause);
1226
- errors.push(node.fault);
1227
- }
1228
- }
1229
- }
1230
- completed.push(node);
1231
- if (changed || wasFaulted || node.fault) {
1232
- require_issue.profile.projection("publishedNodes");
1233
- emissions.add(node);
1234
- node.consumers.forEach(enqueue);
1235
- }
1236
- }
1237
- completed.forEach((node) => node.publish());
1238
- } finally {
1239
- lock(false);
1240
- phase = "idle";
1241
- }
1242
- };
1243
- const flush = () => {
1244
- if (disposed || depth) return [];
1245
- if (pending.size || completed.length) require_issue.profile.projection("flushes");
1246
- phase = "notify";
1247
- lock(true);
1248
- const reportFailures = [];
1249
- try {
1250
- emissions.forEach((node) => node.emit((listener) => {
1251
- try {
1252
- listener();
1253
- } catch (cause) {
1254
- errors.push(errorFor(node, cause, "listener"));
1255
- }
1256
- }));
1257
- for (const error of errors.slice()) try {
1258
- onError(error);
1259
- } catch (cause) {
1260
- reportFailures.push(cause);
1261
- }
1262
- return Object.freeze([...errors.map((error) => Object.freeze({
1263
- phase: error.phase === "listener" ? "listener" : "processor",
1264
- error
1265
- })), ...reportFailures.map((error) => Object.freeze({
1266
- phase: "listener",
1267
- error
1268
- }))]);
1269
- } finally {
1270
- pending.forEach((source) => source.clear());
1271
- completed.forEach((node) => node.clear());
1272
- pending.clear();
1273
- queued.clear();
1274
- completed.length = 0;
1275
- emissions.clear();
1276
- errors.length = 0;
1277
- reportingFailures = reportFailures;
1278
- lock(false);
1279
- phase = "idle";
1280
- }
1281
- };
1282
- const run = () => {
1283
- settle();
1284
- const result = flush();
1285
- const failures = reportingFailures;
1286
- reportingFailures = [];
1287
- if (failures.length) throw new AggregateError(failures, "Projection error reporter failed.");
1288
- return result;
1289
- };
1290
- const batch = (optionsOrCallback, maybeCallback) => {
1291
- assertIdle();
1292
- const options = typeof optionsOrCallback === "function" ? void 0 : optionsOrCallback;
1293
- const callback = typeof optionsOrCallback === "function" ? optionsOrCallback : maybeCallback;
1294
- if (!callback) throw new TypeError("Projection batch requires a callback.");
1295
- if (depth === 0) activeBatch = Object.freeze({
1296
- id: ++batchSequence,
1297
- ...options?.cause === void 0 ? {} : { cause: options.cause }
1298
- });
1299
- depth++;
1300
- let failed = false;
1301
- try {
1302
- const value = callback();
1303
- assertSynchronous(value);
1304
- return value;
1305
- } catch (error) {
1306
- failed = true;
1307
- throw error;
1308
- } finally {
1309
- depth--;
1310
- if (!depth) try {
1311
- if (failed) try {
1312
- run();
1313
- } catch {}
1314
- else run();
1315
- } finally {
1316
- activeBatch = void 0;
1317
- }
1318
- }
1319
- };
1320
- return {
1321
- assertActive,
1322
- assertIdle,
1323
- capture,
1324
- settle,
1325
- flush,
1326
- run,
1327
- records,
1328
- guards,
1329
- cleanups,
1330
- get active() {
1331
- return !disposed;
1332
- },
1333
- register(handle, record) {
1334
- assertIdle();
1335
- records.set(handle, record);
1336
- projectionHandles.add(handle);
1337
- },
1338
- unregisterSource(handle) {
1339
- const record = records.get(handle);
1340
- if (record) {
1341
- record.disposed = true;
1342
- pending.delete(record);
1343
- record.clear();
1344
- records.delete(handle);
1345
- }
1346
- },
1347
- source(handle) {
1348
- assertActive();
1349
- const source = records.get(handle);
1350
- if (!source || source.disposed) throw new Error("Unknown, disposed, or foreign projection source.");
1351
- return source;
1352
- },
1353
- addNode(node) {
1354
- nodes.add(node);
1355
- node.sources.forEach((source) => source.consumers.add(node));
1356
- },
1357
- order: () => sequence++,
1358
- initialize(run) {
1359
- assertIdle();
1360
- phase = "compute";
1361
- lock(true);
1362
- try {
1363
- return run();
1364
- } finally {
1365
- lock(false);
1366
- phase = "idle";
1367
- }
1368
- },
1369
- batch,
1370
- batchContext: () => activeBatch,
1371
- dispose() {
1372
- if (disposed) return;
1373
- assertIdle();
1374
- disposed = true;
1375
- const failures = [];
1376
- cleanups.forEach((cleanup) => {
1377
- try {
1378
- cleanup();
1379
- } catch (error) {
1380
- failures.push(error);
1381
- }
1382
- });
1383
- cleanups.clear();
1384
- nodes.forEach((node) => {
1385
- node.disposed = true;
1386
- node.release();
1387
- node.consumers.clear();
1388
- });
1389
- records.forEach((source) => {
1390
- source.disposed = true;
1391
- source.consumers.clear();
1392
- source.clear();
1393
- });
1394
- nodes.clear();
1395
- records.clear();
1396
- pending.clear();
1397
- heap.length = 0;
1398
- completed.length = 0;
1399
- queued.clear();
1400
- emissions.clear();
1401
- guards.clear();
1402
- errors.length = 0;
1403
- reportingFailures = [];
1404
- if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
1405
- }
1406
- };
1407
- };
1408
- const assertSynchronous = (value) => {
1409
- if (value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") throw new TypeError("Projection callbacks must be synchronous.");
1410
- };
1411
- const assertScope = (active) => {
1412
- if (!active()) throw new Error("Projection reader or writer is no longer active.");
1413
- };
1414
- //#endregion
1415
1107
  //#region core/src/projection/source.ts
1416
1108
  const createSources = (scheduler) => {
1417
1109
  const documents = /* @__PURE__ */ new Map();
@@ -1464,7 +1156,7 @@ const createSources = (scheduler) => {
1464
1156
  nextBaselineIds = Object.freeze([]);
1465
1157
  },
1466
1158
  context: (active) => {
1467
- assertScope(active);
1159
+ require_advanced.assertScope(active);
1468
1160
  if (state.disposed) throw new Error("Document has been disposed.");
1469
1161
  const context = {
1470
1162
  state,
@@ -1474,16 +1166,16 @@ const createSources = (scheduler) => {
1474
1166
  const snapshots = collection ? /* @__PURE__ */ new Map() : void 0;
1475
1167
  const read = collection ? Object.freeze({
1476
1168
  get: (key) => {
1477
- assertScope(active);
1169
+ require_advanced.assertScope(active);
1478
1170
  if (!snapshots.has(key)) snapshots.set(key, require_scope.snapshot(rawRead.get(key)));
1479
1171
  return snapshots.get(key);
1480
1172
  },
1481
1173
  has: (key) => {
1482
- assertScope(active);
1174
+ require_advanced.assertScope(active);
1483
1175
  return rawRead.has(key);
1484
1176
  },
1485
1177
  ids: () => {
1486
- assertScope(active);
1178
+ require_advanced.assertScope(active);
1487
1179
  return rawRead.ids();
1488
1180
  }
1489
1181
  }) : require_scope.createAccess(context);
@@ -1623,7 +1315,7 @@ const createSources = (scheduler) => {
1623
1315
  flush: scheduler.flush,
1624
1316
  dispose: () => {
1625
1317
  bindings.forEach(({ record }) => {
1626
- 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."));
1627
1319
  scheduler.capture(record);
1628
1320
  });
1629
1321
  scheduler.run();
@@ -1653,7 +1345,7 @@ const createSources = (scheduler) => {
1653
1345
  reset = false;
1654
1346
  },
1655
1347
  context: (active) => {
1656
- assertScope(active);
1348
+ require_advanced.assertScope(active);
1657
1349
  return Object.freeze({
1658
1350
  kind: "value",
1659
1351
  value,
@@ -1685,6 +1377,149 @@ const createSources = (scheduler) => {
1685
1377
  }
1686
1378
  };
1687
1379
  };
1380
+ const collectionInput = (initial) => {
1381
+ const values = new Map(initial);
1382
+ const touched = /* @__PURE__ */ new Map();
1383
+ let beforeIds;
1384
+ let revision = 0;
1385
+ let cause;
1386
+ const handle = Object.freeze({ kind: "collection" });
1387
+ const change = () => {
1388
+ const added = [];
1389
+ const updated = [];
1390
+ const removed = [];
1391
+ for (const [key, before] of touched) {
1392
+ const afterPresent = values.has(key);
1393
+ if (!before.present && afterPresent) added.push({
1394
+ key,
1395
+ after: values.get(key)
1396
+ });
1397
+ else if (before.present && !afterPresent) removed.push({
1398
+ key,
1399
+ before: before.value
1400
+ });
1401
+ else if (before.present && afterPresent && !Object.is(before.value, values.get(key))) updated.push({
1402
+ key,
1403
+ before: before.value,
1404
+ after: values.get(key)
1405
+ });
1406
+ }
1407
+ let order;
1408
+ if (beforeIds) {
1409
+ const afterIds = [...values.keys()];
1410
+ const beforeKeys = new Set(beforeIds);
1411
+ const afterKeys = new Set(afterIds);
1412
+ const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1413
+ const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1414
+ if (beforeCommon.some((key, index) => key !== afterCommon[index])) order = Object.freeze({
1415
+ before: beforeIds,
1416
+ after: Object.freeze(afterIds)
1417
+ });
1418
+ }
1419
+ if (!added.length && !updated.length && !removed.length && !order) return void 0;
1420
+ return Object.freeze({
1421
+ kind: "incremental",
1422
+ added: Object.freeze(added),
1423
+ updated: Object.freeze(updated),
1424
+ removed: Object.freeze(removed),
1425
+ ...order ? { order } : {}
1426
+ });
1427
+ };
1428
+ const record = {
1429
+ consumers: /* @__PURE__ */ new Set(),
1430
+ disposed: false,
1431
+ fault: void 0,
1432
+ revision: () => revision,
1433
+ reset: () => false,
1434
+ shouldSettle: () => change() !== void 0,
1435
+ clear: () => {
1436
+ touched.clear();
1437
+ beforeIds = void 0;
1438
+ cause = void 0;
1439
+ },
1440
+ context: (active) => {
1441
+ require_advanced.assertScope(active);
1442
+ const read = Object.freeze({
1443
+ get: (key) => {
1444
+ require_advanced.assertScope(active);
1445
+ return values.get(key);
1446
+ },
1447
+ has: (key) => {
1448
+ require_advanced.assertScope(active);
1449
+ return values.has(key);
1450
+ },
1451
+ ids: () => {
1452
+ require_advanced.assertScope(active);
1453
+ return Object.freeze([...values.keys()]);
1454
+ }
1455
+ });
1456
+ return Object.freeze({
1457
+ kind: "collection",
1458
+ read,
1459
+ change: change(),
1460
+ revision,
1461
+ cause
1462
+ });
1463
+ }
1464
+ };
1465
+ scheduler.register(handle, record);
1466
+ return {
1467
+ source: handle,
1468
+ update(run) {
1469
+ scheduler.assertIdle();
1470
+ const staged = /* @__PURE__ */ new Map();
1471
+ let active = true;
1472
+ const draft = Object.freeze({
1473
+ get: (key) => {
1474
+ require_advanced.assertScope(() => active);
1475
+ const entry = staged.get(key);
1476
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
1477
+ },
1478
+ has: (key) => {
1479
+ require_advanced.assertScope(() => active);
1480
+ const entry = staged.get(key);
1481
+ return entry ? entry.present : values.has(key);
1482
+ },
1483
+ set: (key, value) => {
1484
+ require_advanced.assertScope(() => active);
1485
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1486
+ staged.set(key, {
1487
+ present: true,
1488
+ value
1489
+ });
1490
+ },
1491
+ remove: (key) => {
1492
+ require_advanced.assertScope(() => active);
1493
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1494
+ staged.set(key, { present: false });
1495
+ }
1496
+ });
1497
+ try {
1498
+ require_advanced.assertSynchronous(run(draft));
1499
+ } finally {
1500
+ active = false;
1501
+ }
1502
+ let changed = false;
1503
+ for (const [key, entry] of staged) {
1504
+ const present = values.has(key);
1505
+ if (entry.present ? present && Object.is(values.get(key), entry.value) : !present) continue;
1506
+ if (!touched.has(key)) touched.set(key, {
1507
+ present,
1508
+ value: values.get(key)
1509
+ });
1510
+ if (!beforeIds && present !== entry.present) beforeIds = Object.freeze([...values.keys()]);
1511
+ if (entry.present) values.set(key, entry.value);
1512
+ else values.delete(key);
1513
+ changed = true;
1514
+ }
1515
+ if (!changed) return;
1516
+ revision++;
1517
+ cause = scheduler.batchContext()?.cause ?? cause;
1518
+ scheduler.capture(record);
1519
+ scheduler.run();
1520
+ }
1521
+ };
1522
+ };
1688
1523
  const externalValueSource = (port, equal) => {
1689
1524
  scheduler.assertIdle();
1690
1525
  const old = externalSources.get(port);
@@ -1692,7 +1527,7 @@ const createSources = (scheduler) => {
1692
1527
  const input = valueSource(port.current(), equal);
1693
1528
  const fail = (cause) => {
1694
1529
  const record = scheduler.source(input.source);
1695
- record.fault = new ProjectionError("source", "external source", [record.revision()], cause);
1530
+ record.fault = new require_advanced.ProjectionError("source", "external source", [record.revision()], cause);
1696
1531
  scheduler.capture(record);
1697
1532
  };
1698
1533
  let unsubscribe;
@@ -1748,18 +1583,18 @@ const createSources = (scheduler) => {
1748
1583
  transitionKeys.clear();
1749
1584
  },
1750
1585
  context: (active) => {
1751
- assertScope(active);
1586
+ require_advanced.assertScope(active);
1752
1587
  const currentRead = Object.freeze({
1753
1588
  get: (key) => {
1754
- assertScope(active);
1589
+ require_advanced.assertScope(active);
1755
1590
  return read.get(key);
1756
1591
  },
1757
1592
  has: (key) => {
1758
- assertScope(active);
1593
+ require_advanced.assertScope(active);
1759
1594
  return read.has(key);
1760
1595
  },
1761
1596
  ids: () => {
1762
- assertScope(active);
1597
+ require_advanced.assertScope(active);
1763
1598
  return read.ids();
1764
1599
  }
1765
1600
  });
@@ -1774,7 +1609,7 @@ const createSources = (scheduler) => {
1774
1609
  };
1775
1610
  scheduler.register(handle, record);
1776
1611
  const fail = (error) => {
1777
- record.fault = new ProjectionError("source", "external collection source", [revision], error);
1612
+ record.fault = new require_advanced.ProjectionError("source", "external collection source", [revision], error);
1778
1613
  scheduler.capture(record);
1779
1614
  };
1780
1615
  const deriveChange = () => {
@@ -1877,9 +1712,10 @@ const createSources = (scheduler) => {
1877
1712
  set
1878
1713
  });
1879
1714
  },
1715
+ collectionInput,
1880
1716
  fromReadable: (readable, options) => {
1881
1717
  scheduler.assertIdle();
1882
- 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.");
1883
1719
  const equal = options?.isEqual ?? Object.is;
1884
1720
  let bindings = readables.get(readable);
1885
1721
  const old = bindings?.get(equal);
@@ -1905,7 +1741,7 @@ const createSources = (scheduler) => {
1905
1741
  } catch (cause) {
1906
1742
  for (const member of members.values()) {
1907
1743
  const record = scheduler.source(member.source);
1908
- record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
1744
+ record.fault = new require_advanced.ProjectionError("source", "external readable", [record.revision()], cause);
1909
1745
  scheduler.capture(record);
1910
1746
  }
1911
1747
  if (settle) scheduler.run();
@@ -1915,7 +1751,7 @@ const createSources = (scheduler) => {
1915
1751
  member.accept(value);
1916
1752
  } catch (cause) {
1917
1753
  const record = scheduler.source(member.source);
1918
- record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
1754
+ record.fault = new require_advanced.ProjectionError("source", "external readable", [record.revision()], cause);
1919
1755
  scheduler.capture(record);
1920
1756
  }
1921
1757
  if (settle) scheduler.run();
@@ -1933,7 +1769,7 @@ const createSources = (scheduler) => {
1933
1769
  dispose: () => {
1934
1770
  for (const member of members.values()) {
1935
1771
  const record = scheduler.source(member.source);
1936
- 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."));
1937
1773
  scheduler.capture(record);
1938
1774
  }
1939
1775
  scheduler.run();
@@ -1970,6 +1806,7 @@ const createNode = (scheduler, spec, behavior) => {
1970
1806
  const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
1971
1807
  const order = scheduler.order();
1972
1808
  const node = {
1809
+ owner: void 0,
1973
1810
  ...behavior,
1974
1811
  publish: () => {
1975
1812
  if (node.fault) behavior.clear();
@@ -1992,15 +1829,16 @@ const createNode = (scheduler, spec, behavior) => {
1992
1829
  }
1993
1830
  }
1994
1831
  };
1832
+ node.owner = node;
1995
1833
  scheduler.initialize(() => {
1996
1834
  const invalid = node.sources.find((source) => source.fault || source.disposed);
1997
- if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
1835
+ if (invalid) throw invalid.fault ?? new require_advanced.ProjectionDisposedError();
1998
1836
  node.evaluate(true);
1999
1837
  node.publish();
2000
1838
  node.clear();
2001
1839
  });
2002
1840
  const check = () => {
2003
- if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
1841
+ if (node.disposed || !scheduler.active) throw new require_advanced.ProjectionDisposedError();
2004
1842
  if (node.fault) throw node.fault;
2005
1843
  };
2006
1844
  return {
@@ -2032,17 +1870,17 @@ const createValue = (scheduler, spec, options) => {
2032
1870
  require_issue.profile.materialized.rebuilt();
2033
1871
  instance = void 0;
2034
1872
  const built = spec.build(sources);
2035
- assertSynchronous(built);
1873
+ require_advanced.assertSynchronous(built);
2036
1874
  instance = built;
2037
1875
  candidate = built.value;
2038
1876
  } else {
2039
1877
  require_issue.profile.materialized.updated();
2040
1878
  const result = instance.update(sources);
2041
- assertSynchronous(result);
1879
+ require_advanced.assertSynchronous(result);
2042
1880
  if (result.kind === "rebuild") {
2043
1881
  instance = void 0;
2044
1882
  const built = spec.build(sources);
2045
- assertSynchronous(built);
1883
+ require_advanced.assertSynchronous(built);
2046
1884
  instance = built;
2047
1885
  candidate = built.value;
2048
1886
  build = true;
@@ -2054,7 +1892,7 @@ const createValue = (scheduler, spec, options) => {
2054
1892
  return changed;
2055
1893
  },
2056
1894
  context: (active) => {
2057
- assertScope(active);
1895
+ require_advanced.assertScope(active);
2058
1896
  return Object.freeze({
2059
1897
  kind: "value",
2060
1898
  value: next,
@@ -2108,7 +1946,7 @@ const createValue = (scheduler, spec, options) => {
2108
1946
  return handle;
2109
1947
  };
2110
1948
  //#endregion
2111
- //#region core/src/projection/collection.ts
1949
+ //#region core/src/projection/collection-state.ts
2112
1950
  const height = (node) => node?.height ?? 0;
2113
1951
  const makeTreeNode = (key, value, left, right) => Object.freeze({
2114
1952
  key,
@@ -2174,214 +2012,197 @@ const treeFromSorted = (entries, start = 0, end = entries.length) => {
2174
2012
  const [key, value] = entries[middle];
2175
2013
  return makeTreeNode(key, value, treeFromSorted(entries, start, middle), treeFromSorted(entries, middle + 1, end));
2176
2014
  };
2177
- 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 = () => {
2178
2022
  const values = /* @__PURE__ */ new Map();
2179
2023
  let ids = Object.freeze([]);
2180
2024
  let staged = /* @__PURE__ */ new Map();
2181
2025
  let nextIds = ids;
2182
2026
  let change;
2183
- let instance;
2184
2027
  let initialized = false;
2185
2028
  let reset = false;
2186
2029
  let revision = 0;
2187
2030
  let publishedRoot;
2188
- let cause;
2189
- const listeners = /* @__PURE__ */ new Set();
2190
- const changedKeys = /* @__PURE__ */ new Set();
2031
+ let changedKeys = /* @__PURE__ */ new Set();
2191
2032
  let orderChanged = false;
2192
- const readPublished = (root, publishedIds) => Object.freeze({
2193
- get: (key) => {
2194
- owner.check();
2195
- return treeGet(root, key);
2196
- },
2197
- has: (key) => {
2198
- owner.check();
2199
- return treeHas(root, key);
2200
- },
2201
- ids: () => {
2202
- owner.check();
2203
- return publishedIds;
2204
- }
2205
- });
2206
- const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
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);
2207
2037
  const getNext = (key) => {
2208
2038
  const entry = staged.get(key);
2209
- 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);
2210
2040
  };
2211
- const owner = createNode(scheduler, spec, {
2212
- evaluate: (sources, build, active) => {
2213
- cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
2214
- staged = /* @__PURE__ */ new Map();
2215
- nextIds = ids;
2216
- change = void 0;
2217
- changedKeys.clear();
2218
- let explicitOrder;
2219
- let cleared = false;
2220
- const nextHas = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
2221
- const nextGet = (key) => {
2222
- const entry = staged.get(key);
2223
- return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
2224
- };
2225
- const deriveIds = () => {
2226
- const result = cleared ? [] : ids.filter(nextHas);
2227
- for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
2228
- return result;
2229
- };
2230
- const read = (next) => ({
2231
- get: (key) => {
2232
- assertScope(active);
2233
- return next ? nextGet(key) : values.get(key);
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
+ });
2234
2079
  },
2235
- has: (key) => {
2236
- assertScope(active);
2237
- return next ? nextHas(key) : values.has(key);
2080
+ remove: (key) => {
2081
+ require_advanced.assertScope(active);
2082
+ staged.set(key, { present: false });
2238
2083
  },
2239
- ids: () => {
2240
- assertScope(active);
2241
- return next ? Object.freeze(explicitOrder ? explicitOrder.slice() : deriveIds()) : ids;
2242
- }
2243
- });
2244
- const input = {
2245
- sources,
2246
- previous: read(false),
2247
- next: read(true),
2248
- output: {
2249
- set: (key, value) => {
2250
- assertScope(active);
2251
- if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
2252
- staged.set(key, {
2253
- present: true,
2254
- value
2255
- });
2256
- },
2257
- remove: (key) => {
2258
- assertScope(active);
2259
- staged.set(key, { present: false });
2260
- },
2261
- order: (order) => {
2262
- assertScope(active);
2263
- explicitOrder = order.slice();
2264
- }
2084
+ order: (order) => {
2085
+ require_advanced.assertScope(active);
2086
+ explicitOrder = Object.freeze([...order]);
2265
2087
  }
2266
- };
2267
- if (build || !instance) {
2268
- require_issue.profile.materialized.rebuilt();
2269
- instance = void 0;
2270
- cleared = true;
2271
- const built = spec.build(input);
2272
- assertSynchronous(built);
2273
- instance = built;
2274
- } else {
2275
- require_issue.profile.materialized.updated();
2276
- const result = instance.update(input);
2277
- assertSynchronous(result);
2278
- if (result?.kind === "rebuild") {
2279
- staged.clear();
2280
- cleared = true;
2281
- explicitOrder = void 0;
2282
- instance = void 0;
2283
- const built = spec.build(input);
2284
- assertSynchronous(built);
2285
- instance = built;
2286
- build = true;
2287
- }
2288
- }
2289
- if (cleared) {
2290
- for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
2291
2088
  }
2292
- const added = /* @__PURE__ */ new Set();
2293
- const removed = /* @__PURE__ */ new Set();
2294
- const updated = /* @__PURE__ */ new Set();
2295
- require_issue.profile.projection("touchedKeys", staged.size);
2296
- for (const [key, entry] of staged) {
2297
- const existed = values.has(key);
2298
- if (entry.present) if (!existed) added.add(key);
2299
- else if (!(spec.isEqual ?? Object.is)(values.get(key), entry.value)) updated.add(key);
2300
- else staged.delete(key);
2301
- else if (existed) removed.add(key);
2302
- else staged.delete(key);
2303
- }
2304
- if (explicitOrder || added.size || removed.size) {
2305
- const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
2306
- if (new Set(order).size !== order.length || order.length !== values.size + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
2307
- nextIds = ids.length === order.length && ids.every((key, i) => key === order[i]) ? ids : Object.freeze(order.slice());
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;
2308
2126
  }
2309
- orderChanged = nextIds !== ids;
2310
- [
2311
- ...added,
2312
- ...removed,
2313
- ...updated
2314
- ].forEach((key) => changedKeys.add(key));
2315
- require_issue.profile.projection("changedKeys", changedKeys.size);
2316
- const addedEntries = [];
2317
- const updatedEntries = [];
2318
- const removedEntries = [];
2319
- for (const key of changedKeys) {
2320
- const beforePresent = values.has(key);
2321
- const afterPresent = hasNext(key);
2322
- if (beforePresent === afterPresent) {
2323
- if (!beforePresent) continue;
2324
- if ((spec.isEqual ?? Object.is)(values.get(key), getNext(key))) continue;
2325
- }
2326
- if (!beforePresent) addedEntries.push({
2327
- key,
2328
- after: getNext(key)
2329
- });
2330
- else if (!afterPresent) removedEntries.push({
2331
- key,
2332
- before: values.get(key)
2333
- });
2334
- else updatedEntries.push({
2335
- key,
2336
- before: values.get(key),
2337
- after: getNext(key)
2338
- });
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);
2339
2148
  }
2340
- if (build) change = Object.freeze({ kind: "reset" });
2341
- else if (addedEntries.length || updatedEntries.length || removedEntries.length || orderChanged) {
2342
- let commonOrderChanged = false;
2343
- if (orderChanged) {
2344
- const before = ids.filter((key) => !removed.has(key));
2345
- const after = nextIds.filter((key) => !added.has(key));
2346
- commonOrderChanged = before.some((key, i) => after[i] !== key);
2347
- }
2348
- change = Object.freeze({
2349
- kind: "incremental",
2350
- added: Object.freeze(addedEntries),
2351
- updated: Object.freeze(updatedEntries),
2352
- removed: Object.freeze(removedEntries),
2353
- ...commonOrderChanged ? { order: Object.freeze({
2354
- before: Object.freeze([...ids]),
2355
- after: Object.freeze([...nextIds])
2356
- }) } : {}
2357
- });
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;
2358
2177
  }
2359
- reset = build;
2360
- return change !== void 0;
2361
- },
2362
- context: (active) => {
2363
- const input = {
2364
- kind: "collection",
2365
- read: {
2366
- get: (key) => {
2367
- assertScope(active);
2368
- return getNext(key);
2369
- },
2370
- has: (key) => {
2371
- assertScope(active);
2372
- return hasNext(key);
2373
- },
2374
- ids: () => {
2375
- assertScope(active);
2376
- return nextIds;
2377
- }
2378
- },
2379
- change,
2380
- revision: revision + (change ? 1 : 0),
2381
- cause
2382
- };
2383
- return Object.freeze(input);
2384
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,
2385
2206
  revision: () => revision,
2386
2207
  reset: () => reset,
2387
2208
  publish: () => {
@@ -2393,14 +2214,11 @@ const createCollection = (scheduler, spec) => {
2393
2214
  } else for (const [key, entry] of staged) publishedRoot = entry.present ? treeSet(publishedRoot, key, entry.value) : treeRemove(publishedRoot, key);
2394
2215
  for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
2395
2216
  else values.delete(key);
2396
- if (change) {
2397
- if (initialized) revision++;
2398
- }
2217
+ if (change && initialized) revision++;
2399
2218
  ids = nextIds;
2400
2219
  initialized = true;
2401
2220
  },
2402
2221
  emit: (call) => {
2403
- require_issue.profile.materialized.notification();
2404
2222
  const resetEvent = Object.freeze({ kind: "reset" });
2405
2223
  Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
2406
2224
  },
@@ -2410,8 +2228,9 @@ const createCollection = (scheduler, spec) => {
2410
2228
  change = void 0;
2411
2229
  reset = false;
2412
2230
  changedKeys.clear();
2413
- cause = void 0;
2231
+ explicitOrder = void 0;
2414
2232
  orderChanged = false;
2233
+ cleared = false;
2415
2234
  },
2416
2235
  release: () => {
2417
2236
  values.clear();
@@ -2419,6 +2238,66 @@ const createCollection = (scheduler, spec) => {
2419
2238
  publishedRoot = void 0;
2420
2239
  listeners.clear();
2421
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();
2422
2301
  instance = void 0;
2423
2302
  }
2424
2303
  });
@@ -2426,34 +2305,186 @@ const createCollection = (scheduler, spec) => {
2426
2305
  kind: "collection",
2427
2306
  current: () => {
2428
2307
  owner.check();
2429
- return readPublished(publishedRoot, ids);
2308
+ return state.current(owner.check);
2430
2309
  },
2431
2310
  revision: () => {
2432
2311
  owner.check();
2433
- return revision;
2312
+ return state.revision();
2434
2313
  },
2435
2314
  subscribe: (listener) => {
2436
2315
  owner.check();
2437
- listeners.add(listener);
2438
- return () => {
2439
- listeners.delete(listener);
2440
- };
2316
+ state.subscribe(listener);
2317
+ return () => state.unsubscribe(listener);
2441
2318
  }
2442
2319
  };
2443
2320
  owner.install(handle);
2444
2321
  return Object.freeze(handle);
2445
2322
  };
2446
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
2447
2478
  //#region core/src/projection/graph.ts
2448
2479
  const createProjectionGraph = (options) => {
2449
- const scheduler = createScheduler(options.onError);
2480
+ const scheduler = require_advanced.createScheduler(options.onError);
2450
2481
  const sources = createSources(scheduler);
2451
2482
  const value = (spec, options) => createValue(scheduler, spec, options);
2452
2483
  const map = (source, mapper, options) => {
2453
2484
  sources.assertCollection(source);
2454
2485
  const calculate = (id, entry) => {
2455
2486
  const value = mapper(id, entry);
2456
- assertSynchronous(value);
2487
+ require_advanced.assertSynchronous(value);
2457
2488
  return value;
2458
2489
  };
2459
2490
  return createCollection(scheduler, {
@@ -2497,11 +2528,16 @@ const createProjectionGraph = (options) => {
2497
2528
  fromSource: (source, options) => sources.fromSource(source, options),
2498
2529
  fromCollectionSource: (source) => sources.fromCollectionSource(source),
2499
2530
  input: sources.input,
2531
+ collectionInput: sources.collectionInput,
2500
2532
  fromReadable: sources.fromReadable,
2501
2533
  value,
2502
2534
  collection: (spec) => createCollection(scheduler, spec),
2535
+ group: (spec) => createCollectionGroup(scheduler, spec),
2503
2536
  map,
2504
2537
  batch: scheduler.batch,
2538
+ assertIdle: scheduler.assertIdle,
2539
+ releaseNode: scheduler.releaseNode,
2540
+ releaseInput: scheduler.unregisterSource,
2505
2541
  dispose: () => {
2506
2542
  try {
2507
2543
  scheduler.dispose();
@@ -2671,16 +2707,52 @@ const sourceOf = (instance) => instance;
2671
2707
  const createProjectionRuntime = (options) => {
2672
2708
  const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
2673
2709
  const materialized = /* @__PURE__ */ new WeakMap();
2674
- const materialize = (projection) => {
2710
+ const groups = /* @__PURE__ */ new WeakMap();
2711
+ let disposed = false;
2712
+ const scopes = /* @__PURE__ */ new Set();
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) => {
2737
+ const owner = require_advanced.ownerOf(projection);
2738
+ if (owner) {
2739
+ if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
2740
+ if (!requester.active) throw new require_advanced.ProjectionDisposedError();
2741
+ }
2675
2742
  const old = materialized.get(projection);
2676
2743
  if (old) return old.node;
2677
- const definition = require_definition.definitionOf(projection);
2744
+ const definition = require_advanced.definitionOf(projection);
2745
+ if (definition.kind === "incremental-group-output") return materializeGroup(definition.group, requester).nodes[definition.output];
2678
2746
  let instance;
2679
- let setter;
2747
+ let write;
2680
2748
  switch (definition.kind) {
2681
2749
  case "input": {
2682
2750
  const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
2683
- setter = input.set;
2751
+ write = {
2752
+ kind: "value-input",
2753
+ source: input.source,
2754
+ set: input.set
2755
+ };
2684
2756
  instance = runtime.value({
2685
2757
  sources: { input: input.source },
2686
2758
  build: (values) => ({
@@ -2693,6 +2765,16 @@ const createProjectionRuntime = (options) => {
2693
2765
  });
2694
2766
  break;
2695
2767
  }
2768
+ case "collection-input": {
2769
+ const source = runtime.collectionInput(definition.initial);
2770
+ write = {
2771
+ kind: "collection-input",
2772
+ source: source.source,
2773
+ update: source.update
2774
+ };
2775
+ instance = runtime.map(source.source, (_key, value) => value);
2776
+ break;
2777
+ }
2696
2778
  case "readable": {
2697
2779
  const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
2698
2780
  instance = runtime.value({
@@ -2764,7 +2846,7 @@ const createProjectionRuntime = (options) => {
2764
2846
  break;
2765
2847
  }
2766
2848
  case "derive": {
2767
- const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency))]));
2849
+ const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency, owner))]));
2768
2850
  instance = runtime.value({
2769
2851
  sources,
2770
2852
  build: (values) => ({
@@ -2778,7 +2860,7 @@ const createProjectionRuntime = (options) => {
2778
2860
  break;
2779
2861
  }
2780
2862
  case "incremental-value": {
2781
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2863
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2782
2864
  instance = runtime.value({
2783
2865
  sources,
2784
2866
  build: definition.build
@@ -2786,7 +2868,7 @@ const createProjectionRuntime = (options) => {
2786
2868
  break;
2787
2869
  }
2788
2870
  case "incremental-collection": {
2789
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2871
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2790
2872
  instance = runtime.collection({
2791
2873
  sources,
2792
2874
  build: definition.build,
@@ -2796,14 +2878,19 @@ const createProjectionRuntime = (options) => {
2796
2878
  break;
2797
2879
  }
2798
2880
  }
2799
- materialized.set(projection, {
2881
+ const record = write ? {
2800
2882
  node: instance,
2801
- set: setter
2802
- });
2883
+ ...write
2884
+ } : {
2885
+ kind: "projection",
2886
+ node: instance
2887
+ };
2888
+ materialized.set(projection, record);
2889
+ if (owner) owner.materialized.push(projection);
2803
2890
  return instance;
2804
2891
  };
2805
- const get = (projection) => {
2806
- const instance = materialize(projection);
2892
+ const get = (projection, requester) => {
2893
+ const instance = materialize(projection, requester);
2807
2894
  const current = instance.current();
2808
2895
  if (instance.kind === "collection") {
2809
2896
  const record = materialized.get(projection) ?? materialized.get(instance);
@@ -2831,11 +2918,11 @@ const createProjectionRuntime = (options) => {
2831
2918
  }
2832
2919
  return current;
2833
2920
  };
2834
- function readable(projection, selector, equality = Object.is) {
2921
+ const makeReadable = (projection, selector, equality = Object.is, requester) => {
2835
2922
  if (!selector) return Object.freeze({
2836
- current: () => get(projection),
2837
- revision: () => materialize(projection).revision(),
2838
- subscribe: (listener) => materialize(projection).subscribe(listener)
2923
+ current: () => get(projection, requester),
2924
+ revision: () => materialize(projection, requester).revision(),
2925
+ subscribe: (listener) => materialize(projection, requester).subscribe(listener)
2839
2926
  });
2840
2927
  let initialized = false;
2841
2928
  let value;
@@ -2850,7 +2937,7 @@ const createProjectionRuntime = (options) => {
2850
2937
  let unsubscribeSource;
2851
2938
  const listeners = /* @__PURE__ */ new Set();
2852
2939
  const updateCollectionSnapshot = () => {
2853
- const current = get(projection);
2940
+ const current = get(projection, requester);
2854
2941
  previousCollection = isMapLike(current) ? current : void 0;
2855
2942
  };
2856
2943
  const evaluate = () => {
@@ -2864,7 +2951,7 @@ const createProjectionRuntime = (options) => {
2864
2951
  let source;
2865
2952
  let next;
2866
2953
  try {
2867
- source = get(projection);
2954
+ source = get(projection, requester);
2868
2955
  next = selector(source);
2869
2956
  } finally {
2870
2957
  activeTracker = previousTracker;
@@ -2881,30 +2968,30 @@ const createProjectionRuntime = (options) => {
2881
2968
  }
2882
2969
  selection = nextSelection;
2883
2970
  initialized = true;
2884
- sourceRevision = materialize(projection).revision();
2971
+ sourceRevision = materialize(projection, requester).revision();
2885
2972
  updateCollectionSnapshot();
2886
2973
  return changed;
2887
2974
  };
2888
2975
  const ensureCurrent = () => {
2889
- const revision = materialize(projection).revision();
2976
+ const revision = materialize(projection, requester).revision();
2890
2977
  if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
2891
2978
  return value;
2892
2979
  };
2893
2980
  const onProjectionChange = (incoming) => {
2894
2981
  let change = incoming;
2895
2982
  if (!change && previousCollection) {
2896
- const current = get(projection);
2983
+ const current = get(projection, requester);
2897
2984
  if (isMapLike(current)) {
2898
2985
  change = mapChange(previousCollection, current);
2899
2986
  previousCollection = current;
2900
2987
  if (!change) {
2901
- sourceRevision = materialize(projection).revision();
2988
+ sourceRevision = materialize(projection, requester).revision();
2902
2989
  return;
2903
2990
  }
2904
2991
  } else previousCollection = void 0;
2905
2992
  }
2906
2993
  if (!selectionAffects(selection, change)) {
2907
- sourceRevision = materialize(projection).revision();
2994
+ sourceRevision = materialize(projection, requester).revision();
2908
2995
  updateCollectionSnapshot();
2909
2996
  return;
2910
2997
  }
@@ -2912,12 +2999,12 @@ const createProjectionRuntime = (options) => {
2912
2999
  const changed = evaluate();
2913
3000
  if (unsubscribeSource && !sameSelection(previousSelection, selection)) {
2914
3001
  unsubscribeSource();
2915
- unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
3002
+ unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
2916
3003
  }
2917
3004
  if (changed) Array.from(listeners).forEach((listener) => listener());
2918
3005
  };
2919
3006
  const installSource = () => {
2920
- if (!unsubscribeSource) unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
3007
+ if (!unsubscribeSource) unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
2921
3008
  };
2922
3009
  return Object.freeze({
2923
3010
  current: ensureCurrent,
@@ -2938,18 +3025,150 @@ const createProjectionRuntime = (options) => {
2938
3025
  };
2939
3026
  }
2940
3027
  });
3028
+ };
3029
+ function readable(projection, selector, equality) {
3030
+ return makeReadable(projection, selector, equality);
2941
3031
  }
3032
+ const setInput = (target, value, requester) => {
3033
+ materialize(target, requester);
3034
+ const record = materialized.get(target);
3035
+ if (record?.kind !== "value-input") throw new TypeError("Projection is not a value input.");
3036
+ record.set(value);
3037
+ };
3038
+ const updateInput = (target, run, requester) => {
3039
+ materialize(target, requester);
3040
+ const record = materialized.get(target);
3041
+ if (record?.kind !== "collection-input") throw new TypeError("Projection is not a collection input.");
3042
+ record.update(run);
3043
+ };
3044
+ const createScope = () => {
3045
+ runtime.assertIdle();
3046
+ const state = {
3047
+ active: true,
3048
+ materialized: [],
3049
+ subscriptions: /* @__PURE__ */ new Set()
3050
+ };
3051
+ scopes.add(state);
3052
+ const assertActive = () => {
3053
+ if (!state.active) throw new require_advanced.ProjectionDisposedError();
3054
+ };
3055
+ const own = (projection) => {
3056
+ assertActive();
3057
+ const definition = require_advanced.definitionOf(projection);
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) : [];
3059
+ for (const dependency of dependencies) {
3060
+ const dependencyOwner = require_advanced.ownerOf(dependency);
3061
+ if (dependencyOwner && dependencyOwner !== state) throw new TypeError("A scope cannot depend on another scope.");
3062
+ }
3063
+ require_advanced.ownDefinition(projection, state);
3064
+ return projection;
3065
+ };
3066
+ const scopedInput = Object.assign((initial, equality) => own(require_advanced.input(initial, equality)), { collection: (initial) => own(require_advanced.input.collection(initial)) });
3067
+ const scopedDerive = (dependencies, compute, equality) => own(require_advanced.derive(dependencies, compute, equality));
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
+ });
3078
+ function scopeReadable(projection, selector, equality) {
3079
+ assertActive();
3080
+ materialize(projection, state);
3081
+ const source = makeReadable(projection, selector, equality, state);
3082
+ return Object.freeze({
3083
+ current: () => {
3084
+ assertActive();
3085
+ return source.current();
3086
+ },
3087
+ revision: () => {
3088
+ assertActive();
3089
+ return source.revision();
3090
+ },
3091
+ subscribe: (listener) => {
3092
+ assertActive();
3093
+ const stop = source.subscribe(listener);
3094
+ let subscribed = true;
3095
+ const unsubscribe = () => {
3096
+ if (!subscribed) return;
3097
+ subscribed = false;
3098
+ state.subscriptions.delete(unsubscribe);
3099
+ stop();
3100
+ };
3101
+ state.subscriptions.add(unsubscribe);
3102
+ return unsubscribe;
3103
+ }
3104
+ });
3105
+ }
3106
+ function scopeBatch(optionsOrCallback, maybeCallback) {
3107
+ assertActive();
3108
+ return typeof optionsOrCallback === "function" ? runtime.batch(optionsOrCallback) : runtime.batch(optionsOrCallback, maybeCallback);
3109
+ }
3110
+ return Object.freeze({
3111
+ input: scopedInput,
3112
+ derive: scopedDerive,
3113
+ incremental: scopedIncremental,
3114
+ get: (projection) => {
3115
+ assertActive();
3116
+ return get(projection, state);
3117
+ },
3118
+ readable: scopeReadable,
3119
+ set: (target, value) => {
3120
+ assertActive();
3121
+ setInput(target, value, state);
3122
+ },
3123
+ update: (target, run) => {
3124
+ assertActive();
3125
+ updateInput(target, run, state);
3126
+ },
3127
+ batch: scopeBatch,
3128
+ dispose: () => {
3129
+ if (!state.active) return;
3130
+ runtime.assertIdle();
3131
+ state.active = false;
3132
+ state.subscriptions.forEach((unsubscribe) => unsubscribe());
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
+ }
3141
+ const record = materialized.get(definition);
3142
+ if (!record) continue;
3143
+ runtime.releaseNode(record.node);
3144
+ if (record.kind !== "projection") runtime.releaseInput(record.source);
3145
+ materialized.delete(definition);
3146
+ }
3147
+ state.materialized.length = 0;
3148
+ scopes.delete(state);
3149
+ }
3150
+ });
3151
+ };
2942
3152
  const store = {
2943
3153
  get,
2944
3154
  readable,
2945
- set: ((input, value) => {
2946
- materialize(input);
2947
- const target = materialized.get(input);
2948
- if (!target?.set) throw new TypeError("Projection is not an input.");
2949
- target.set(value);
2950
- }),
3155
+ set: setInput,
3156
+ update: updateInput,
2951
3157
  batch: runtime.batch,
2952
- dispose: runtime.dispose
3158
+ scope: createScope,
3159
+ dispose: () => {
3160
+ if (disposed) return;
3161
+ runtime.assertIdle();
3162
+ disposed = true;
3163
+ for (const scope of scopes) {
3164
+ scope.active = false;
3165
+ scope.subscriptions.forEach((unsubscribe) => unsubscribe());
3166
+ scope.subscriptions.clear();
3167
+ scope.materialized.length = 0;
3168
+ }
3169
+ scopes.clear();
3170
+ runtime.dispose();
3171
+ }
2953
3172
  };
2954
3173
  return Object.freeze(store);
2955
3174
  };
@@ -2957,19 +3176,19 @@ const createProjectionRuntime = (options) => {
2957
3176
  exports.DocumentDisposedError = require_notification.DocumentDisposedError;
2958
3177
  exports.DocumentReentrancyError = require_notification.DocumentReentrancyError;
2959
3178
  exports.ParseError = require_scope.ParseError;
2960
- exports.ProjectionDisposedError = ProjectionDisposedError;
2961
- exports.ProjectionError = ProjectionError;
3179
+ exports.ProjectionDisposedError = require_advanced.ProjectionDisposedError;
3180
+ exports.ProjectionError = require_advanced.ProjectionError;
2962
3181
  exports.TransactionRejected = require_notification.TransactionRejected;
2963
3182
  exports.asReadable = asReadable;
2964
3183
  exports.createDocument = createDocument;
2965
3184
  exports.createProjectionRuntime = createProjectionRuntime;
2966
- exports.derive = require_definition.derive;
3185
+ exports.derive = require_advanced.derive;
2967
3186
  exports.field = field;
2968
- exports.input = require_definition.input;
3187
+ exports.input = require_advanced.input;
2969
3188
  exports.list = list;
2970
3189
  exports.map = map;
2971
3190
  exports.object = object;
2972
- exports.observe = require_definition.observe;
3191
+ exports.observe = require_advanced.observe;
2973
3192
  exports.optional = optional;
2974
3193
  exports.parse = require_scope.parse;
2975
3194
  exports.read = read;