@optimystic/db-core 0.14.1 → 0.16.3
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/src/blocks/block-store.d.ts +18 -1
- package/dist/src/blocks/block-store.d.ts.map +1 -1
- package/dist/src/blocks/helpers.d.ts +2 -2
- package/dist/src/blocks/helpers.d.ts.map +1 -1
- package/dist/src/blocks/helpers.js +2 -2
- package/dist/src/blocks/helpers.js.map +1 -1
- package/dist/src/btree/btree.d.ts +27 -6
- package/dist/src/btree/btree.d.ts.map +1 -1
- package/dist/src/btree/btree.js +104 -61
- package/dist/src/btree/btree.js.map +1 -1
- package/dist/src/chain/chain.d.ts.map +1 -1
- package/dist/src/chain/chain.js +11 -1
- package/dist/src/chain/chain.js.map +1 -1
- package/dist/src/cluster/index.d.ts +1 -0
- package/dist/src/cluster/index.d.ts.map +1 -1
- package/dist/src/cluster/index.js +1 -0
- package/dist/src/cluster/index.js.map +1 -1
- package/dist/src/cluster/membership.d.ts +49 -0
- package/dist/src/cluster/membership.d.ts.map +1 -0
- package/dist/src/cluster/membership.js +75 -0
- package/dist/src/cluster/membership.js.map +1 -0
- package/dist/src/cluster/structs.d.ts +50 -1
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/cluster/structs.js +9 -1
- package/dist/src/cluster/structs.js.map +1 -1
- package/dist/src/cohort-topic/addressing.d.ts +8 -2
- package/dist/src/cohort-topic/addressing.d.ts.map +1 -1
- package/dist/src/cohort-topic/addressing.js +11 -3
- package/dist/src/cohort-topic/addressing.js.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +13 -7
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +17 -6
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -1
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts +17 -0
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/replay-guard.js +33 -0
- package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -1
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts +7 -0
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/topic-budget.js +8 -2
- package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -1
- package/dist/src/cohort-topic/coldstart.d.ts +34 -14
- package/dist/src/cohort-topic/coldstart.d.ts.map +1 -1
- package/dist/src/cohort-topic/coldstart.js +25 -11
- package/dist/src/cohort-topic/coldstart.js.map +1 -1
- package/dist/src/cohort-topic/gossip/bus.d.ts +12 -1
- package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -1
- package/dist/src/cohort-topic/gossip/bus.js +27 -1
- package/dist/src/cohort-topic/gossip/bus.js.map +1 -1
- package/dist/src/cohort-topic/gossip/view.d.ts +8 -0
- package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -1
- package/dist/src/cohort-topic/gossip/view.js +17 -1
- package/dist/src/cohort-topic/gossip/view.js.map +1 -1
- package/dist/src/cohort-topic/member-engine.d.ts +10 -3
- package/dist/src/cohort-topic/member-engine.d.ts.map +1 -1
- package/dist/src/cohort-topic/member-engine.js +29 -14
- package/dist/src/cohort-topic/member-engine.js.map +1 -1
- package/dist/src/cohort-topic/membership/publisher.d.ts +14 -7
- package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/publisher.js +15 -27
- package/dist/src/cohort-topic/membership/publisher.js.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.d.ts +35 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.js +88 -3
- package/dist/src/cohort-topic/membership/verifier.js.map +1 -1
- package/dist/src/cohort-topic/promotion.d.ts +7 -0
- package/dist/src/cohort-topic/promotion.d.ts.map +1 -1
- package/dist/src/cohort-topic/promotion.js +16 -2
- package/dist/src/cohort-topic/promotion.js.map +1 -1
- package/dist/src/cohort-topic/registration/renewal.d.ts +18 -0
- package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -1
- package/dist/src/cohort-topic/registration/renewal.js +54 -0
- package/dist/src/cohort-topic/registration/renewal.js.map +1 -1
- package/dist/src/cohort-topic/registration/types.d.ts +12 -0
- package/dist/src/cohort-topic/registration/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/registration/types.js +14 -0
- package/dist/src/cohort-topic/registration/types.js.map +1 -1
- package/dist/src/cohort-topic/service.d.ts +4 -3
- package/dist/src/cohort-topic/service.d.ts.map +1 -1
- package/dist/src/cohort-topic/service.js +38 -12
- package/dist/src/cohort-topic/service.js.map +1 -1
- package/dist/src/cohort-topic/sig/payloads.d.ts +23 -7
- package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -1
- package/dist/src/cohort-topic/sig/payloads.js +22 -6
- package/dist/src/cohort-topic/sig/payloads.js.map +1 -1
- package/dist/src/cohort-topic/traffic.d.ts +6 -0
- package/dist/src/cohort-topic/traffic.d.ts.map +1 -1
- package/dist/src/cohort-topic/traffic.js +16 -1
- package/dist/src/cohort-topic/traffic.js.map +1 -1
- package/dist/src/cohort-topic/walk.d.ts +22 -7
- package/dist/src/cohort-topic/walk.d.ts.map +1 -1
- package/dist/src/cohort-topic/walk.js +78 -18
- package/dist/src/cohort-topic/walk.js.map +1 -1
- package/dist/src/cohort-topic/wire/codec.d.ts +3 -1
- package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/codec.js +7 -1
- package/dist/src/cohort-topic/wire/codec.js.map +1 -1
- package/dist/src/cohort-topic/wire/index.d.ts +1 -1
- package/dist/src/cohort-topic/wire/index.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/index.js +1 -1
- package/dist/src/cohort-topic/wire/index.js.map +1 -1
- package/dist/src/cohort-topic/wire/payloads.d.ts +5 -4
- package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/payloads.js +14 -5
- package/dist/src/cohort-topic/wire/payloads.js.map +1 -1
- package/dist/src/cohort-topic/wire/primitives.d.ts +65 -0
- package/dist/src/cohort-topic/wire/primitives.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/primitives.js +173 -0
- package/dist/src/cohort-topic/wire/primitives.js.map +1 -0
- package/dist/src/cohort-topic/wire/types.d.ts +132 -5
- package/dist/src/cohort-topic/wire/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.d.ts +15 -5
- package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.js +146 -123
- package/dist/src/cohort-topic/wire/validate.js.map +1 -1
- package/dist/src/collection/collection.d.ts +23 -12
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +111 -35
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/collection/struct.d.ts +37 -2
- package/dist/src/collection/struct.d.ts.map +1 -1
- package/dist/src/collection/struct.js +16 -1
- package/dist/src/collection/struct.js.map +1 -1
- package/dist/src/collections/tree/tree.d.ts +18 -1
- package/dist/src/collections/tree/tree.d.ts.map +1 -1
- package/dist/src/collections/tree/tree.js +36 -6
- package/dist/src/collections/tree/tree.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/log/log.d.ts +26 -3
- package/dist/src/log/log.d.ts.map +1 -1
- package/dist/src/log/log.js +34 -7
- package/dist/src/log/log.js.map +1 -1
- package/dist/src/log/struct.d.ts +5 -4
- package/dist/src/log/struct.d.ts.map +1 -1
- package/dist/src/log/struct.js.map +1 -1
- package/dist/src/matchmaking/wire.d.ts +2 -1
- package/dist/src/matchmaking/wire.d.ts.map +1 -1
- package/dist/src/matchmaking/wire.js +13 -92
- package/dist/src/matchmaking/wire.js.map +1 -1
- package/dist/src/network/i-key-network.d.ts +9 -0
- package/dist/src/network/i-key-network.d.ts.map +1 -1
- package/dist/src/network/i-repo.d.ts +8 -0
- package/dist/src/network/i-repo.d.ts.map +1 -1
- package/dist/src/network/struct.d.ts +10 -0
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/reactivity/push-state.d.ts.map +1 -1
- package/dist/src/reactivity/push-state.js +4 -33
- package/dist/src/reactivity/push-state.js.map +1 -1
- package/dist/src/reactivity/wire-validate.d.ts +7 -22
- package/dist/src/reactivity/wire-validate.d.ts.map +1 -1
- package/dist/src/reactivity/wire-validate.js +7 -73
- package/dist/src/reactivity/wire-validate.js.map +1 -1
- package/dist/src/reactivity/wire.js +3 -3
- package/dist/src/reactivity/wire.js.map +1 -1
- package/dist/src/testing/async-wait.d.ts +23 -0
- package/dist/src/testing/async-wait.d.ts.map +1 -0
- package/dist/src/testing/async-wait.js +42 -0
- package/dist/src/testing/async-wait.js.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +3 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/test-transactor.d.ts +44 -0
- package/dist/src/testing/test-transactor.d.ts.map +1 -0
- package/dist/src/testing/test-transactor.js +423 -0
- package/dist/src/testing/test-transactor.js.map +1 -0
- package/dist/src/transaction/actions-engine.d.ts +6 -3
- package/dist/src/transaction/actions-engine.d.ts.map +1 -1
- package/dist/src/transaction/actions-engine.js +8 -7
- package/dist/src/transaction/actions-engine.js.map +1 -1
- package/dist/src/transaction/coordinator.d.ts +43 -22
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +443 -178
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transaction/errors.d.ts +82 -0
- package/dist/src/transaction/errors.d.ts.map +1 -0
- package/dist/src/transaction/errors.js +93 -0
- package/dist/src/transaction/errors.js.map +1 -0
- package/dist/src/transaction/index.d.ts +6 -3
- package/dist/src/transaction/index.d.ts.map +1 -1
- package/dist/src/transaction/index.js +4 -2
- package/dist/src/transaction/index.js.map +1 -1
- package/dist/src/transaction/operations-hash.d.ts +119 -0
- package/dist/src/transaction/operations-hash.d.ts.map +1 -0
- package/dist/src/transaction/operations-hash.js +180 -0
- package/dist/src/transaction/operations-hash.js.map +1 -0
- package/dist/src/transaction/read-dependency-collector.d.ts +43 -0
- package/dist/src/transaction/read-dependency-collector.d.ts.map +1 -0
- package/dist/src/transaction/read-dependency-collector.js +69 -0
- package/dist/src/transaction/read-dependency-collector.js.map +1 -0
- package/dist/src/transaction/session.d.ts +12 -3
- package/dist/src/transaction/session.d.ts.map +1 -1
- package/dist/src/transaction/session.js +35 -9
- package/dist/src/transaction/session.js.map +1 -1
- package/dist/src/transaction/transaction.d.ts +162 -1
- package/dist/src/transaction/transaction.d.ts.map +1 -1
- package/dist/src/transaction/transaction.js +85 -3
- package/dist/src/transaction/transaction.js.map +1 -1
- package/dist/src/transaction/validator.d.ts +3 -11
- package/dist/src/transaction/validator.d.ts.map +1 -1
- package/dist/src/transaction/validator.js +69 -28
- package/dist/src/transaction/validator.js.map +1 -1
- package/dist/src/transactor/network-transactor.d.ts +35 -0
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +146 -40
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/dist/src/transactor/transactor-source.d.ts +19 -5
- package/dist/src/transactor/transactor-source.d.ts.map +1 -1
- package/dist/src/transactor/transactor-source.js +43 -11
- package/dist/src/transactor/transactor-source.js.map +1 -1
- package/dist/src/transform/atomic-proxy.d.ts +35 -5
- package/dist/src/transform/atomic-proxy.d.ts.map +1 -1
- package/dist/src/transform/atomic-proxy.js +39 -7
- package/dist/src/transform/atomic-proxy.js.map +1 -1
- package/dist/src/transform/cache-source.d.ts +36 -5
- package/dist/src/transform/cache-source.d.ts.map +1 -1
- package/dist/src/transform/cache-source.js +84 -8
- package/dist/src/transform/cache-source.js.map +1 -1
- package/dist/src/transform/helpers.d.ts.map +1 -1
- package/dist/src/transform/helpers.js +8 -2
- package/dist/src/transform/helpers.js.map +1 -1
- package/dist/src/transform/tracker.d.ts +17 -2
- package/dist/src/transform/tracker.d.ts.map +1 -1
- package/dist/src/transform/tracker.js +66 -11
- package/dist/src/transform/tracker.js.map +1 -1
- package/dist/src/utility/backoff.d.ts +48 -0
- package/dist/src/utility/backoff.d.ts.map +1 -0
- package/dist/src/utility/backoff.js +60 -0
- package/dist/src/utility/backoff.js.map +1 -0
- package/dist/src/utility/pending.d.ts +1 -0
- package/dist/src/utility/pending.d.ts.map +1 -1
- package/dist/src/utility/pending.js +3 -1
- package/dist/src/utility/pending.js.map +1 -1
- package/package.json +4 -4
- package/src/blocks/block-store.ts +19 -1
- package/src/blocks/helpers.ts +3 -3
- package/src/btree/btree.ts +108 -63
- package/src/chain/chain.ts +11 -1
- package/src/cluster/index.ts +1 -0
- package/src/cluster/membership.ts +85 -0
- package/src/cluster/structs.ts +51 -1
- package/src/cohort-topic/addressing.ts +12 -4
- package/src/cohort-topic/antidos/bootstrap-evidence.ts +21 -9
- package/src/cohort-topic/antidos/replay-guard.ts +37 -0
- package/src/cohort-topic/antidos/topic-budget.ts +17 -2
- package/src/cohort-topic/coldstart.ts +48 -15
- package/src/cohort-topic/gossip/bus.ts +39 -2
- package/src/cohort-topic/gossip/view.ts +26 -1
- package/src/cohort-topic/member-engine.ts +36 -15
- package/src/cohort-topic/membership/publisher.ts +25 -33
- package/src/cohort-topic/membership/verifier.ts +119 -3
- package/src/cohort-topic/promotion.ts +23 -2
- package/src/cohort-topic/registration/renewal.ts +73 -1
- package/src/cohort-topic/registration/types.ts +15 -0
- package/src/cohort-topic/service.ts +41 -15
- package/src/cohort-topic/sig/payloads.ts +29 -9
- package/src/cohort-topic/traffic.ts +23 -1
- package/src/cohort-topic/walk.ts +95 -24
- package/src/cohort-topic/wire/codec.ts +12 -0
- package/src/cohort-topic/wire/index.ts +2 -0
- package/src/cohort-topic/wire/payloads.ts +14 -5
- package/src/cohort-topic/wire/primitives.ts +188 -0
- package/src/cohort-topic/wire/types.ts +136 -4
- package/src/cohort-topic/wire/validate.ts +168 -134
- package/src/collection/collection.ts +123 -40
- package/src/collection/struct.ts +44 -2
- package/src/collections/tree/tree.ts +42 -5
- package/src/index.ts +1 -0
- package/src/log/log.ts +37 -9
- package/src/log/struct.ts +5 -4
- package/src/matchmaking/wire.ts +25 -102
- package/src/network/i-key-network.ts +10 -0
- package/src/network/i-repo.ts +8 -0
- package/src/network/struct.ts +10 -0
- package/src/reactivity/push-state.ts +4 -37
- package/src/reactivity/wire-validate.ts +7 -81
- package/src/reactivity/wire.ts +3 -3
- package/src/testing/async-wait.ts +65 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/test-transactor.ts +468 -0
- package/src/transaction/actions-engine.ts +8 -6
- package/src/transaction/coordinator.ts +498 -229
- package/src/transaction/errors.ts +91 -0
- package/src/transaction/index.ts +23 -3
- package/src/transaction/operations-hash.ts +196 -0
- package/src/transaction/read-dependency-collector.ts +78 -0
- package/src/transaction/session.ts +37 -9
- package/src/transaction/transaction.ts +211 -4
- package/src/transaction/validator.ts +73 -48
- package/src/transactor/network-transactor.ts +164 -49
- package/src/transactor/transactor-source.ts +47 -12
- package/src/transform/atomic-proxy.ts +92 -49
- package/src/transform/cache-source.ts +87 -9
- package/src/transform/helpers.ts +8 -2
- package/src/transform/tracker.ts +69 -11
- package/src/utility/backoff.ts +95 -0
- package/src/utility/pending.ts +3 -1
- package/src/transaction/context.ts +0 -103
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import type { IBlock, BlockId, BlockStore, BlockType, BlockHeader, BlockOperation } from '../index.js';
|
|
1
|
+
import type { IBlock, BlockId, BlockStore, BlockType, BlockHeader, BlockOperation, ReadPurpose } from '../index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Opaque handle to an in-flight atomic scope.
|
|
4
|
+
*
|
|
5
|
+
* A method that opens a scope (via {@link AtomicProxy.atomic}) receives the scope as its
|
|
6
|
+
* callback argument. To make a *nested* call reuse the enclosing scope — rather than open
|
|
7
|
+
* (and separately commit) a second one — pass that handle back as the `parent` argument of
|
|
8
|
+
* the nested `atomic()` call. This is how genuine nesting is distinguished from a second,
|
|
9
|
+
* unrelated concurrent scope, portably (no `AsyncLocalStorage`, which is Node-only).
|
|
10
|
+
*/
|
|
11
|
+
export interface AtomicScope {
|
|
12
|
+
readonly __atomicScope: unique symbol;
|
|
13
|
+
}
|
|
2
14
|
/**
|
|
3
15
|
* A BlockStore proxy that enables scoped atomic operations.
|
|
4
16
|
* Operations normally delegate directly to the underlying store,
|
|
@@ -8,19 +20,37 @@ import type { IBlock, BlockId, BlockStore, BlockType, BlockHeader, BlockOperatio
|
|
|
8
20
|
* Both the BTree and its trunk should share the same AtomicProxy instance
|
|
9
21
|
* so that all mutations (including root pointer updates) are part of the
|
|
10
22
|
* same atomic batch.
|
|
23
|
+
*
|
|
24
|
+
* Concurrency model: top-level `atomic()` calls are *serialized* through a promise queue,
|
|
25
|
+
* so a second, un-awaited scope started while the first is still in flight waits for the
|
|
26
|
+
* first to commit/roll back before opening its own tracker — it never shares the first's
|
|
27
|
+
* tracker. A genuinely nested call (one that hands back its enclosing scope) bypasses the
|
|
28
|
+
* queue and reuses that scope, so it neither deadlocks on itself nor double-commits.
|
|
11
29
|
*/
|
|
12
30
|
export declare class AtomicProxy<T extends IBlock> implements BlockStore<T> {
|
|
13
31
|
private _base;
|
|
14
32
|
private _active;
|
|
33
|
+
/** Tail of the serialization queue; each new top-level scope awaits the prior one. */
|
|
34
|
+
private _tail;
|
|
35
|
+
/** The scope currently executing, or undefined between scopes. Serialization guarantees
|
|
36
|
+
* at most one is active at a time, so identity against `parent` cleanly separates a
|
|
37
|
+
* nested call (parent === current) from a foreign concurrent one (parent absent/stale). */
|
|
38
|
+
private _current?;
|
|
15
39
|
constructor(store: BlockStore<T>);
|
|
16
|
-
tryGet(id: BlockId): Promise<T | undefined>;
|
|
40
|
+
tryGet(id: BlockId, purpose?: ReadPurpose): Promise<T | undefined>;
|
|
41
|
+
/** Forward a leaf-value upgrade to the active store (duck-typed; the Tracker/CacheSource chain
|
|
42
|
+
* implements it). Keeps navigation-read filtering working for a B-tree bound to this proxy. */
|
|
43
|
+
markReadValue(id: BlockId): void;
|
|
17
44
|
insert(block: T): void;
|
|
18
45
|
update(blockId: BlockId, op: BlockOperation): void;
|
|
19
46
|
delete(blockId: BlockId): void;
|
|
20
47
|
generateId(): BlockId;
|
|
21
48
|
createBlockHeader(type: BlockType, newId?: BlockId): BlockHeader;
|
|
22
|
-
/** Execute fn within an atomic scope. All store mutations are collected
|
|
23
|
-
*
|
|
24
|
-
|
|
49
|
+
/** Execute fn within an atomic scope. All store mutations are collected and committed on
|
|
50
|
+
* success, or discarded on error. `fn` receives a scope handle; pass it as the `parent`
|
|
51
|
+
* of any nested `atomic()` call that should join this scope instead of opening its own.
|
|
52
|
+
* Re-entrant safe (nesting reuses the parent scope) and concurrency safe (unrelated
|
|
53
|
+
* overlapping scopes serialize rather than sharing a tracker). */
|
|
54
|
+
atomic<R>(fn: (scope: AtomicScope) => Promise<R>, parent?: AtomicScope): Promise<R>;
|
|
25
55
|
}
|
|
26
56
|
//# sourceMappingURL=atomic-proxy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-proxy.d.ts","sourceRoot":"","sources":["../../../src/transform/atomic-proxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"atomic-proxy.d.ts","sourceRoot":"","sources":["../../../src/transform/atomic-proxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpH;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAAG,QAAQ,CAAC,aAAa,EAAE,OAAO,MAAM,CAAC;CAAE;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,WAAW,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,UAAU,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,OAAO,CAAgB;IAC/B,sFAAsF;IACtF,OAAO,CAAC,KAAK,CAAoC;IACjD;;gGAE4F;IAC5F,OAAO,CAAC,QAAQ,CAAC,CAAc;gBAEnB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IAK1B,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IACxE;oGACgG;IAChG,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI;IAChC,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IACtB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI;IAClD,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAC9B,UAAU,IAAI,OAAO;IACrB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,WAAW;IAEhE;;;;uEAImE;IAC7D,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;CA+BzF"}
|
|
@@ -8,30 +8,60 @@ import { Atomic } from './atomic.js';
|
|
|
8
8
|
* Both the BTree and its trunk should share the same AtomicProxy instance
|
|
9
9
|
* so that all mutations (including root pointer updates) are part of the
|
|
10
10
|
* same atomic batch.
|
|
11
|
+
*
|
|
12
|
+
* Concurrency model: top-level `atomic()` calls are *serialized* through a promise queue,
|
|
13
|
+
* so a second, un-awaited scope started while the first is still in flight waits for the
|
|
14
|
+
* first to commit/roll back before opening its own tracker — it never shares the first's
|
|
15
|
+
* tracker. A genuinely nested call (one that hands back its enclosing scope) bypasses the
|
|
16
|
+
* queue and reuses that scope, so it neither deadlocks on itself nor double-commits.
|
|
11
17
|
*/
|
|
12
18
|
export class AtomicProxy {
|
|
13
19
|
_base;
|
|
14
20
|
_active;
|
|
21
|
+
/** Tail of the serialization queue; each new top-level scope awaits the prior one. */
|
|
22
|
+
_tail = Promise.resolve();
|
|
23
|
+
/** The scope currently executing, or undefined between scopes. Serialization guarantees
|
|
24
|
+
* at most one is active at a time, so identity against `parent` cleanly separates a
|
|
25
|
+
* nested call (parent === current) from a foreign concurrent one (parent absent/stale). */
|
|
26
|
+
_current;
|
|
15
27
|
constructor(store) {
|
|
16
28
|
this._base = store;
|
|
17
29
|
this._active = store;
|
|
18
30
|
}
|
|
19
|
-
async tryGet(id) { return this._active.tryGet(id); }
|
|
31
|
+
async tryGet(id, purpose) { return this._active.tryGet(id, purpose); }
|
|
32
|
+
/** Forward a leaf-value upgrade to the active store (duck-typed; the Tracker/CacheSource chain
|
|
33
|
+
* implements it). Keeps navigation-read filtering working for a B-tree bound to this proxy. */
|
|
34
|
+
markReadValue(id) { this._active.markReadValue?.(id); }
|
|
20
35
|
insert(block) { this._active.insert(block); }
|
|
21
36
|
update(blockId, op) { this._active.update(blockId, op); }
|
|
22
37
|
delete(blockId) { this._active.delete(blockId); }
|
|
23
38
|
generateId() { return this._active.generateId(); }
|
|
24
39
|
createBlockHeader(type, newId) { return this._active.createBlockHeader(type, newId); }
|
|
25
|
-
/** Execute fn within an atomic scope. All store mutations are collected
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
/** Execute fn within an atomic scope. All store mutations are collected and committed on
|
|
41
|
+
* success, or discarded on error. `fn` receives a scope handle; pass it as the `parent`
|
|
42
|
+
* of any nested `atomic()` call that should join this scope instead of opening its own.
|
|
43
|
+
* Re-entrant safe (nesting reuses the parent scope) and concurrency safe (unrelated
|
|
44
|
+
* overlapping scopes serialize rather than sharing a tracker). */
|
|
45
|
+
async atomic(fn, parent) {
|
|
46
|
+
// Genuine nesting: the caller handed back the scope it is already inside. Reuse it —
|
|
47
|
+
// no second tracker, no second commit — and skip the queue, since waiting on the very
|
|
48
|
+
// scope we are running inside would deadlock.
|
|
49
|
+
if (parent !== undefined && parent === this._current) {
|
|
50
|
+
return fn(parent);
|
|
30
51
|
}
|
|
52
|
+
// Top-level scope (first, or a foreign call that overlapped one in flight): take a
|
|
53
|
+
// place in the serialization queue so it runs against its own tracker only after the
|
|
54
|
+
// prior scope has committed/rolled back.
|
|
55
|
+
const prior = this._tail;
|
|
56
|
+
let release;
|
|
57
|
+
this._tail = new Promise(resolve => { release = resolve; });
|
|
58
|
+
await prior;
|
|
31
59
|
const atomic = new Atomic(this._base);
|
|
60
|
+
const scope = atomic;
|
|
61
|
+
this._current = scope;
|
|
32
62
|
this._active = atomic;
|
|
33
63
|
try {
|
|
34
|
-
const result = await fn();
|
|
64
|
+
const result = await fn(scope);
|
|
35
65
|
atomic.commit();
|
|
36
66
|
return result;
|
|
37
67
|
}
|
|
@@ -41,6 +71,8 @@ export class AtomicProxy {
|
|
|
41
71
|
}
|
|
42
72
|
finally {
|
|
43
73
|
this._active = this._base;
|
|
74
|
+
this._current = undefined;
|
|
75
|
+
release();
|
|
44
76
|
}
|
|
45
77
|
}
|
|
46
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-proxy.js","sourceRoot":"","sources":["../../../src/transform/atomic-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"atomic-proxy.js","sourceRoot":"","sources":["../../../src/transform/atomic-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAcrC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,WAAW;IACf,KAAK,CAAgB;IACrB,OAAO,CAAgB;IAC/B,sFAAsF;IAC9E,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IACjD;;gGAE4F;IACpF,QAAQ,CAAe;IAE/B,YAAY,KAAoB;QAC/B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAW,EAAE,OAAqB,IAA4B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACrH;oGACgG;IAChG,aAAa,CAAC,EAAW,IAAW,IAAI,CAAC,OAAqD,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrH,MAAM,CAAC,KAAQ,IAAU,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,CAAC,OAAgB,EAAE,EAAkB,IAAU,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACxF,MAAM,CAAC,OAAgB,IAAU,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAChE,UAAU,KAAc,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC3D,iBAAiB,CAAC,IAAe,EAAE,KAAe,IAAiB,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAExH;;;;uEAImE;IACnE,KAAK,CAAC,MAAM,CAAI,EAAsC,EAAE,MAAoB;QAC3E,qFAAqF;QACrF,sFAAsF;QACtF,8CAA8C;QAC9C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtD,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QACD,mFAAmF;QACnF,qFAAqF;QACrF,yCAAyC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,OAAoB,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,KAAK,CAAC;QACZ,MAAM,MAAM,GAAG,IAAI,MAAM,CAAI,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,MAAgC,CAAC;QAC/C,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO,MAAM,CAAC;QACf,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACZ,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,CAAC;QACT,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC;YAC1B,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;YAC1B,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC;CACD"}
|
|
@@ -1,14 +1,45 @@
|
|
|
1
|
-
import type { IBlock, BlockHeader, BlockId, BlockSource, BlockType, Transforms } from "../index.js";
|
|
1
|
+
import type { IBlock, BlockHeader, BlockId, BlockSource, BlockType, ReadPurpose, Transforms } from "../index.js";
|
|
2
2
|
import { LruMap } from "../utility/lru-map.js";
|
|
3
|
+
import type { ReadDependencyCollector } from "../transaction/read-dependency-collector.js";
|
|
3
4
|
export declare class CacheSource<T extends IBlock> implements BlockSource<T> {
|
|
4
5
|
protected readonly source: BlockSource<T>;
|
|
6
|
+
/** Shared per-transaction read-dependency accumulator (same instance the collection's
|
|
7
|
+
* TransactorSource holds). Optional: log-walk caches that never form a transaction omit it. */
|
|
8
|
+
private readonly collector?;
|
|
5
9
|
protected cache: LruMap<BlockId, T>;
|
|
6
|
-
|
|
7
|
-
|
|
10
|
+
/** Per-id monotonic counter, bumped whenever the cached content for an id changes.
|
|
11
|
+
* Consumers (e.g. {@link Tracker}'s materialized-block memo) read it via
|
|
12
|
+
* {@link getGeneration} to detect that a cached "source + ops" result has gone stale.
|
|
13
|
+
* Over-bumping is safe (it only forces a re-materialize); under-bumping is a correctness
|
|
14
|
+
* bug, so every content-changing site bumps. A benign LRU evict + reload also bumps. */
|
|
15
|
+
private generations;
|
|
16
|
+
/** Per-id committed revision of the content currently cached for that id. Learned from the
|
|
17
|
+
* source on a miss-load, advanced by {@link transformCache} when a commit folds new content
|
|
18
|
+
* in, and dropped alongside the cached block on delete/clear. Re-emitted on every cache HIT
|
|
19
|
+
* so a hit records a read dependency at the right revision — the whole point of this map, since
|
|
20
|
+
* the underlying source is never consulted on a hit. */
|
|
21
|
+
private revisions;
|
|
22
|
+
constructor(source: BlockSource<T>, maxSize?: number,
|
|
23
|
+
/** Shared per-transaction read-dependency accumulator (same instance the collection's
|
|
24
|
+
* TransactorSource holds). Optional: log-walk caches that never form a transaction omit it. */
|
|
25
|
+
collector?: ReadDependencyCollector | undefined);
|
|
26
|
+
private bump;
|
|
27
|
+
/** The current generation for an id — advances every time this cache's content for
|
|
28
|
+
* the id changes (miss-load, clear, or transformCache). Stable across pure cache hits. */
|
|
29
|
+
getGeneration(id: BlockId): number;
|
|
30
|
+
tryGet(id: BlockId, purpose?: ReadPurpose): Promise<T | undefined>;
|
|
31
|
+
/** Upgrade an already-captured read of `id` to a `value` read in the shared collector,
|
|
32
|
+
* retaining it in the conflict set. The B-tree point-lookup descent calls this (through the
|
|
33
|
+
* Tracker, which forwards) to pin the terminal leaf after recording the interior nodes as
|
|
34
|
+
* `navigation`. No-op when no collector is wired (log-walk caches) or the id was never
|
|
35
|
+
* recorded. Duck-typed by the Tracker; keep the name in sync with Tracker.markReadValue. */
|
|
36
|
+
markReadValue(id: BlockId): void;
|
|
8
37
|
generateId(): BlockId;
|
|
9
38
|
createBlockHeader(type: BlockType, newId?: BlockId): BlockHeader;
|
|
10
39
|
clear(blockIds?: BlockId[] | undefined): void;
|
|
11
|
-
/** Mutates the cache without affecting the source
|
|
12
|
-
|
|
40
|
+
/** Mutates the cache without affecting the source. `revision` is the committed revision this
|
|
41
|
+
* transform lands at; the stored per-id revision advances to it so a later read records a
|
|
42
|
+
* dependency at the NEW revision (recording the old one would spuriously fail validation). */
|
|
43
|
+
transformCache(transform: Transforms, revision: number): void;
|
|
13
44
|
}
|
|
14
45
|
//# sourceMappingURL=cache-source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-source.d.ts","sourceRoot":"","sources":["../../../src/transform/cache-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"cache-source.d.ts","sourceRoot":"","sources":["../../../src/transform/cache-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEjH,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAE/C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAC;AAc3F,qBAAa,WAAW,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,WAAW,CAAC,CAAC,CAAC;IAuBlE,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAEzC;oGACgG;IAChG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IA1B5B,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACpC;;;;4FAIwF;IAKxF,OAAO,CAAC,WAAW,CAA8B;IACjD;;;;6DAIyD;IAIzD,OAAO,CAAC,SAAS,CAA8B;gBAG3B,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACzC,OAAO,SAAiB;IACxB;oGACgG;IAC/E,SAAS,CAAC,EAAE,uBAAuB,YAAA;IAKrD,OAAO,CAAC,IAAI;IAIZ;8FAC0F;IAC1F,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM;IAI5B,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,GAAE,WAAqB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IA8BjF;;;;iGAI6F;IAC7F,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI;IAIhC,UAAU,IAAI,OAAO;IAIrB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,WAAW;IAIhE,KAAK,CAAC,QAAQ,GAAE,OAAO,EAAE,GAAG,SAAqB;IAgBjD;;mGAE+F;IAC/F,cAAc,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM;CAsBtD"}
|
|
@@ -3,30 +3,92 @@ import { LruMap } from "../utility/lru-map.js";
|
|
|
3
3
|
import { createLogger } from "../logger.js";
|
|
4
4
|
const log = createLogger('cache');
|
|
5
5
|
const DefaultMaxSize = 128;
|
|
6
|
+
/** The revision a source reports for an id, or undefined if the source can't report one.
|
|
7
|
+
* Duck-typed exactly like {@link Tracker}'s getGeneration probe — CacheSource layers over
|
|
8
|
+
* arbitrary BlockSources (including test doubles) that need not implement it. */
|
|
9
|
+
function sourceReadRevision(source, id) {
|
|
10
|
+
const src = source;
|
|
11
|
+
return typeof src.getReadRevision === 'function' ? src.getReadRevision(id) : undefined;
|
|
12
|
+
}
|
|
6
13
|
export class CacheSource {
|
|
7
14
|
source;
|
|
15
|
+
collector;
|
|
8
16
|
cache;
|
|
9
|
-
|
|
17
|
+
/** Per-id monotonic counter, bumped whenever the cached content for an id changes.
|
|
18
|
+
* Consumers (e.g. {@link Tracker}'s materialized-block memo) read it via
|
|
19
|
+
* {@link getGeneration} to detect that a cached "source + ops" result has gone stale.
|
|
20
|
+
* Over-bumping is safe (it only forces a re-materialize); under-bumping is a correctness
|
|
21
|
+
* bug, so every content-changing site bumps. A benign LRU evict + reload also bumps. */
|
|
22
|
+
// NOTE: generations is never pruned — it retains one small (id → number) entry per distinct id
|
|
23
|
+
// ever touched, even after LRU eviction from `cache`. Bounded by the number of distinct blocks a
|
|
24
|
+
// collection sees over its lifetime; if that ever grows large enough to matter, evict alongside
|
|
25
|
+
// the LRU (dropping a generation is safe — a reload re-bumps from 0/absent, forcing re-materialize).
|
|
26
|
+
generations = new Map();
|
|
27
|
+
/** Per-id committed revision of the content currently cached for that id. Learned from the
|
|
28
|
+
* source on a miss-load, advanced by {@link transformCache} when a commit folds new content
|
|
29
|
+
* in, and dropped alongside the cached block on delete/clear. Re-emitted on every cache HIT
|
|
30
|
+
* so a hit records a read dependency at the right revision — the whole point of this map, since
|
|
31
|
+
* the underlying source is never consulted on a hit. */
|
|
32
|
+
// NOTE: an LRU-evicted id can leave a stale `revisions` entry (eviction drops `cache` but not
|
|
33
|
+
// this map — see clear()/the LruMap eviction). Benign: the next read of that id is a cache MISS
|
|
34
|
+
// that re-learns the revision from the source and overwrites the entry before recording anything.
|
|
35
|
+
revisions = new Map();
|
|
36
|
+
constructor(source, maxSize = DefaultMaxSize,
|
|
37
|
+
/** Shared per-transaction read-dependency accumulator (same instance the collection's
|
|
38
|
+
* TransactorSource holds). Optional: log-walk caches that never form a transaction omit it. */
|
|
39
|
+
collector) {
|
|
10
40
|
this.source = source;
|
|
41
|
+
this.collector = collector;
|
|
11
42
|
this.cache = new LruMap(maxSize);
|
|
12
43
|
}
|
|
13
|
-
|
|
44
|
+
bump(id) {
|
|
45
|
+
this.generations.set(id, (this.generations.get(id) ?? 0) + 1);
|
|
46
|
+
}
|
|
47
|
+
/** The current generation for an id — advances every time this cache's content for
|
|
48
|
+
* the id changes (miss-load, clear, or transformCache). Stable across pure cache hits. */
|
|
49
|
+
getGeneration(id) {
|
|
50
|
+
return this.generations.get(id) ?? 0;
|
|
51
|
+
}
|
|
52
|
+
async tryGet(id, purpose = 'value') {
|
|
14
53
|
let block = this.cache.get(id);
|
|
15
54
|
if (block) {
|
|
55
|
+
// Cache hit: the source is never consulted, so re-emit the revision we learned when this
|
|
56
|
+
// id was first loaded/folded. Without this a block served from cache records NO read
|
|
57
|
+
// dependency (the original bug), so its stale-read check could never fire. Carry the
|
|
58
|
+
// caller's purpose so a navigation-only cache hit stays droppable from the conflict set.
|
|
59
|
+
const rev = this.revisions.get(id);
|
|
60
|
+
if (rev !== undefined)
|
|
61
|
+
this.collector?.record(id, rev, purpose);
|
|
16
62
|
log('hit id=%s', id);
|
|
17
63
|
}
|
|
18
64
|
else {
|
|
19
|
-
block = await this.source.tryGet(id);
|
|
65
|
+
block = await this.source.tryGet(id, purpose);
|
|
20
66
|
if (block) {
|
|
21
67
|
this.cache.set(id, block);
|
|
68
|
+
this.bump(id);
|
|
69
|
+
// Learn the revision from the source (which just served it) and record it. On a miss the
|
|
70
|
+
// underlying TransactorSource already recorded the same id@rev/purpose into the shared
|
|
71
|
+
// collector; max-wins (revision) + value-wins (purpose) collapse the two to one entry.
|
|
72
|
+
const rev = sourceReadRevision(this.source, id) ?? 0;
|
|
73
|
+
this.revisions.set(id, rev);
|
|
74
|
+
this.collector?.record(id, rev, purpose);
|
|
22
75
|
log('miss:loaded id=%s cacheSize=%d', id, this.cache.size);
|
|
23
76
|
}
|
|
24
77
|
else {
|
|
78
|
+
// Absent block: record nothing (matches TransactorSource, which skips missing blocks).
|
|
25
79
|
log('miss:absent id=%s', id);
|
|
26
80
|
}
|
|
27
81
|
}
|
|
28
82
|
return structuredClone(block);
|
|
29
83
|
}
|
|
84
|
+
/** Upgrade an already-captured read of `id` to a `value` read in the shared collector,
|
|
85
|
+
* retaining it in the conflict set. The B-tree point-lookup descent calls this (through the
|
|
86
|
+
* Tracker, which forwards) to pin the terminal leaf after recording the interior nodes as
|
|
87
|
+
* `navigation`. No-op when no collector is wired (log-walk caches) or the id was never
|
|
88
|
+
* recorded. Duck-typed by the Tracker; keep the name in sync with Tracker.markReadValue. */
|
|
89
|
+
markReadValue(id) {
|
|
90
|
+
this.collector?.markValue(id);
|
|
91
|
+
}
|
|
30
92
|
generateId() {
|
|
31
93
|
return this.source.generateId();
|
|
32
94
|
}
|
|
@@ -37,26 +99,40 @@ export class CacheSource {
|
|
|
37
99
|
if (blockIds) {
|
|
38
100
|
for (const id of blockIds) {
|
|
39
101
|
this.cache.delete(id);
|
|
102
|
+
this.revisions.delete(id);
|
|
103
|
+
this.bump(id);
|
|
40
104
|
}
|
|
41
105
|
}
|
|
42
106
|
else {
|
|
107
|
+
for (const [id] of this.cache) {
|
|
108
|
+
this.bump(id);
|
|
109
|
+
}
|
|
43
110
|
this.cache.clear();
|
|
111
|
+
this.revisions.clear();
|
|
44
112
|
}
|
|
45
113
|
}
|
|
46
|
-
/** Mutates the cache without affecting the source
|
|
47
|
-
|
|
114
|
+
/** Mutates the cache without affecting the source. `revision` is the committed revision this
|
|
115
|
+
* transform lands at; the stored per-id revision advances to it so a later read records a
|
|
116
|
+
* dependency at the NEW revision (recording the old one would spuriously fail validation). */
|
|
117
|
+
transformCache(transform, revision) {
|
|
48
118
|
for (const blockId of transform.deletes ?? []) {
|
|
49
119
|
this.cache.delete(blockId);
|
|
120
|
+
this.revisions.delete(blockId);
|
|
121
|
+
this.bump(blockId);
|
|
50
122
|
}
|
|
51
123
|
for (const [, block] of Object.entries(transform.inserts ?? {})) {
|
|
52
124
|
this.cache.set(block.header.id, structuredClone(block));
|
|
125
|
+
this.revisions.set(block.header.id, revision);
|
|
126
|
+
this.bump(block.header.id);
|
|
53
127
|
}
|
|
54
128
|
for (const [blockId, operations] of Object.entries(transform.updates ?? {})) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
129
|
+
const block = this.cache.get(blockId);
|
|
130
|
+
if (block) {
|
|
131
|
+
for (const op of operations) {
|
|
58
132
|
applyOperation(block, op);
|
|
133
|
+
this.bump(blockId);
|
|
59
134
|
}
|
|
135
|
+
this.revisions.set(blockId, revision);
|
|
60
136
|
}
|
|
61
137
|
}
|
|
62
138
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-source.js","sourceRoot":"","sources":["../../../src/transform/cache-source.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"cache-source.js","sourceRoot":"","sources":["../../../src/transform/cache-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/transform/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvH;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,cAAc,QAMnG;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,QAIzE;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,cAAc;;EAOlG;AAED,8DAA8D;AAC9D,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,YAMvE;AAED,iCAAiC;AACjC,wBAAgB,eAAe,IAAI,UAAU,CAE5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU,CAQhE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/transform/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvH;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,cAAc,QAMnG;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,QAIzE;AAED,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,cAAc;;EAOlG;AAED,8DAA8D;AAC9D,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,YAMvE;AAED,iCAAiC;AACjC,wBAAgB,eAAe,IAAI,UAAU,CAE5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,UAAU,GAAG,UAAU,CAQhE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAYxE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAIhE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,UAAU,EAAE,UAAU,EAAE,GAAG,UAAU,CAExE;AAGD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS,CAOtF;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,UAAU,CAM1F;AAED,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,QAYlG;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAWlG;AAED,uDAAuD;AACvD,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,UAAU,CAM1G"}
|
|
@@ -67,10 +67,16 @@ export function copyTransforms(transform) {
|
|
|
67
67
|
return { inserts, updates, deletes: transform.deletes ? [...transform.deletes] : undefined };
|
|
68
68
|
}
|
|
69
69
|
export function mergeTransforms(a, b) {
|
|
70
|
+
// updates: for a block id present in both sides, concatenate a's ops then b's ops
|
|
71
|
+
// (order-preserving) rather than letting b's array replace a's. inserts stay last-wins.
|
|
72
|
+
const updates = { ...a.updates };
|
|
73
|
+
for (const [id, ops] of Object.entries(b.updates ?? {})) {
|
|
74
|
+
updates[id] = updates[id] ? [...updates[id], ...ops] : ops;
|
|
75
|
+
}
|
|
70
76
|
return {
|
|
71
77
|
inserts: { ...a.inserts, ...b.inserts },
|
|
72
|
-
updates
|
|
73
|
-
deletes: [...(a.deletes ?? []), ...(b.deletes ?? [])]
|
|
78
|
+
updates,
|
|
79
|
+
deletes: [...new Set([...(a.deletes ?? []), ...(b.deletes ?? [])])]
|
|
74
80
|
};
|
|
75
81
|
}
|
|
76
82
|
export function isTransformsEmpty(transform) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/transform/helpers.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/transform/helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IBlock, BlockId, BlockStore as IBlockStore, BlockHeader, BlockOperation, BlockType, BlockSource as IBlockSource } from "../index.js";
|
|
1
|
+
import type { IBlock, BlockId, BlockStore as IBlockStore, BlockHeader, BlockOperation, BlockType, BlockSource as IBlockSource, ReadPurpose } from "../index.js";
|
|
2
2
|
/** A block store that collects transformations, without applying them to the underlying source.
|
|
3
3
|
* Transformations are also applied to the retrieved blocks, making it seem like the source has been modified.
|
|
4
4
|
*/
|
|
@@ -6,10 +6,25 @@ export declare class Tracker<T extends IBlock> implements IBlockStore<T> {
|
|
|
6
6
|
private readonly source;
|
|
7
7
|
/** The collected set of transformations to be applied. Treat as immutable */
|
|
8
8
|
transforms: import("./struct.js").Transforms;
|
|
9
|
+
/** Per-id memo of the materialized (source block + all `updates[id]` ops) result, so a
|
|
10
|
+
* repeated read of a hot op-carrying block is O(block size) instead of O(block size + ops).
|
|
11
|
+
* Kept fresh incrementally on {@link update}, dropped on {@link insert}/{@link delete}/{@link reset},
|
|
12
|
+
* and invalidated when the source's generation for the id advances (external cache mutation).
|
|
13
|
+
* Only populated for sources that expose `getGeneration` — without a drift signal we cannot
|
|
14
|
+
* detect source changes, so those fall back to always-replay. `gen` is the source generation of
|
|
15
|
+
* the base block content the memo was built from. */
|
|
16
|
+
private materialized;
|
|
9
17
|
constructor(source: IBlockSource<T>,
|
|
10
18
|
/** The collected set of transformations to be applied. Treat as immutable */
|
|
11
19
|
transforms?: import("./struct.js").Transforms);
|
|
12
|
-
|
|
20
|
+
/** The source's generation for an id, or undefined if the source cannot report drift. */
|
|
21
|
+
private sourceGeneration;
|
|
22
|
+
tryGet(id: BlockId, purpose?: ReadPurpose): Promise<T | undefined>;
|
|
23
|
+
/** Forward a leaf-value upgrade down to the source's read collector (duck-typed: only the
|
|
24
|
+
* CacheSource layer implements it). Lets the B-tree point-lookup descent, which reads through
|
|
25
|
+
* this tracker, pin its terminal leaf as a `value` read after tagging interior nodes
|
|
26
|
+
* `navigation`. No-op for sources without a collector (test doubles, log-walk caches). */
|
|
27
|
+
markReadValue(id: BlockId): void;
|
|
13
28
|
generateId(): BlockId;
|
|
14
29
|
createBlockHeader(type: BlockType, newId?: BlockId): BlockHeader;
|
|
15
30
|
insert(block: T): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../../src/transform/tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,IAAI,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../../src/transform/tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,IAAI,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGhK;;GAEG;AACH,qBAAa,OAAO,CAAC,CAAC,SAAS,MAAM,CAAE,YAAW,WAAW,CAAC,CAAC,CAAC;IAW9D,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,6EAA6E;IACtE,UAAU;IAZlB;;;;;;yDAMqD;IACrD,OAAO,CAAC,YAAY,CAAiD;gBAGnD,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IACxC,6EAA6E;IACtE,UAAU,mCAAoB;IAGtC,yFAAyF;IACzF,OAAO,CAAC,gBAAgB;IAKlB,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,GAAE,WAAqB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAsCjF;;;+FAG2F;IAC3F,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI;IAIhC,UAAU,IAAI,OAAO;IAIrB,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,WAAW;IAIhE,MAAM,CAAC,KAAK,EAAE,CAAC;IAYf,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,cAAc;IAkB3C,MAAM,CAAC,OAAO,EAAE,OAAO;IAQvB,KAAK,CAAC,YAAY,mCAAoB;IAOtC,mBAAmB,IAAI,OAAO,EAAE;IAIhC,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CAGhC"}
|
|
@@ -1,29 +1,72 @@
|
|
|
1
|
-
import { applyOperation, emptyTransforms, blockIdsForTransforms, ensured } from "../index.js";
|
|
1
|
+
import { applyOperation, applyOperations, emptyTransforms, blockIdsForTransforms, ensured } from "../index.js";
|
|
2
2
|
/** A block store that collects transformations, without applying them to the underlying source.
|
|
3
3
|
* Transformations are also applied to the retrieved blocks, making it seem like the source has been modified.
|
|
4
4
|
*/
|
|
5
5
|
export class Tracker {
|
|
6
6
|
source;
|
|
7
7
|
transforms;
|
|
8
|
+
/** Per-id memo of the materialized (source block + all `updates[id]` ops) result, so a
|
|
9
|
+
* repeated read of a hot op-carrying block is O(block size) instead of O(block size + ops).
|
|
10
|
+
* Kept fresh incrementally on {@link update}, dropped on {@link insert}/{@link delete}/{@link reset},
|
|
11
|
+
* and invalidated when the source's generation for the id advances (external cache mutation).
|
|
12
|
+
* Only populated for sources that expose `getGeneration` — without a drift signal we cannot
|
|
13
|
+
* detect source changes, so those fall back to always-replay. `gen` is the source generation of
|
|
14
|
+
* the base block content the memo was built from. */
|
|
15
|
+
materialized = new Map();
|
|
8
16
|
constructor(source,
|
|
9
17
|
/** The collected set of transformations to be applied. Treat as immutable */
|
|
10
18
|
transforms = emptyTransforms()) {
|
|
11
19
|
this.source = source;
|
|
12
20
|
this.transforms = transforms;
|
|
13
21
|
}
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
/** The source's generation for an id, or undefined if the source cannot report drift. */
|
|
23
|
+
sourceGeneration(id) {
|
|
24
|
+
const src = this.source;
|
|
25
|
+
return typeof src.getGeneration === 'function' ? src.getGeneration(id) : undefined;
|
|
26
|
+
}
|
|
27
|
+
async tryGet(id, purpose = 'value') {
|
|
28
|
+
// NOTE: precedence here is insert > delete > source+updates. In a well-formed transform an id is
|
|
29
|
+
// never in both `inserts` and `deletes` (insert/delete each clear the other), so order is moot. It
|
|
30
|
+
// only diverges from the canonical `applyTransform` (delete-last-wins, see struct.ts / helpers.ts:132)
|
|
31
|
+
// in the malformed insert+delete state reachable via the phantom-delete bug (double-delete then
|
|
32
|
+
// reinsert). Likewise the insert path intentionally skips `updates[id]` — inserted blocks bake ops
|
|
33
|
+
// in-place via update(); a stale pre-insert `updates[id]` is discarded here but would be re-applied
|
|
34
|
+
// on commit. Both are read-vs-commit inconsistencies confined to malformed states; fix the source
|
|
35
|
+
// bug (phantom delete / stale updates) rather than papering over it here.
|
|
36
|
+
if (this.transforms.inserts && Object.hasOwn(this.transforms.inserts, id)) {
|
|
37
|
+
return structuredClone(this.transforms.inserts[id]);
|
|
38
|
+
}
|
|
39
|
+
if (this.transforms.deletes?.includes(id)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const gen = this.sourceGeneration(id);
|
|
43
|
+
const memo = this.materialized.get(id);
|
|
44
|
+
if (memo && (gen === undefined || memo.gen === gen)) {
|
|
45
|
+
return structuredClone(memo.block); // O(block size), no replay
|
|
46
|
+
}
|
|
47
|
+
const block = await this.source.tryGet(id, purpose);
|
|
16
48
|
if (block) {
|
|
17
49
|
const ops = this.transforms.updates?.[id] ?? [];
|
|
18
|
-
ops.
|
|
19
|
-
|
|
20
|
-
|
|
50
|
+
if (ops.length > 0) {
|
|
51
|
+
applyOperations(block, ops);
|
|
52
|
+
// Memoize only when the source can report drift, and stamp with the generation read
|
|
53
|
+
// AFTER the load — the source may bump during tryGet (a cache miss-load), and stamping
|
|
54
|
+
// with the pre-load generation would force a needless reload on the very next read.
|
|
55
|
+
const freshGen = this.sourceGeneration(id);
|
|
56
|
+
if (freshGen !== undefined) {
|
|
57
|
+
this.materialized.set(id, { block, gen: freshGen });
|
|
58
|
+
}
|
|
59
|
+
return structuredClone(block); // clone so callers can't mutate the memo
|
|
21
60
|
}
|
|
22
61
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
62
|
+
return block; // no-ops path unchanged (source already cloned)
|
|
63
|
+
}
|
|
64
|
+
/** Forward a leaf-value upgrade down to the source's read collector (duck-typed: only the
|
|
65
|
+
* CacheSource layer implements it). Lets the B-tree point-lookup descent, which reads through
|
|
66
|
+
* this tracker, pin its terminal leaf as a `value` read after tagging interior nodes
|
|
67
|
+
* `navigation`. No-op for sources without a collector (test doubles, log-walk caches). */
|
|
68
|
+
markReadValue(id) {
|
|
69
|
+
this.source.markReadValue?.(id);
|
|
27
70
|
}
|
|
28
71
|
generateId() {
|
|
29
72
|
return this.source.generateId();
|
|
@@ -34,6 +77,8 @@ export class Tracker {
|
|
|
34
77
|
insert(block) {
|
|
35
78
|
const inserts = this.transforms.inserts ??= {};
|
|
36
79
|
inserts[block.header.id] = structuredClone(block);
|
|
80
|
+
// Served from `inserts` now, not source+updates — the materialized memo no longer applies.
|
|
81
|
+
this.materialized.delete(block.header.id);
|
|
37
82
|
const deletes = this.transforms.deletes;
|
|
38
83
|
const deleteIndex = deletes?.indexOf(block.header.id) ?? -1;
|
|
39
84
|
if (deleteIndex >= 0) {
|
|
@@ -48,6 +93,14 @@ export class Tracker {
|
|
|
48
93
|
else {
|
|
49
94
|
const updates = this.transforms.updates ??= {};
|
|
50
95
|
ensured(updates, blockId, () => []).push(structuredClone(op));
|
|
96
|
+
// The memo already equals (base source content + prior ops); applying just the new op
|
|
97
|
+
// keeps it equal to the full ops list — O(1), no full replay. Leave `gen` untouched: it
|
|
98
|
+
// still records the base-content generation, so a later external source change still
|
|
99
|
+
// forces a reload. (Refreshing gen here would mask stale base content.)
|
|
100
|
+
const memo = this.materialized.get(blockId);
|
|
101
|
+
if (memo) {
|
|
102
|
+
applyOperation(memo.block, op);
|
|
103
|
+
}
|
|
51
104
|
}
|
|
52
105
|
}
|
|
53
106
|
delete(blockId) {
|
|
@@ -55,16 +108,18 @@ export class Tracker {
|
|
|
55
108
|
delete this.transforms.inserts[blockId];
|
|
56
109
|
if (this.transforms.updates)
|
|
57
110
|
delete this.transforms.updates[blockId];
|
|
111
|
+
this.materialized.delete(blockId);
|
|
58
112
|
const deletes = this.transforms.deletes ??= [];
|
|
59
113
|
deletes.push(blockId);
|
|
60
114
|
}
|
|
61
115
|
reset(newTransform = emptyTransforms()) {
|
|
62
116
|
const oldTransform = this.transforms;
|
|
63
117
|
this.transforms = newTransform;
|
|
118
|
+
this.materialized.clear();
|
|
64
119
|
return oldTransform;
|
|
65
120
|
}
|
|
66
121
|
transformedBlockIds() {
|
|
67
|
-
return
|
|
122
|
+
return blockIdsForTransforms(this.transforms);
|
|
68
123
|
}
|
|
69
124
|
conflicts(blockIds) {
|
|
70
125
|
return this.transformedBlockIds().filter(id => blockIds.has(id));
|