@optimystic/db-core 0.25.1 → 0.27.0
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/cohort-topic/membership/verifier.d.ts +8 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.js +17 -0
- package/dist/src/cohort-topic/membership/verifier.js.map +1 -1
- package/dist/src/cohort-topic/promotion.d.ts +25 -0
- package/dist/src/cohort-topic/promotion.d.ts.map +1 -1
- package/dist/src/cohort-topic/promotion.js +58 -11
- package/dist/src/cohort-topic/promotion.js.map +1 -1
- package/dist/src/cohort-topic/wire/primitives.d.ts +6 -0
- package/dist/src/cohort-topic/wire/primitives.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/primitives.js +6 -0
- package/dist/src/cohort-topic/wire/primitives.js.map +1 -1
- package/dist/src/cohort-topic/wire/types.d.ts +5 -0
- package/dist/src/cohort-topic/wire/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.js +10 -19
- package/dist/src/cohort-topic/wire/validate.js.map +1 -1
- package/dist/src/collection/collection.d.ts +8 -1
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +19 -3
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/matchmaking/wire.d.ts +2 -2
- package/dist/src/matchmaking/wire.d.ts.map +1 -1
- package/dist/src/matchmaking/wire.js +3 -3
- package/dist/src/matchmaking/wire.js.map +1 -1
- package/dist/src/network/struct.d.ts +5 -5
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/testing/index.d.ts +1 -0
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/index.js +1 -0
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/testing/refresh-probe.d.ts +31 -0
- package/dist/src/testing/refresh-probe.d.ts.map +1 -0
- package/dist/src/testing/refresh-probe.js +39 -0
- package/dist/src/testing/refresh-probe.js.map +1 -0
- package/dist/src/testing/test-transactor.d.ts +58 -5
- package/dist/src/testing/test-transactor.d.ts.map +1 -1
- package/dist/src/testing/test-transactor.js +87 -4
- package/dist/src/testing/test-transactor.js.map +1 -1
- package/dist/src/transaction/coordinator.d.ts +90 -6
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +311 -90
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transaction/errors.d.ts +42 -0
- package/dist/src/transaction/errors.d.ts.map +1 -1
- package/dist/src/transaction/errors.js +50 -0
- package/dist/src/transaction/errors.js.map +1 -1
- package/dist/src/transaction/index.d.ts +1 -1
- package/dist/src/transaction/index.d.ts.map +1 -1
- package/dist/src/transaction/index.js +1 -1
- package/dist/src/transaction/index.js.map +1 -1
- package/dist/src/transaction/session.d.ts +7 -3
- package/dist/src/transaction/session.d.ts.map +1 -1
- package/dist/src/transaction/session.js +7 -3
- package/dist/src/transaction/session.js.map +1 -1
- package/dist/src/transaction/transaction.d.ts +12 -2
- package/dist/src/transaction/transaction.d.ts.map +1 -1
- package/dist/src/transaction/transaction.js.map +1 -1
- package/dist/src/transactor/network-transactor.d.ts +1 -1
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +6 -26
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/dist/src/transactor/transactor-source.d.ts +4 -6
- package/dist/src/transactor/transactor-source.d.ts.map +1 -1
- package/dist/src/transactor/transactor-source.js +4 -6
- package/dist/src/transactor/transactor-source.js.map +1 -1
- package/dist/src/transform/atomic.d.ts.map +1 -1
- package/dist/src/transform/atomic.js +7 -0
- package/dist/src/transform/atomic.js.map +1 -1
- package/dist/src/transform/base-pins.d.ts +48 -0
- package/dist/src/transform/base-pins.d.ts.map +1 -0
- package/dist/src/transform/base-pins.js +65 -0
- package/dist/src/transform/base-pins.js.map +1 -0
- package/dist/src/transform/digest.d.ts +4 -2
- package/dist/src/transform/digest.d.ts.map +1 -1
- package/dist/src/transform/digest.js +14 -19
- package/dist/src/transform/digest.js.map +1 -1
- package/dist/src/transform/tracker.d.ts +31 -3
- package/dist/src/transform/tracker.d.ts.map +1 -1
- package/dist/src/transform/tracker.js +105 -13
- package/dist/src/transform/tracker.js.map +1 -1
- package/package.json +1 -1
- package/src/cohort-topic/membership/verifier.ts +487 -461
- package/src/cohort-topic/promotion.ts +86 -14
- package/src/cohort-topic/wire/primitives.ts +195 -188
- package/src/cohort-topic/wire/types.ts +480 -475
- package/src/cohort-topic/wire/validate.ts +504 -512
- package/src/collection/collection.ts +19 -3
- package/src/matchmaking/wire.ts +605 -603
- package/src/network/struct.ts +5 -5
- package/src/testing/index.ts +1 -0
- package/src/testing/refresh-probe.ts +52 -0
- package/src/testing/test-transactor.ts +100 -4
- package/src/transaction/coordinator.ts +312 -99
- package/src/transaction/errors.ts +141 -91
- package/src/transaction/index.ts +1 -1
- package/src/transaction/session.ts +7 -3
- package/src/transaction/transaction.ts +12 -2
- package/src/transactor/network-transactor.ts +1025 -1045
- package/src/transactor/transactor-source.ts +170 -172
- package/src/transform/atomic.ts +6 -0
- package/src/transform/base-pins.ts +83 -0
- package/src/transform/digest.ts +14 -19
- package/src/transform/tracker.ts +108 -15
package/src/network/struct.ts
CHANGED
|
@@ -125,12 +125,12 @@ export type BlockContentDigest = {
|
|
|
125
125
|
export type BlockContentDigests = Record<BlockId, BlockContentDigest>;
|
|
126
126
|
|
|
127
127
|
export type CommitRequest = ActionBlocks & {
|
|
128
|
-
/** The header block of the collection,
|
|
129
|
-
*
|
|
130
|
-
*
|
|
128
|
+
/** The header block of the collection, present only when this commit creates the collection
|
|
129
|
+
* (the header is a fresh insert). Collection-identifying metadata, not an ordering signal —
|
|
130
|
+
* the header commits inside the ordinary sweep, after the tail, like any other touched block.
|
|
131
|
+
* Consumed by dispute reporting (`dispute-service.ts`) as a fallback to name the collection. */
|
|
131
132
|
headerId?: BlockId;
|
|
132
|
-
/** The tail block of the log.
|
|
133
|
-
* fires in production, this is in practice the FIRST block committed, which is the ordering
|
|
133
|
+
/** The tail block of the log. Unconditionally the first block committed — the ordering
|
|
134
134
|
* `Collection.bootstrapContext` relies on. */
|
|
135
135
|
tailId: BlockId;
|
|
136
136
|
/** The new revision for the committed action */
|
package/src/testing/index.ts
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Collection } from '../collection/collection.js';
|
|
2
|
+
|
|
3
|
+
/** Yield the event loop through the MACROTASK queue `turns` times, so a refresh that is free to
|
|
4
|
+
* proceed gets every chance to run its (await-heavy) course before a gate opens. A microtask
|
|
5
|
+
* drain would not be enough: `Collection.update()` awaits real transactor reads.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: the turn count is what gives {@link releaseRefresh}'s `blocked()` assertions their teeth
|
|
8
|
+
* — an unlatched `Collection.update()` has to be able to run to COMPLETION within these turns,
|
|
9
|
+
* or "still pending" stops distinguishing "blocked on the latch" from "merely slow". Ten is a
|
|
10
|
+
* ~10x margin today: with the coordinator's latch reverted locally, the refreshes in
|
|
11
|
+
* `coordinator-latch-interleaving.spec.ts` completed on macrotask turn 1 against
|
|
12
|
+
* `TestTransactor`. If `update()` ever grows a deeper await chain — or a caller moves onto a
|
|
13
|
+
* transactor with real I/O — raise this rather than letting the assertion quietly weaken into a
|
|
14
|
+
* tautology. */
|
|
15
|
+
export async function drainMacrotasks(turns = 10): Promise<void> {
|
|
16
|
+
for (let i = 0; i < turns; i++) {
|
|
17
|
+
await new Promise(r => setTimeout(r, 0));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The handle {@link releaseRefresh} returns: `blocked()` reports whether the refresh is still
|
|
22
|
+
* pending, `settle()` awaits it and rethrows whatever it rejected with. */
|
|
23
|
+
export type RefreshProbe = {
|
|
24
|
+
/** Await the refresh, rethrowing its failure (captured at start time — see the note there). */
|
|
25
|
+
settle: () => Promise<void>;
|
|
26
|
+
/** True while the refresh has neither resolved nor rejected. */
|
|
27
|
+
blocked: () => boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Start a refresh on `collection` and report — via the returned `blocked()` — whether it is
|
|
31
|
+
* STILL pending.
|
|
32
|
+
*
|
|
33
|
+
* A final-state assertion alone would also pass if the refresh had simply run to completion
|
|
34
|
+
* harmlessly, so on its own it never proves the latch is what protected the state. This does:
|
|
35
|
+
* checked after {@link drainMacrotasks} but before a parked commit's gate opens, a still-pending
|
|
36
|
+
* refresh is a refresh queued behind the commit's held latch. */
|
|
37
|
+
export function releaseRefresh<TAction>(collection: Collection<TAction>): RefreshProbe {
|
|
38
|
+
let done = false;
|
|
39
|
+
let failure: unknown;
|
|
40
|
+
// Both handlers attach SYNCHRONOUSLY, and the rethrowing promise is built only when the caller
|
|
41
|
+
// asks for it. The refresh sits unawaited across drainMacrotasks, so a rejection reaching the
|
|
42
|
+
// end of a turn with no handler would be a fatal unhandled rejection — killing the process
|
|
43
|
+
// instead of failing the case. Captured here, rethrown at `settle()`.
|
|
44
|
+
const captured = collection.update().then(
|
|
45
|
+
() => { done = true; },
|
|
46
|
+
(e: unknown) => { done = true; failure = e; },
|
|
47
|
+
);
|
|
48
|
+
return {
|
|
49
|
+
settle: () => captured.then(() => { if (failure !== undefined) throw failure; }),
|
|
50
|
+
blocked: () => !done,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -4,6 +4,7 @@ import { Latches } from "../utility/latches.js";
|
|
|
4
4
|
import { applyTransform, blockIdsForTransforms, transformForBlockId, emptyTransforms, concatTransform, transformsFromTransform } from "../transform/index.js";
|
|
5
5
|
import { Tree } from "../collections/tree/tree.js";
|
|
6
6
|
import type { TreeReplaceAction } from "../collections/tree/struct.js";
|
|
7
|
+
import type { Collection } from "../collection/collection.js";
|
|
7
8
|
|
|
8
9
|
type RevisionNumber = number;
|
|
9
10
|
|
|
@@ -496,10 +497,10 @@ export class FlakyCommitTransactor extends DelegatingTransactor {
|
|
|
496
497
|
|
|
497
498
|
/**
|
|
498
499
|
* Commits durably on the inner {@link TestTransactor}, then reports a stale failure anyway —
|
|
499
|
-
* the exact observable shape of `NetworkTransactor.commit`'s TORN ACTION: the collection
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
500
|
+
* the exact observable shape of `NetworkTransactor.commit`'s TORN ACTION: the collection's log
|
|
501
|
+
* tail is committed BEFORE the sweep of the remaining blocks, so a later sweep block coming back
|
|
502
|
+
* as a confirmed conflict returns a stale failure over an action whose log entry is already
|
|
503
|
+
* durable.
|
|
503
504
|
*
|
|
504
505
|
* Safe against the inner transactor's bookkeeping because `TransactorSource.transact` cancels
|
|
505
506
|
* the pend on the reported failure and {@link TestTransactor.cancel} only deletes PENDING records
|
|
@@ -617,6 +618,101 @@ export class CompetingWriterTransactor extends DelegatingTransactor {
|
|
|
617
618
|
}
|
|
618
619
|
}
|
|
619
620
|
|
|
621
|
+
type Deferred = { promise: Promise<void>; resolve: () => void };
|
|
622
|
+
|
|
623
|
+
function deferred(): Deferred {
|
|
624
|
+
let resolve!: () => void;
|
|
625
|
+
const promise = new Promise<void>(r => { resolve = r; });
|
|
626
|
+
return { promise, resolve };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Parks the commit AFTER the inner transactor has made it durable and BEFORE the result gets back
|
|
631
|
+
* to the coordinator — precisely the window in which storage already holds the new revision but
|
|
632
|
+
* the caller's local fold (`Collection.recordCommitted`) has not run.
|
|
633
|
+
*
|
|
634
|
+
* Everything else delegates, so the pend/commit round trip is otherwise the real one.
|
|
635
|
+
*
|
|
636
|
+
* Parks only the FIRST commit call and delegates every later one: a multi-collection commit issues
|
|
637
|
+
* one `commit` per participant, and parking all of them would mean the span never completes.
|
|
638
|
+
*/
|
|
639
|
+
export class GatedCommitTransactor extends DelegatingTransactor {
|
|
640
|
+
/** The `rev` carried on each pend request seen, in call order. */
|
|
641
|
+
readonly pendRevs: (number | undefined)[] = [];
|
|
642
|
+
/** The `rev` carried on each commit request seen, in call order. */
|
|
643
|
+
readonly commitRevs: number[] = [];
|
|
644
|
+
/** Resolves once a commit has landed durably in the inner transactor and is parked. */
|
|
645
|
+
readonly commitParked: Promise<void>;
|
|
646
|
+
private readonly parked = deferred();
|
|
647
|
+
private readonly gate = deferred();
|
|
648
|
+
private parkedOnce = false;
|
|
649
|
+
|
|
650
|
+
constructor(inner: TestTransactor) {
|
|
651
|
+
super(inner);
|
|
652
|
+
this.commitParked = this.parked.promise;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
override async pend(request: PendRequest): Promise<PendResult> {
|
|
656
|
+
this.pendRevs.push(request.rev);
|
|
657
|
+
return this.inner.pend(request);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
override async commit(request: CommitRequest): Promise<CommitResult> {
|
|
661
|
+
this.commitRevs.push(request.rev);
|
|
662
|
+
const result = await this.inner.commit(request); // durable FIRST
|
|
663
|
+
if (!this.parkedOnce) {
|
|
664
|
+
this.parkedOnce = true;
|
|
665
|
+
this.parked.resolve();
|
|
666
|
+
await this.gate.promise;
|
|
667
|
+
}
|
|
668
|
+
return result;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
openGate(): void { this.gate.resolve(); }
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Parks the FIRST pend BEFORE it reaches the inner transactor, so a rival can durably take the
|
|
676
|
+
* revision while the coordinator is already committed to a set of transforms it captured at the
|
|
677
|
+
* log append.
|
|
678
|
+
*
|
|
679
|
+
* Snapshots the pair `(what was handed to the network, what the collection's tracker held)` at
|
|
680
|
+
* DELEGATION time — after the gate — for every pend, so a refresh that swapped the tracker's
|
|
681
|
+
* transforms object out from under the coordinator shows up as a divergent pair.
|
|
682
|
+
*/
|
|
683
|
+
export class GatedPendTransactor<TAction> extends DelegatingTransactor {
|
|
684
|
+
/** One entry per delegated pend, in call order. */
|
|
685
|
+
readonly snapshots: { pended: Transforms; staged: Transforms }[] = [];
|
|
686
|
+
/** Resolves once a pend is parked and has NOT yet reached the inner transactor. */
|
|
687
|
+
readonly pendParked: Promise<void>;
|
|
688
|
+
/** The collection under test. Assigned after construction — the collection is built over this
|
|
689
|
+
* wrapper, so it cannot be a constructor argument. */
|
|
690
|
+
collection?: Collection<TAction>;
|
|
691
|
+
private readonly parked = deferred();
|
|
692
|
+
private readonly gate = deferred();
|
|
693
|
+
private parkedOnce = false;
|
|
694
|
+
|
|
695
|
+
constructor(inner: TestTransactor) {
|
|
696
|
+
super(inner);
|
|
697
|
+
this.pendParked = this.parked.promise;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
override async pend(request: PendRequest): Promise<PendResult> {
|
|
701
|
+
if (!this.parkedOnce) {
|
|
702
|
+
this.parkedOnce = true;
|
|
703
|
+
this.parked.resolve();
|
|
704
|
+
await this.gate.promise;
|
|
705
|
+
}
|
|
706
|
+
this.snapshots.push({
|
|
707
|
+
pended: structuredClone(request.transforms),
|
|
708
|
+
staged: structuredClone(this.collection!.tracker.transforms),
|
|
709
|
+
});
|
|
710
|
+
return this.inner.pend(request);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
openGate(): void { this.gate.resolve(); }
|
|
714
|
+
}
|
|
715
|
+
|
|
620
716
|
/**
|
|
621
717
|
* Durably commit a conflicting change to a tree collection: opens a SECOND {@link Tree} over the
|
|
622
718
|
* same transactor + collection id and replaces `entries` through it, producing a real log entry
|