@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
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { type ITransactor, type GetBlockResults, type ActionBlocks, type BlockActionStatus, type PendResult, type CommitResult, type PendRequest, type BlockId, type CommitRequest, type BlockGets, type IBlock, type ActionId, type ActionTransforms, type Transform, type Transforms, ensuredMap, Latches, type ClusterNomineesResult } from "../index.js";
|
|
2
|
+
import { applyTransform, blockIdsForTransforms, transformForBlockId, emptyTransforms, concatTransform, transformsFromTransform } from "../transform/index.js";
|
|
3
|
+
|
|
4
|
+
type RevisionNumber = number;
|
|
5
|
+
|
|
6
|
+
type BlockState = {
|
|
7
|
+
/** The current materialized block at each revision */
|
|
8
|
+
materializedBlocks: Map<RevisionNumber, IBlock>;
|
|
9
|
+
/** The latest revision number */
|
|
10
|
+
latestRev: RevisionNumber;
|
|
11
|
+
/** The action that created each revision */
|
|
12
|
+
revisionActions: Map<RevisionNumber, ActionId>;
|
|
13
|
+
/** Currently pending actions */
|
|
14
|
+
pendingActions: Map<ActionId, Transform>;
|
|
15
|
+
/** Committed actions */
|
|
16
|
+
committedActions: Map<ActionId, Transform>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Simple in-memory transactor for testing that maintains materialized blocks for every revision
|
|
20
|
+
export class TestTransactor implements ITransactor {
|
|
21
|
+
private blocks = new Map<BlockId, BlockState>();
|
|
22
|
+
available = true;
|
|
23
|
+
private getLocks = new Map<BlockId, Promise<() => void>>(); // Track lock releases
|
|
24
|
+
|
|
25
|
+
constructor() {}
|
|
26
|
+
|
|
27
|
+
async get(blockGets: BlockGets): Promise<GetBlockResults> {
|
|
28
|
+
this.checkAvailable();
|
|
29
|
+
const results: GetBlockResults = {};
|
|
30
|
+
const uniqueBlockIds = [...new Set(blockGets.blockIds)].sort(); // Ensure consistent lock order if needed, though get is read-only
|
|
31
|
+
const releases: (() => void)[] = [];
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Acquire locks for all requested blocks to ensure consistent read
|
|
35
|
+
for (const blockId of uniqueBlockIds) {
|
|
36
|
+
const lockId = `TestTransactor.commit:${blockId}`; // Use the same lock as commit
|
|
37
|
+
// Wait for any existing lock promise to resolve before acquiring the next
|
|
38
|
+
let release = await this.getLocks.get(lockId);
|
|
39
|
+
if (release) await Promise.resolve(release); // Ensure previous lock released if overlapping calls happen
|
|
40
|
+
|
|
41
|
+
const releasePromise = Latches.acquire(lockId);
|
|
42
|
+
this.getLocks.set(blockId, releasePromise.then(r => () => {
|
|
43
|
+
r();
|
|
44
|
+
this.getLocks.delete(blockId); // Clean up map entry after release
|
|
45
|
+
}));
|
|
46
|
+
release = await releasePromise;
|
|
47
|
+
releases.push(release);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Start of Critical Section (Read) ---
|
|
51
|
+
for (const blockId of blockGets.blockIds) {
|
|
52
|
+
const blockState = this.blocks.get(blockId);
|
|
53
|
+
if (!blockState) {
|
|
54
|
+
// Block doesn't exist yet
|
|
55
|
+
results[blockId] = {
|
|
56
|
+
block: undefined,
|
|
57
|
+
state: { latest: undefined, pendings: [] }
|
|
58
|
+
};
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Get the appropriate materialized block based on context
|
|
63
|
+
let block: IBlock | undefined;
|
|
64
|
+
if (blockGets.context?.actionId !== undefined) {
|
|
65
|
+
// If requesting a specific action, apply pending transform if it exists
|
|
66
|
+
const pendingTransform = blockState.pendingActions.get(blockGets.context.actionId);
|
|
67
|
+
if (pendingTransform) {
|
|
68
|
+
// Read latest committed block as base for pending transform
|
|
69
|
+
const baseBlock = blockState.materializedBlocks.get(blockState.latestRev);
|
|
70
|
+
block = applyTransformSafe(baseBlock, pendingTransform);
|
|
71
|
+
} else {
|
|
72
|
+
// Action not pending, maybe committed? Or maybe invalid actionId for context.
|
|
73
|
+
// For simplicity, return undefined block if specific pending action not found.
|
|
74
|
+
// A more complex impl might check committedActions history.
|
|
75
|
+
block = undefined;
|
|
76
|
+
}
|
|
77
|
+
} else if (blockGets.context?.committed) {
|
|
78
|
+
// Check context.committed for matching pending actions — mirrors coordinator
|
|
79
|
+
// behavior: context.committed proves the action succeeded, so pending blocks
|
|
80
|
+
// for that action should be served.
|
|
81
|
+
for (const { actionId: cId } of blockGets.context.committed) {
|
|
82
|
+
const pendingTransform = blockState.pendingActions.get(cId);
|
|
83
|
+
if (pendingTransform) {
|
|
84
|
+
const baseBlock = blockState.materializedBlocks.get(blockState.latestRev);
|
|
85
|
+
block = applyTransformSafe(baseBlock, pendingTransform);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Fall through to standard resolution if no pending match
|
|
90
|
+
if (block === undefined) {
|
|
91
|
+
if (blockGets.context.rev !== undefined) {
|
|
92
|
+
block = structuredClone(latestMaterializedAt(blockState, blockGets.context.rev));
|
|
93
|
+
} else {
|
|
94
|
+
block = structuredClone(blockState.materializedBlocks.get(blockState.latestRev));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else if (blockGets.context?.rev !== undefined) {
|
|
98
|
+
// Return the materialized block at the highest revision ≤ requested
|
|
99
|
+
block = structuredClone(latestMaterializedAt(blockState, blockGets.context.rev));
|
|
100
|
+
} else {
|
|
101
|
+
// Otherwise return latest materialized block
|
|
102
|
+
block = structuredClone(blockState.materializedBlocks.get(blockState.latestRev));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
const actionId = blockState.revisionActions.get(blockState.latestRev);
|
|
107
|
+
results[blockId] = {
|
|
108
|
+
block,
|
|
109
|
+
state: {
|
|
110
|
+
latest: actionId !== undefined ? {
|
|
111
|
+
rev: blockState.latestRev,
|
|
112
|
+
actionId
|
|
113
|
+
} : undefined,
|
|
114
|
+
pendings: Array.from(blockState.pendingActions.keys())
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// --- End of Critical Section (Read) ---
|
|
119
|
+
|
|
120
|
+
} finally {
|
|
121
|
+
// Release locks in reverse order
|
|
122
|
+
releases.reverse().forEach(release => release());
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async getStatus(actionRefs: ActionBlocks[]): Promise<BlockActionStatus[]> {
|
|
128
|
+
return actionRefs.map(ref => ({
|
|
129
|
+
...ref,
|
|
130
|
+
statuses: ref.blockIds.map(blockId => {
|
|
131
|
+
const blockState = this.blocks.get(blockId);
|
|
132
|
+
if (!blockState) return 'aborted';
|
|
133
|
+
return blockState.pendingActions.has(ref.actionId) ? 'pending'
|
|
134
|
+
: Array.from(blockState.revisionActions.values()).some(actionId => actionId === ref.actionId) ? 'committed'
|
|
135
|
+
: 'aborted';
|
|
136
|
+
})
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async pend(request: PendRequest): Promise<PendResult> {
|
|
141
|
+
this.checkAvailable();
|
|
142
|
+
const { actionId, transforms, policy, rev } = request;
|
|
143
|
+
const blockIds = blockIdsForTransforms(transforms);
|
|
144
|
+
const conflictingPendings: { blockId: BlockId, actionId: ActionId }[] = [];
|
|
145
|
+
const missing: ActionTransforms[] = [];
|
|
146
|
+
|
|
147
|
+
// Check for conflicts (pending or committed based on rev/insert)
|
|
148
|
+
for (const blockId of blockIds) {
|
|
149
|
+
const blockState = this.blocks.get(blockId);
|
|
150
|
+
const blockTransform = transformForBlockId(transforms, blockId);
|
|
151
|
+
if (!blockTransform) continue; // Should not happen
|
|
152
|
+
|
|
153
|
+
if (blockState) {
|
|
154
|
+
// Check for existing pending actions
|
|
155
|
+
if (blockState.pendingActions.size > 0) {
|
|
156
|
+
blockState.pendingActions.forEach((_, pendingActionId) => {
|
|
157
|
+
conflictingPendings.push({ blockId, actionId: pendingActionId });
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for conflicting committed revisions (if rev specified or it's an insert)
|
|
162
|
+
if (rev !== undefined || blockTransform.insert) {
|
|
163
|
+
const checkRev = rev ?? 0; // Check from revision 0 if it's an insert
|
|
164
|
+
if (blockState.latestRev >= checkRev) {
|
|
165
|
+
// Collect conflicting committed actions
|
|
166
|
+
const missingForBlock = new Map<ActionId, { rev: number, transform: Transform }>();
|
|
167
|
+
for (let r = checkRev as number; r <= blockState.latestRev; r++) {
|
|
168
|
+
const committedActionId = blockState.revisionActions.get(r);
|
|
169
|
+
if (committedActionId !== undefined) {
|
|
170
|
+
const committedTransform = blockState.committedActions.get(committedActionId);
|
|
171
|
+
if (committedTransform) {
|
|
172
|
+
missingForBlock.set(committedActionId, { rev: r, transform: committedTransform });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add collected missing transforms for this block to the main missing list
|
|
178
|
+
for (const [mActionId, data] of missingForBlock.entries()) {
|
|
179
|
+
let existing = missing.find(m => m.actionId === mActionId);
|
|
180
|
+
if (!existing) {
|
|
181
|
+
existing = { actionId: mActionId, rev: data.rev, transforms: emptyTransforms() };
|
|
182
|
+
missing.push(existing);
|
|
183
|
+
}
|
|
184
|
+
existing.rev = Math.max(existing.rev ?? 0, data.rev);
|
|
185
|
+
existing.transforms = concatTransform(existing.transforms, blockId, data.transform);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Handle failure due to committed conflicts first
|
|
193
|
+
if (missing.length > 0) {
|
|
194
|
+
return {
|
|
195
|
+
success: false,
|
|
196
|
+
missing
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Handle failure/retry due to pending conflicts
|
|
201
|
+
if (conflictingPendings.length > 0) {
|
|
202
|
+
if (policy === 'f') {
|
|
203
|
+
return { success: false, pending: conflictingPendings };
|
|
204
|
+
} else if (policy === 'r') {
|
|
205
|
+
// Simulate fetching pending transforms for 'r' policy
|
|
206
|
+
const pendingWithTransforms = conflictingPendings
|
|
207
|
+
.map(({ blockId: pBlockId, actionId: pActionId }) => {
|
|
208
|
+
const pBlockState = this.blocks.get(pBlockId);
|
|
209
|
+
const pTransform = pBlockState?.pendingActions.get(pActionId)
|
|
210
|
+
?? pBlockState?.committedActions.get(pActionId); // Might have been committed since check
|
|
211
|
+
if (pTransform) {
|
|
212
|
+
return { blockId: pBlockId, actionId: pActionId, transform: pTransform };
|
|
213
|
+
}
|
|
214
|
+
return null; // Handle case where it disappeared (cancelled?)
|
|
215
|
+
})
|
|
216
|
+
.filter(p => p !== null) as { blockId: BlockId, actionId: ActionId, transform: Transform }[];
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
pending: pendingWithTransforms
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// Policy 'w' allows proceeding despite pending transactions
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// No fatal conflicts found, proceed to pend
|
|
227
|
+
for (const blockId of blockIds) {
|
|
228
|
+
const blockTransform = transformForBlockId(transforms, blockId);
|
|
229
|
+
if (blockTransform) {
|
|
230
|
+
const blockState = ensuredMap(this.blocks, blockId, () => newBlockState());
|
|
231
|
+
blockState.pendingActions.set(actionId, blockTransform);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Return success, include pending list as per StorageRepo behavior
|
|
236
|
+
return {
|
|
237
|
+
success: true,
|
|
238
|
+
pending: conflictingPendings,
|
|
239
|
+
blockIds
|
|
240
|
+
} as PendResult;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async cancel(actionRef: ActionBlocks): Promise<void> {
|
|
244
|
+
this.checkAvailable();
|
|
245
|
+
for (const blockId of actionRef.blockIds) {
|
|
246
|
+
const blockState = this.blocks.get(blockId);
|
|
247
|
+
if (blockState) {
|
|
248
|
+
blockState.pendingActions.delete(actionRef.actionId);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async commit(request: CommitRequest): Promise<CommitResult> {
|
|
254
|
+
this.checkAvailable();
|
|
255
|
+
const { actionId, rev, blockIds } = request;
|
|
256
|
+
const uniqueBlockIds = [...new Set(blockIds)].sort();
|
|
257
|
+
const releases: (() => void)[] = [];
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
// Simulate acquiring locks sequentially like StorageRepo
|
|
261
|
+
for (const id of uniqueBlockIds) {
|
|
262
|
+
const lockId = `TestTransactor.commit:${id}`;
|
|
263
|
+
const release = await Latches.acquire(lockId);
|
|
264
|
+
releases.push(release);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// --- Start of Critical Section (Simulated) ---
|
|
268
|
+
|
|
269
|
+
// Check for stale revisions
|
|
270
|
+
const staleBlocks = blockIds.filter(blockId => {
|
|
271
|
+
const blockState = this.blocks.get(blockId);
|
|
272
|
+
return blockState && blockState.latestRev >= rev;
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (staleBlocks.length > 0) {
|
|
276
|
+
// Collect missing actions for stale blocks
|
|
277
|
+
const missingByAction = new Map<ActionId, Transforms>();
|
|
278
|
+
for (const blockId of staleBlocks) {
|
|
279
|
+
const blockState = this.blocks.get(blockId)!;
|
|
280
|
+
for (let r = rev; r <= blockState.latestRev; r++) {
|
|
281
|
+
const committedActionId = blockState.revisionActions.get(r);
|
|
282
|
+
if (committedActionId) {
|
|
283
|
+
const transform = blockState.committedActions.get(committedActionId);
|
|
284
|
+
if (transform) {
|
|
285
|
+
const existing = missingByAction.get(committedActionId) ?? emptyTransforms();
|
|
286
|
+
missingByAction.set(committedActionId, concatTransform(existing, blockId, transform));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const missing: ActionTransforms[] = Array.from(missingByAction.entries()).map(([actionId, transforms]) => ({
|
|
293
|
+
actionId,
|
|
294
|
+
rev: Array.from(this.blocks.values())
|
|
295
|
+
.flatMap(bs => Array.from(bs.revisionActions.entries()))
|
|
296
|
+
.find(([, aId]) => aId === actionId)?.[0] ?? rev,
|
|
297
|
+
transforms
|
|
298
|
+
}));
|
|
299
|
+
return { success: false, missing };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Verify all blocks have the pending action
|
|
303
|
+
for (const blockId of blockIds) {
|
|
304
|
+
const blockState = this.blocks.get(blockId);
|
|
305
|
+
if (!blockState || !blockState.pendingActions.has(actionId)) {
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
reason: `Action ${actionId} not found or not pending for block ${blockId}`
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Commit the action for each block
|
|
314
|
+
for (const blockId of blockIds) {
|
|
315
|
+
const blockState = this.blocks.get(blockId)!;
|
|
316
|
+
const transform = blockState.pendingActions.get(actionId)!;
|
|
317
|
+
|
|
318
|
+
// Get base block to apply transform to
|
|
319
|
+
const baseBlock = blockState.materializedBlocks.get(blockState.latestRev);
|
|
320
|
+
|
|
321
|
+
let newBlock: IBlock | undefined;
|
|
322
|
+
if (!baseBlock) {
|
|
323
|
+
if (!transform.insert) {
|
|
324
|
+
throw new Error(`Commit Error: Action ${actionId} has no insert for new block ${blockId}`);
|
|
325
|
+
}
|
|
326
|
+
newBlock = structuredClone(transform.insert);
|
|
327
|
+
} else {
|
|
328
|
+
newBlock = applyTransformSafe(baseBlock, transform);
|
|
329
|
+
if (!newBlock && !transform.delete) {
|
|
330
|
+
throw new Error(`Commit Error: Action ${actionId} resulted in undefined block but had no delete flag for block ${blockId}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (newBlock) {
|
|
335
|
+
blockState.materializedBlocks.set(rev, newBlock);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Update block state
|
|
339
|
+
blockState.latestRev = rev;
|
|
340
|
+
blockState.revisionActions.set(rev, actionId);
|
|
341
|
+
blockState.committedActions.set(actionId, transform);
|
|
342
|
+
blockState.pendingActions.delete(actionId);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// --- End of Critical Section (Simulated) ---
|
|
346
|
+
|
|
347
|
+
return { success: true };
|
|
348
|
+
|
|
349
|
+
} finally {
|
|
350
|
+
// Release locks in reverse order
|
|
351
|
+
releases.reverse().forEach(release => release());
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Helper methods for testing
|
|
356
|
+
reset() {
|
|
357
|
+
this.blocks.clear();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
getPendingActions(): Map<ActionId, ActionTransforms> {
|
|
361
|
+
const allPending = new Map<ActionId, ActionTransforms>();
|
|
362
|
+
for (const [blockId, blockState] of this.blocks.entries()) {
|
|
363
|
+
for (const [actionId, transform] of blockState.pendingActions) {
|
|
364
|
+
const existing = allPending.get(actionId);
|
|
365
|
+
if (!existing) {
|
|
366
|
+
allPending.set(actionId, { actionId, transforms: transformsFromTransform(transform, blockId) });
|
|
367
|
+
} else {
|
|
368
|
+
existing.transforms = concatTransform(existing.transforms, blockId, transform);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return allPending;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
getCommittedActions(): Map<ActionId, ActionTransforms> {
|
|
376
|
+
const allCommitted = new Map<ActionId, ActionTransforms>();
|
|
377
|
+
for (const [blockId, blockState] of this.blocks.entries()) {
|
|
378
|
+
for (const [rev, actionId] of blockState.revisionActions) {
|
|
379
|
+
const transform = blockState.committedActions.get(actionId);
|
|
380
|
+
if (transform) {
|
|
381
|
+
const existing = allCommitted.get(actionId);
|
|
382
|
+
if (!existing) {
|
|
383
|
+
allCommitted.set(actionId, {
|
|
384
|
+
actionId,
|
|
385
|
+
rev,
|
|
386
|
+
transforms: transformsFromTransform(transform, blockId)
|
|
387
|
+
});
|
|
388
|
+
} else {
|
|
389
|
+
existing.transforms = concatTransform(existing.transforms, blockId, transform);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return allCommitted;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
setAvailable(available: boolean) {
|
|
398
|
+
this.available = available;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
checkAvailable() {
|
|
402
|
+
if (!this.available) {
|
|
403
|
+
throw new Error('Transactor is not available');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Optional method for querying cluster nominees (used in GATHER phase for multi-collection transactions) */
|
|
408
|
+
queryClusterNominees?: (blockId: BlockId) => Promise<ClusterNomineesResult>;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Wraps a {@link TestTransactor} and forces its commit phase to fail a bounded (or unbounded)
|
|
413
|
+
* number of times before delegating, so tests can exercise the sync retry / backoff / give-up
|
|
414
|
+
* path deterministically. pend/get/getStatus/cancel delegate unchanged, so the pend→commit→cancel
|
|
415
|
+
* round-trip runs exactly as in production.
|
|
416
|
+
*/
|
|
417
|
+
export class FlakyCommitTransactor implements ITransactor {
|
|
418
|
+
/** Number of commit() calls observed so far (across success and forced failure). */
|
|
419
|
+
commitAttempts = 0;
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @param inner delegate transactor
|
|
423
|
+
* @param failFirstN number of initial commit() calls to fail; Infinity to always fail
|
|
424
|
+
* @param reason the StaleFailure.reason returned on a forced failure
|
|
425
|
+
*/
|
|
426
|
+
constructor(
|
|
427
|
+
private readonly inner: TestTransactor,
|
|
428
|
+
private readonly failFirstN: number,
|
|
429
|
+
private readonly reason = 'forced stale',
|
|
430
|
+
) {}
|
|
431
|
+
|
|
432
|
+
get(b: BlockGets): Promise<GetBlockResults> { return this.inner.get(b); }
|
|
433
|
+
getStatus(a: ActionBlocks[]): Promise<BlockActionStatus[]> { return this.inner.getStatus(a); }
|
|
434
|
+
pend(r: PendRequest): Promise<PendResult> { return this.inner.pend(r); }
|
|
435
|
+
cancel(a: ActionBlocks): Promise<void> { return this.inner.cancel(a); }
|
|
436
|
+
|
|
437
|
+
async commit(request: CommitRequest): Promise<CommitResult> {
|
|
438
|
+
this.commitAttempts++;
|
|
439
|
+
if (this.commitAttempts <= this.failFirstN) {
|
|
440
|
+
return { success: false, reason: this.reason };
|
|
441
|
+
}
|
|
442
|
+
return this.inner.commit(request);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function newBlockState(): BlockState {
|
|
447
|
+
return {
|
|
448
|
+
materializedBlocks: new Map(),
|
|
449
|
+
latestRev: 0,
|
|
450
|
+
revisionActions: new Map(),
|
|
451
|
+
pendingActions: new Map(),
|
|
452
|
+
committedActions: new Map()
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** Returns the materialized block at the highest revision ≤ the given revision */
|
|
457
|
+
function latestMaterializedAt(blockState: BlockState, maxRev: number): IBlock | undefined {
|
|
458
|
+
for (let rev = maxRev; rev >= 0; rev--) {
|
|
459
|
+
const block = blockState.materializedBlocks.get(rev);
|
|
460
|
+
if (block) return block;
|
|
461
|
+
}
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function applyTransformSafe(block: IBlock | undefined, transform: Transform): IBlock | undefined {
|
|
466
|
+
if (!block) return undefined;
|
|
467
|
+
return applyTransform(structuredClone(block), transform);
|
|
468
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ITransactionEngine, Transaction, ExecutionResult, CollectionActions } from './transaction.js';
|
|
2
|
-
import type { TransactionCoordinator } from './coordinator.js';
|
|
3
2
|
|
|
4
3
|
export const ACTIONS_ENGINE_ID = "actions@1.0.0";
|
|
5
4
|
|
|
@@ -9,6 +8,11 @@ export const ACTIONS_ENGINE_ID = "actions@1.0.0";
|
|
|
9
8
|
* This engine treats each statement as a JSON-encoded CollectionActions object.
|
|
10
9
|
* It's useful for testing the transaction infrastructure without needing SQL.
|
|
11
10
|
*
|
|
11
|
+
* This is a PURE TRANSLATOR (model (a) of the {@link ITransactionEngine} contract):
|
|
12
|
+
* execute() parses statements into CollectionActions[] and RETURNS them. It never
|
|
13
|
+
* touches a coordinator or mutates any collection state — application is the sole
|
|
14
|
+
* responsibility of the session/coordinator that receives the returned actions.
|
|
15
|
+
*
|
|
12
16
|
* Each statement format:
|
|
13
17
|
* ```json
|
|
14
18
|
* {
|
|
@@ -20,7 +24,7 @@ export const ACTIONS_ENGINE_ID = "actions@1.0.0";
|
|
|
20
24
|
* ```
|
|
21
25
|
*/
|
|
22
26
|
export class ActionsEngine implements ITransactionEngine {
|
|
23
|
-
|
|
27
|
+
readonly id = ACTIONS_ENGINE_ID;
|
|
24
28
|
|
|
25
29
|
async execute(transaction: Transaction): Promise<ExecutionResult> {
|
|
26
30
|
try {
|
|
@@ -46,12 +50,10 @@ export class ActionsEngine implements ITransactionEngine {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
allActions.push(collectionActions);
|
|
49
|
-
|
|
50
|
-
// Apply actions through coordinator (for validation/replay)
|
|
51
|
-
await this.coordinator.applyActions([collectionActions], transaction.stamp.id);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
+
// Pure translation: return parsed actions WITHOUT applying them. The caller
|
|
56
|
+
// (session.execute / coordinator.execute) applies them exactly once.
|
|
55
57
|
return {
|
|
56
58
|
success: true,
|
|
57
59
|
actions: allActions
|