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