@peerbit/shared-log 16.0.16 → 16.0.18
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/index.d.ts +15 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +482 -50
- package/dist/src/index.js.map +1 -1
- package/package.json +9 -9
- package/src/index.ts +616 -61
package/src/index.ts
CHANGED
|
@@ -688,8 +688,13 @@ type PreparedLocalAppendCommit<R extends "u32" | "u64"> = {
|
|
|
688
688
|
next: string[];
|
|
689
689
|
wallTime: bigint;
|
|
690
690
|
logical: number;
|
|
691
|
+
clockId?: Uint8Array;
|
|
692
|
+
type?: EntryType;
|
|
693
|
+
metaData?: Uint8Array;
|
|
691
694
|
payloadSize: number;
|
|
695
|
+
entrySize?: number;
|
|
692
696
|
metaBytes?: Uint8Array;
|
|
697
|
+
storageBytes?: Uint8Array;
|
|
693
698
|
hashNumber?: NumberFromType<R>;
|
|
694
699
|
coordinateFields?: SharedLogCoordinateNativeFields<R>;
|
|
695
700
|
nativeBackboneDocumentIndexCommitted?: boolean;
|
|
@@ -708,6 +713,12 @@ type PersistedDeliveryPlanningEntry<T, R extends "u32" | "u64"> =
|
|
|
708
713
|
| ShallowOrFullEntry<T>
|
|
709
714
|
| EntryReplicated<R>;
|
|
710
715
|
|
|
716
|
+
type PersistedDeliveryPlanningRecord<T, R extends "u32" | "u64"> = Readonly<{
|
|
717
|
+
canonicalHash: string;
|
|
718
|
+
createDefaultPlanningSource: () => PersistedDeliveryPlanningEntry<T, R>;
|
|
719
|
+
createFullPlanningSource?: () => Entry<T>;
|
|
720
|
+
}>;
|
|
721
|
+
|
|
711
722
|
type NativeBackboneSimpleDocumentProjectionPlan = {
|
|
712
723
|
documentVariantType?: "u8" | "string";
|
|
713
724
|
documentVariantValue?: string;
|
|
@@ -1733,6 +1744,20 @@ export type DeliveryOptions = {
|
|
|
1733
1744
|
signal?: AbortSignal;
|
|
1734
1745
|
};
|
|
1735
1746
|
|
|
1747
|
+
type PersistedDeliveryOptions = Readonly<{
|
|
1748
|
+
reliability: "persisted";
|
|
1749
|
+
minAcks: number;
|
|
1750
|
+
requireRecipients?: boolean;
|
|
1751
|
+
priority?: number;
|
|
1752
|
+
timeout?: number;
|
|
1753
|
+
signal?: AbortSignal;
|
|
1754
|
+
}>;
|
|
1755
|
+
|
|
1756
|
+
type PersistedAppendInvocation<T> = {
|
|
1757
|
+
options: SharedAppendOptions<T>;
|
|
1758
|
+
delivery: PersistedDeliveryOptions;
|
|
1759
|
+
};
|
|
1760
|
+
|
|
1736
1761
|
type CrashSafeStorageBarrier = {
|
|
1737
1762
|
readonly crashSafe: true;
|
|
1738
1763
|
barrier(): MaybePromise<void>;
|
|
@@ -1859,7 +1884,10 @@ type SharedAppendBaseOptions<T> = AppendOptions<T> & {
|
|
|
1859
1884
|
|
|
1860
1885
|
type TrustedLogAppendOptions<T> = AppendOptions<T> & {
|
|
1861
1886
|
__peerbitCanAppendAlreadyValidated?: boolean;
|
|
1862
|
-
__peerbitOnLocalCommit?: (
|
|
1887
|
+
__peerbitOnLocalCommit?: (
|
|
1888
|
+
hashes: readonly string[],
|
|
1889
|
+
entries?: readonly Entry<T>[],
|
|
1890
|
+
) => void;
|
|
1863
1891
|
};
|
|
1864
1892
|
|
|
1865
1893
|
const attachTrustedLocalCommitEvidence = <T>(
|
|
@@ -1886,6 +1914,11 @@ export type SharedAppendOptions<T> =
|
|
|
1886
1914
|
delivery?: false | undefined;
|
|
1887
1915
|
});
|
|
1888
1916
|
|
|
1917
|
+
type DocumentSharedAppendOptions<T> = SharedAppendOptions<T> & {
|
|
1918
|
+
unique?: boolean;
|
|
1919
|
+
checkRemote?: boolean;
|
|
1920
|
+
};
|
|
1921
|
+
|
|
1889
1922
|
type TrustedLowerLogAppendHashesSink = (
|
|
1890
1923
|
hashes: string[],
|
|
1891
1924
|
) => void | Promise<void>;
|
|
@@ -2006,6 +2039,9 @@ type TrustedLowerLogNativeCommitFinalizer = {
|
|
|
2006
2039
|
};
|
|
2007
2040
|
|
|
2008
2041
|
type TrustedLowerLog<T> = {
|
|
2042
|
+
snapshotAppendEncryptionForTrustedCaller(
|
|
2043
|
+
encryption: NonNullable<AppendOptions<T>["encryption"]>,
|
|
2044
|
+
): NonNullable<AppendOptions<T>["encryption"]>;
|
|
2009
2045
|
appendLocallyPrepared(
|
|
2010
2046
|
data: T,
|
|
2011
2047
|
options?: TrustedLogAppendOptions<T>,
|
|
@@ -4492,40 +4528,369 @@ export class SharedLog<
|
|
|
4492
4528
|
return true;
|
|
4493
4529
|
}
|
|
4494
4530
|
|
|
4495
|
-
private
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4531
|
+
private snapshotDeliveryOptions(
|
|
4532
|
+
deliveryArgument: DeliveryOptions,
|
|
4533
|
+
reliability: DeliveryReliability | undefined,
|
|
4534
|
+
): Readonly<DeliveryOptions> {
|
|
4535
|
+
// Keep the selected AbortSignal object live: aborting it remains effective,
|
|
4536
|
+
// while reassigning any caller field cannot change this invocation.
|
|
4537
|
+
return Object.freeze({
|
|
4538
|
+
reliability,
|
|
4539
|
+
minAcks: deliveryArgument.minAcks,
|
|
4540
|
+
requireRecipients: deliveryArgument.requireRecipients,
|
|
4541
|
+
priority: deliveryArgument.priority,
|
|
4542
|
+
timeout: deliveryArgument.timeout,
|
|
4543
|
+
signal: deliveryArgument.signal,
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
|
|
4547
|
+
private snapshotAppendTrim(
|
|
4548
|
+
trim: NonNullable<AppendOptions<T>["trim"]>,
|
|
4549
|
+
): NonNullable<AppendOptions<T>["trim"]> {
|
|
4550
|
+
const type = trim.type;
|
|
4551
|
+
const filterArgument = trim.filter;
|
|
4552
|
+
const filter = filterArgument
|
|
4553
|
+
? Object.freeze({
|
|
4554
|
+
canTrim: filterArgument.canTrim,
|
|
4555
|
+
cacheId: filterArgument.cacheId,
|
|
4556
|
+
})
|
|
4557
|
+
: undefined;
|
|
4558
|
+
if (type === "time") {
|
|
4559
|
+
return Object.freeze({ type, maxAge: trim.maxAge, filter });
|
|
4560
|
+
}
|
|
4561
|
+
if (type === "length" || type === "bytelength") {
|
|
4562
|
+
return Object.freeze({
|
|
4563
|
+
type,
|
|
4564
|
+
to: trim.to,
|
|
4565
|
+
from: trim.from,
|
|
4566
|
+
filter,
|
|
4567
|
+
});
|
|
4568
|
+
}
|
|
4569
|
+
throw new Error("Unsupported append trim type");
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4572
|
+
private snapshotAppendEncryption(
|
|
4573
|
+
encryption: NonNullable<AppendOptions<T>["encryption"]>,
|
|
4574
|
+
): NonNullable<AppendOptions<T>["encryption"]> {
|
|
4575
|
+
return asTrustedLowerLog(this.log).snapshotAppendEncryptionForTrustedCaller(
|
|
4576
|
+
encryption,
|
|
4577
|
+
);
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
private captureFullAppendNextStorageReader(
|
|
4581
|
+
next: Entry<any> | ShallowEntry,
|
|
4582
|
+
): (() => Uint8Array) | undefined {
|
|
4583
|
+
if (next instanceof Entry) {
|
|
4584
|
+
const getStorageBytes = next.getStorageBytes;
|
|
4585
|
+
return () => Reflect.apply(getStorageBytes, next, []);
|
|
4586
|
+
}
|
|
4587
|
+
const candidate = next as Partial<Entry<any>>;
|
|
4588
|
+
const init = candidate.init;
|
|
4589
|
+
const getNext = candidate.getNext;
|
|
4590
|
+
const getClock = candidate.getClock;
|
|
4591
|
+
const getStorageBytes = candidate.getStorageBytes;
|
|
4592
|
+
const verifySignatures = candidate.verifySignatures;
|
|
4593
|
+
if (
|
|
4594
|
+
typeof init !== "function" ||
|
|
4595
|
+
typeof getNext !== "function" ||
|
|
4596
|
+
typeof getClock !== "function" ||
|
|
4597
|
+
typeof getStorageBytes !== "function" ||
|
|
4598
|
+
typeof verifySignatures !== "function"
|
|
4599
|
+
) {
|
|
4600
|
+
return;
|
|
4601
|
+
}
|
|
4602
|
+
return () => Reflect.apply(getStorageBytes, next, []);
|
|
4603
|
+
}
|
|
4604
|
+
|
|
4605
|
+
private snapshotFullAppendNext(
|
|
4606
|
+
next: Entry<any>,
|
|
4607
|
+
getStorageBytes: () => Uint8Array,
|
|
4608
|
+
canonicalHash: string,
|
|
4609
|
+
): Entry<any> | undefined {
|
|
4610
|
+
let entrySize: number | undefined;
|
|
4611
|
+
try {
|
|
4612
|
+
entrySize = next.size;
|
|
4613
|
+
} catch {
|
|
4614
|
+
// A valid but not-yet-sized entry still has exact owned storage bytes below.
|
|
4501
4615
|
}
|
|
4502
|
-
const
|
|
4503
|
-
|
|
4616
|
+
const createdLocally = next.createdLocally;
|
|
4617
|
+
let sourceBytes = Entry.getPreparedStorageBytes(next);
|
|
4618
|
+
if (!sourceBytes) {
|
|
4619
|
+
try {
|
|
4620
|
+
sourceBytes = getStorageBytes();
|
|
4621
|
+
} catch {
|
|
4622
|
+
// Native commit-only entries intentionally keep payload/signature bytes
|
|
4623
|
+
// outside the JS Entry. They are valid sortable parents but cannot be
|
|
4624
|
+
// imported into another log as full blocks, so snapshot them below as
|
|
4625
|
+
// shallow canonical references instead.
|
|
4626
|
+
return;
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
const bytes = Uint8Array.from(sourceBytes);
|
|
4630
|
+
const captured = deserialize(bytes, Entry) as Entry<any>;
|
|
4631
|
+
const decodedHash = captured.hash;
|
|
4632
|
+
if (decodedHash && decodedHash !== canonicalHash) {
|
|
4633
|
+
throw new Error(
|
|
4634
|
+
`Explicit append next bytes did not match captured hash ${canonicalHash}`,
|
|
4635
|
+
);
|
|
4636
|
+
}
|
|
4637
|
+
captured.hash = "";
|
|
4638
|
+
Entry.prepareMultihashBytes(captured, bytes, canonicalHash);
|
|
4639
|
+
captured.hash = canonicalHash;
|
|
4640
|
+
captured.size = entrySize ?? bytes.byteLength;
|
|
4641
|
+
captured.createdLocally = createdLocally;
|
|
4642
|
+
captured.init({
|
|
4643
|
+
encoding: this.log.encoding,
|
|
4644
|
+
keychain: this.log.keychain,
|
|
4645
|
+
});
|
|
4646
|
+
return captured;
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
private snapshotAppendNext(
|
|
4650
|
+
next: Entry<any> | ShallowEntry,
|
|
4651
|
+
): Entry<any> | ShallowEntry {
|
|
4652
|
+
const hash = next.hash;
|
|
4653
|
+
if (!hash) {
|
|
4654
|
+
throw new Error("Explicit append next requires a canonical hash");
|
|
4655
|
+
}
|
|
4656
|
+
const getStorageBytes = this.captureFullAppendNextStorageReader(next);
|
|
4657
|
+
if (getStorageBytes) {
|
|
4658
|
+
const captured = this.snapshotFullAppendNext(
|
|
4659
|
+
next as Entry<any>,
|
|
4660
|
+
getStorageBytes,
|
|
4661
|
+
hash,
|
|
4662
|
+
);
|
|
4663
|
+
if (captured) return captured;
|
|
4664
|
+
}
|
|
4665
|
+
const meta = next.meta;
|
|
4666
|
+
const gid = meta.gid;
|
|
4667
|
+
const nextHashes = [...meta.next];
|
|
4668
|
+
const type = meta.type;
|
|
4669
|
+
const sourceData = meta.data;
|
|
4670
|
+
const data = sourceData && Uint8Array.from(sourceData);
|
|
4671
|
+
const clock = meta.clock;
|
|
4672
|
+
const clockId = Uint8Array.from(clock.id);
|
|
4673
|
+
const sourceTimestamp = clock.timestamp;
|
|
4674
|
+
const timestamp = new Timestamp({
|
|
4675
|
+
wallTime: sourceTimestamp.wallTime,
|
|
4676
|
+
logical: sourceTimestamp.logical,
|
|
4677
|
+
});
|
|
4678
|
+
const candidate = next as ShallowEntry & { payloadByteLength?: number };
|
|
4679
|
+
let payloadSize = 0;
|
|
4680
|
+
try {
|
|
4681
|
+
const shallowPayloadSize = candidate.payloadSize;
|
|
4682
|
+
if (typeof shallowPayloadSize === "number") {
|
|
4683
|
+
payloadSize = shallowPayloadSize;
|
|
4684
|
+
} else {
|
|
4685
|
+
const fullPayloadSize = candidate.payloadByteLength;
|
|
4686
|
+
if (typeof fullPayloadSize === "number") payloadSize = fullPayloadSize;
|
|
4687
|
+
}
|
|
4688
|
+
} catch {
|
|
4689
|
+
// Hollow native parents need only their canonical sorting/link facts.
|
|
4690
|
+
}
|
|
4691
|
+
const candidateHead = candidate.head;
|
|
4692
|
+
const head = typeof candidateHead === "boolean" ? candidateHead : true;
|
|
4693
|
+
const captured = new ShallowEntry({
|
|
4694
|
+
hash,
|
|
4695
|
+
payloadSize,
|
|
4696
|
+
head,
|
|
4697
|
+
meta: new ShallowMeta({
|
|
4698
|
+
gid,
|
|
4699
|
+
next: nextHashes,
|
|
4700
|
+
type,
|
|
4701
|
+
data,
|
|
4702
|
+
clock: new LamportClock({ id: clockId, timestamp }),
|
|
4703
|
+
}),
|
|
4704
|
+
});
|
|
4705
|
+
Object.freeze(captured.meta.next);
|
|
4706
|
+
Object.freeze(captured.meta.clock.timestamp);
|
|
4707
|
+
Object.freeze(captured.meta.clock);
|
|
4708
|
+
Object.freeze(captured.meta);
|
|
4709
|
+
return Object.freeze(captured);
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4712
|
+
private snapshotSupportedAppendOptions(
|
|
4713
|
+
options: DocumentSharedAppendOptions<T>,
|
|
4714
|
+
delivery: SharedAppendOptions<T>["delivery"],
|
|
4715
|
+
includeDocumentShape: boolean,
|
|
4716
|
+
validateCapturedOptions?: (options: DocumentSharedAppendOptions<T>) => void,
|
|
4717
|
+
): DocumentSharedAppendOptions<T> {
|
|
4718
|
+
// Copy the finite public option surface, not arbitrary enumerable caller
|
|
4719
|
+
// properties. This pins every value that the async append/document paths
|
|
4720
|
+
// reread while avoiding surprising evaluation of unrelated custom getters.
|
|
4721
|
+
const captured = Object.create(null) as DocumentSharedAppendOptions<T>;
|
|
4722
|
+
const durability = options.durability;
|
|
4723
|
+
const deferIndexWrite = options.deferIndexWrite;
|
|
4724
|
+
const meta = options.meta;
|
|
4725
|
+
const identity = options.identity;
|
|
4726
|
+
const signers = options.signers;
|
|
4727
|
+
const trim = options.trim;
|
|
4728
|
+
const encryption = options.encryption;
|
|
4729
|
+
const onChange = options.onChange;
|
|
4730
|
+
const canAppend = options.canAppend;
|
|
4731
|
+
const replicas = options.replicas;
|
|
4732
|
+
const replicate = options.replicate;
|
|
4733
|
+
const target = options.target;
|
|
4734
|
+
const unique = includeDocumentShape ? options.unique : undefined;
|
|
4735
|
+
const checkRemote = includeDocumentShape ? options.checkRemote : undefined;
|
|
4736
|
+
|
|
4737
|
+
if (durability !== undefined) captured.durability = durability;
|
|
4738
|
+
if (deferIndexWrite !== undefined) {
|
|
4739
|
+
captured.deferIndexWrite = deferIndexWrite;
|
|
4740
|
+
}
|
|
4741
|
+
if (meta !== undefined) {
|
|
4742
|
+
const capturedMetaInput = Object.create(null) as NonNullable<
|
|
4743
|
+
SharedAppendOptions<T>["meta"]
|
|
4744
|
+
>;
|
|
4745
|
+
const type = meta.type;
|
|
4746
|
+
const gidSeed = meta.gidSeed;
|
|
4747
|
+
const hasData = "data" in meta;
|
|
4748
|
+
const data = meta.data;
|
|
4749
|
+
const timestamp = meta.timestamp;
|
|
4750
|
+
const next = meta.next;
|
|
4751
|
+
if (type !== undefined) capturedMetaInput.type = type;
|
|
4752
|
+
if (gidSeed !== undefined) capturedMetaInput.gidSeed = gidSeed;
|
|
4753
|
+
if (hasData) capturedMetaInput.data = data;
|
|
4754
|
+
if (timestamp !== undefined) capturedMetaInput.timestamp = timestamp;
|
|
4755
|
+
if (next !== undefined) capturedMetaInput.next = next;
|
|
4756
|
+
captured.meta = capturedMetaInput;
|
|
4757
|
+
}
|
|
4758
|
+
if (identity !== undefined) captured.identity = identity;
|
|
4759
|
+
if (signers !== undefined) captured.signers = signers;
|
|
4760
|
+
if (trim !== undefined) captured.trim = trim;
|
|
4761
|
+
if (encryption !== undefined) captured.encryption = encryption;
|
|
4762
|
+
if (onChange !== undefined) captured.onChange = onChange;
|
|
4763
|
+
if (canAppend !== undefined) captured.canAppend = canAppend;
|
|
4764
|
+
if (replicas !== undefined) captured.replicas = replicas;
|
|
4765
|
+
if (replicate !== undefined) captured.replicate = replicate;
|
|
4766
|
+
if (target !== undefined) captured.target = target;
|
|
4767
|
+
if (delivery !== undefined) captured.delivery = delivery;
|
|
4768
|
+
if (includeDocumentShape) {
|
|
4769
|
+
if (unique !== undefined) captured.unique = unique;
|
|
4770
|
+
if (checkRemote !== undefined) captured.checkRemote = checkRemote;
|
|
4771
|
+
}
|
|
4772
|
+
// Strict-native Documents validates the already-captured finite surface
|
|
4773
|
+
// before any accepted nested value is cloned or normalized. This keeps its
|
|
4774
|
+
// mode error stable even for malformed unsupported option values, without
|
|
4775
|
+
// rereading caller-owned top-level fields.
|
|
4776
|
+
validateCapturedOptions?.(captured);
|
|
4777
|
+
if (meta !== undefined) {
|
|
4778
|
+
const capturedMetaInput = captured.meta!;
|
|
4779
|
+
const capturedMeta = Object.create(null) as NonNullable<
|
|
4780
|
+
SharedAppendOptions<T>["meta"]
|
|
4781
|
+
>;
|
|
4782
|
+
if (capturedMetaInput.type !== undefined) {
|
|
4783
|
+
capturedMeta.type = capturedMetaInput.type;
|
|
4784
|
+
}
|
|
4785
|
+
if (capturedMetaInput.gidSeed !== undefined) {
|
|
4786
|
+
capturedMeta.gidSeed = Uint8Array.from(capturedMetaInput.gidSeed);
|
|
4787
|
+
}
|
|
4788
|
+
if ("data" in capturedMetaInput) {
|
|
4789
|
+
capturedMeta.data =
|
|
4790
|
+
capturedMetaInput.data && Uint8Array.from(capturedMetaInput.data);
|
|
4791
|
+
}
|
|
4792
|
+
if (capturedMetaInput.timestamp !== undefined) {
|
|
4793
|
+
capturedMeta.timestamp = capturedMetaInput.timestamp.clone();
|
|
4794
|
+
}
|
|
4795
|
+
if (capturedMetaInput.next !== undefined) {
|
|
4796
|
+
capturedMeta.next = Object.freeze(
|
|
4797
|
+
capturedMetaInput.next.map((entry) => this.snapshotAppendNext(entry)),
|
|
4798
|
+
) as unknown as Entry<any>[] | ShallowEntry[];
|
|
4799
|
+
}
|
|
4800
|
+
captured.meta = capturedMeta;
|
|
4801
|
+
}
|
|
4802
|
+
if (signers !== undefined) captured.signers = [...signers];
|
|
4803
|
+
if (trim !== undefined) captured.trim = this.snapshotAppendTrim(trim);
|
|
4804
|
+
if (encryption !== undefined) {
|
|
4805
|
+
captured.encryption = this.snapshotAppendEncryption(encryption);
|
|
4806
|
+
}
|
|
4807
|
+
if (replicas !== undefined) {
|
|
4808
|
+
captured.replicas =
|
|
4809
|
+
typeof replicas === "number"
|
|
4810
|
+
? replicas
|
|
4811
|
+
: new AbsoluteReplicas(replicas.getValue(this));
|
|
4812
|
+
}
|
|
4813
|
+
return captured;
|
|
4814
|
+
}
|
|
4815
|
+
|
|
4816
|
+
private validatePersistedAppendInvocation(
|
|
4817
|
+
capturedOptions: SharedAppendOptions<T>,
|
|
4818
|
+
delivery: PersistedDeliveryOptions,
|
|
4819
|
+
): void {
|
|
4820
|
+
this._parseDeliveryOptions(delivery);
|
|
4821
|
+
const target = capturedOptions.target;
|
|
4504
4822
|
if (target !== undefined && target !== "replicators") {
|
|
4505
4823
|
throw new Error(
|
|
4506
4824
|
'persisted delivery requires target="replicators" (or an omitted target)',
|
|
4507
4825
|
);
|
|
4508
4826
|
}
|
|
4509
4827
|
if (
|
|
4510
|
-
|
|
4511
|
-
(!Number.isFinite(
|
|
4512
|
-
|
|
4513
|
-
|
|
4828
|
+
delivery.timeout != null &&
|
|
4829
|
+
(!Number.isFinite(delivery.timeout) ||
|
|
4830
|
+
delivery.timeout <= 0 ||
|
|
4831
|
+
delivery.timeout > MAX_PERSISTED_DELIVERY_TIMEOUT_MS)
|
|
4514
4832
|
) {
|
|
4515
4833
|
throw new Error(
|
|
4516
4834
|
`persisted delivery timeout must be a positive number no greater than ${MAX_PERSISTED_DELIVERY_TIMEOUT_MS}`,
|
|
4517
4835
|
);
|
|
4518
4836
|
}
|
|
4519
|
-
if (
|
|
4520
|
-
throw
|
|
4837
|
+
if (delivery.signal?.aborted) {
|
|
4838
|
+
throw delivery.signal.reason ?? new AbortError();
|
|
4521
4839
|
}
|
|
4522
|
-
return parsed.delivery!;
|
|
4523
4840
|
}
|
|
4524
4841
|
|
|
4525
|
-
private
|
|
4842
|
+
private capturePersistedAppendInvocation(
|
|
4526
4843
|
options?: SharedAppendOptions<T>,
|
|
4527
|
-
):
|
|
4528
|
-
|
|
4844
|
+
): PersistedAppendInvocation<T> | undefined {
|
|
4845
|
+
const deliveryArgument = options?.delivery;
|
|
4846
|
+
if (typeof deliveryArgument !== "object" || deliveryArgument === null) {
|
|
4847
|
+
return undefined;
|
|
4848
|
+
}
|
|
4849
|
+
const reliability = deliveryArgument.reliability;
|
|
4850
|
+
if (reliability !== "persisted") {
|
|
4851
|
+
return undefined;
|
|
4852
|
+
}
|
|
4853
|
+
const delivery = this.snapshotDeliveryOptions(
|
|
4854
|
+
deliveryArgument,
|
|
4855
|
+
reliability,
|
|
4856
|
+
) as PersistedDeliveryOptions;
|
|
4857
|
+
const capturedOptions = this.snapshotSupportedAppendOptions(
|
|
4858
|
+
options!,
|
|
4859
|
+
delivery,
|
|
4860
|
+
false,
|
|
4861
|
+
);
|
|
4862
|
+
this.validatePersistedAppendInvocation(capturedOptions, delivery);
|
|
4863
|
+
return { options: capturedOptions, delivery };
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4866
|
+
private snapshotDocumentAppendOptions(
|
|
4867
|
+
options?: DocumentSharedAppendOptions<T>,
|
|
4868
|
+
validateCapturedOptions?: (options: DocumentSharedAppendOptions<T>) => void,
|
|
4869
|
+
): DocumentSharedAppendOptions<T> | undefined {
|
|
4870
|
+
if (!options) return;
|
|
4871
|
+
const deliveryArgument = options.delivery;
|
|
4872
|
+
let delivery: SharedAppendOptions<T>["delivery"] = deliveryArgument;
|
|
4873
|
+
if (typeof deliveryArgument === "object" && deliveryArgument !== null) {
|
|
4874
|
+
const reliability = deliveryArgument.reliability;
|
|
4875
|
+
delivery = this.snapshotDeliveryOptions(deliveryArgument, reliability);
|
|
4876
|
+
}
|
|
4877
|
+
const capturedOptions = this.snapshotSupportedAppendOptions(
|
|
4878
|
+
options,
|
|
4879
|
+
delivery,
|
|
4880
|
+
true,
|
|
4881
|
+
validateCapturedOptions,
|
|
4882
|
+
);
|
|
4883
|
+
if (
|
|
4884
|
+
typeof delivery === "object" &&
|
|
4885
|
+
delivery !== null &&
|
|
4886
|
+
delivery.reliability === "persisted"
|
|
4887
|
+
) {
|
|
4888
|
+
this.validatePersistedAppendInvocation(
|
|
4889
|
+
capturedOptions,
|
|
4890
|
+
delivery as PersistedDeliveryOptions,
|
|
4891
|
+
);
|
|
4892
|
+
}
|
|
4893
|
+
return capturedOptions;
|
|
4529
4894
|
}
|
|
4530
4895
|
|
|
4531
4896
|
private async _getSortedRouteHints(targetHash: string): Promise<RouteHint[]> {
|
|
@@ -5217,7 +5582,7 @@ export class SharedLog<
|
|
|
5217
5582
|
}
|
|
5218
5583
|
|
|
5219
5584
|
private persistedDeliveryTimeoutMs(
|
|
5220
|
-
delivery:
|
|
5585
|
+
delivery: PersistedDeliveryOptions,
|
|
5221
5586
|
hashCount: number,
|
|
5222
5587
|
): number {
|
|
5223
5588
|
return (
|
|
@@ -5263,7 +5628,7 @@ export class SharedLog<
|
|
|
5263
5628
|
}
|
|
5264
5629
|
|
|
5265
5630
|
private createPersistedDeliveryDeadline(
|
|
5266
|
-
delivery:
|
|
5631
|
+
delivery: PersistedDeliveryOptions,
|
|
5267
5632
|
ownershipLifecycleController: AbortController,
|
|
5268
5633
|
hashCount = 1,
|
|
5269
5634
|
): PersistedDeliveryDeadline {
|
|
@@ -5304,7 +5669,7 @@ export class SharedLog<
|
|
|
5304
5669
|
}
|
|
5305
5670
|
|
|
5306
5671
|
private async planPersistedDeliveryLeaders(
|
|
5307
|
-
|
|
5672
|
+
records: PersistedDeliveryPlanningRecord<T, R>[],
|
|
5308
5673
|
replicas: number,
|
|
5309
5674
|
ownershipLifecycleController: AbortController,
|
|
5310
5675
|
): Promise<LeaderMap[]> {
|
|
@@ -5312,7 +5677,13 @@ export class SharedLog<
|
|
|
5312
5677
|
this.findLeadersFromEntry !== SharedLog.prototype.findLeadersFromEntry
|
|
5313
5678
|
) {
|
|
5314
5679
|
const leaders: LeaderMap[] = [];
|
|
5315
|
-
for (const
|
|
5680
|
+
for (const record of records) {
|
|
5681
|
+
const entry = record.createFullPlanningSource?.();
|
|
5682
|
+
if (!entry) {
|
|
5683
|
+
throw new Error(
|
|
5684
|
+
`Persisted delivery requires canonical entry bytes for custom leader planning of ${record.canonicalHash}`,
|
|
5685
|
+
);
|
|
5686
|
+
}
|
|
5316
5687
|
leaders.push(
|
|
5317
5688
|
await this.findLeadersFromEntry(
|
|
5318
5689
|
entry,
|
|
@@ -5324,8 +5695,8 @@ export class SharedLog<
|
|
|
5324
5695
|
}
|
|
5325
5696
|
return leaders;
|
|
5326
5697
|
}
|
|
5327
|
-
const items: EntryLeaderBatchItem<R>[] =
|
|
5328
|
-
entry,
|
|
5698
|
+
const items: EntryLeaderBatchItem<R>[] = records.map((record) => ({
|
|
5699
|
+
entry: record.createDefaultPlanningSource(),
|
|
5329
5700
|
replicas,
|
|
5330
5701
|
options: { freshLeaderPlan: true, persist: false },
|
|
5331
5702
|
}));
|
|
@@ -5377,25 +5748,27 @@ export class SharedLog<
|
|
|
5377
5748
|
}
|
|
5378
5749
|
|
|
5379
5750
|
private async settlePersistedDelivery(
|
|
5380
|
-
input:
|
|
5751
|
+
input: PersistedDeliveryPlanningRecord<T, R>[],
|
|
5381
5752
|
replicas: number,
|
|
5382
|
-
delivery:
|
|
5753
|
+
delivery: PersistedDeliveryOptions,
|
|
5383
5754
|
ownershipLifecycleController = this.captureReplicationOwnershipLifecycle(),
|
|
5384
5755
|
persistedDeadline?: PersistedDeliveryDeadline,
|
|
5385
5756
|
transferOnFirstRound = false,
|
|
5386
5757
|
): Promise<void> {
|
|
5387
5758
|
const minAcks = Math.floor(delivery.minAcks!);
|
|
5388
|
-
const
|
|
5389
|
-
|
|
5759
|
+
const records = new Map(
|
|
5760
|
+
input.map((record) => [record.canonicalHash, record]),
|
|
5761
|
+
);
|
|
5762
|
+
if (records.size === 0) return;
|
|
5390
5763
|
|
|
5391
|
-
const committedHashes = [...
|
|
5764
|
+
const committedHashes = [...records.keys()];
|
|
5392
5765
|
const ownedDeadline = !persistedDeadline;
|
|
5393
5766
|
const deadline =
|
|
5394
5767
|
persistedDeadline ??
|
|
5395
5768
|
this.createPersistedDeliveryDeadline(
|
|
5396
5769
|
delivery,
|
|
5397
5770
|
ownershipLifecycleController,
|
|
5398
|
-
|
|
5771
|
+
records.size,
|
|
5399
5772
|
);
|
|
5400
5773
|
const signal = deadline.signal;
|
|
5401
5774
|
let maxAttemptMs = MAX_PERSISTED_RECEIPT_ATTEMPT_MS;
|
|
@@ -5464,7 +5837,7 @@ export class SharedLog<
|
|
|
5464
5837
|
// transport epoch. A revision/session change purges them before they can
|
|
5465
5838
|
// survive an away-and-back leader transition or combine with a later peer.
|
|
5466
5839
|
const hashesByPeer = new Map<string, string[]>();
|
|
5467
|
-
const entryArray = [...
|
|
5840
|
+
const entryArray = [...records.values()];
|
|
5468
5841
|
const leadersByEntry = await this.planPersistedDeliveryLeaders(
|
|
5469
5842
|
entryArray,
|
|
5470
5843
|
replicas,
|
|
@@ -5485,7 +5858,7 @@ export class SharedLog<
|
|
|
5485
5858
|
}
|
|
5486
5859
|
}
|
|
5487
5860
|
for (let index = 0; index < entryArray.length; index++) {
|
|
5488
|
-
const hash = entryArray[index]!.
|
|
5861
|
+
const hash = entryArray[index]!.canonicalHash;
|
|
5489
5862
|
const leaders = leadersByEntry[index]!;
|
|
5490
5863
|
if (signal.aborted) {
|
|
5491
5864
|
throw signal.reason ?? new AbortError();
|
|
@@ -5756,7 +6129,7 @@ export class SharedLog<
|
|
|
5756
6129
|
}
|
|
5757
6130
|
return false;
|
|
5758
6131
|
}
|
|
5759
|
-
const hash = entryArray[index]!.
|
|
6132
|
+
const hash = entryArray[index]!.canonicalHash;
|
|
5760
6133
|
const leaders = validatedLeaders[index]!;
|
|
5761
6134
|
let valid = 0;
|
|
5762
6135
|
const acknowledgements = carriedAcknowledgements.get(hash)!;
|
|
@@ -11503,7 +11876,9 @@ export class SharedLog<
|
|
|
11503
11876
|
removed: ShallowOrFullEntry<T>[];
|
|
11504
11877
|
}> {
|
|
11505
11878
|
this.throwIfNativeDurableCommitFailed();
|
|
11506
|
-
const
|
|
11879
|
+
const persistedInvocation = this.capturePersistedAppendInvocation(options);
|
|
11880
|
+
options = persistedInvocation?.options ?? options;
|
|
11881
|
+
const persistedDelivery = persistedInvocation?.delivery;
|
|
11507
11882
|
const ownershipLifecycleController =
|
|
11508
11883
|
this.captureReplicationOwnershipLifecycle();
|
|
11509
11884
|
if (this._isAdaptiveReplicating) {
|
|
@@ -11515,14 +11890,32 @@ export class SharedLog<
|
|
|
11515
11890
|
ownershipLifecycleController,
|
|
11516
11891
|
);
|
|
11517
11892
|
let committedHashes: readonly string[] | undefined;
|
|
11893
|
+
let persistedAppendCommit: PreparedLocalAppendCommit<R> | undefined;
|
|
11894
|
+
let persistedPlanningRecord:
|
|
11895
|
+
| PersistedDeliveryPlanningRecord<T, R>
|
|
11896
|
+
| undefined;
|
|
11518
11897
|
if (persistedDelivery) {
|
|
11519
11898
|
(appendOptions as TrustedLogAppendOptions<T>).__peerbitOnLocalCommit = (
|
|
11520
11899
|
hashes,
|
|
11900
|
+
entries,
|
|
11521
11901
|
) => {
|
|
11522
11902
|
// The lower log reports the exact hashes immediately after their
|
|
11523
|
-
// irreversible local mutation and before
|
|
11524
|
-
//
|
|
11525
|
-
|
|
11903
|
+
// irreversible local mutation and before trim/change callbacks. Own the
|
|
11904
|
+
// canonical hash first, then detach all planning facts and storage bytes
|
|
11905
|
+
// synchronously so callback mutation cannot retarget the later quorum.
|
|
11906
|
+
committedHashes = Object.freeze([...hashes]);
|
|
11907
|
+
if (hashes.length !== 1 || entries?.length !== 1) {
|
|
11908
|
+
throw new Error(
|
|
11909
|
+
"Persisted delivery requires exact lower-log entry commit evidence",
|
|
11910
|
+
);
|
|
11911
|
+
}
|
|
11912
|
+
persistedAppendCommit = this.capturePersistedLocalAppendCommit(
|
|
11913
|
+
hashes[0]!,
|
|
11914
|
+
entries[0]!,
|
|
11915
|
+
);
|
|
11916
|
+
persistedPlanningRecord = this.createPersistedDeliveryPlanningRecord(
|
|
11917
|
+
persistedAppendCommit,
|
|
11918
|
+
);
|
|
11526
11919
|
};
|
|
11527
11920
|
}
|
|
11528
11921
|
let persistedDeadline: PersistedDeliveryDeadline | undefined;
|
|
@@ -11533,7 +11926,11 @@ export class SharedLog<
|
|
|
11533
11926
|
};
|
|
11534
11927
|
try {
|
|
11535
11928
|
const result = await this.log.append(data, appendOptions);
|
|
11536
|
-
committedHashes
|
|
11929
|
+
if (persistedDelivery && (!committedHashes || !persistedPlanningRecord)) {
|
|
11930
|
+
throw new Error(
|
|
11931
|
+
"Lower log did not provide persisted-delivery commit evidence",
|
|
11932
|
+
);
|
|
11933
|
+
}
|
|
11537
11934
|
persistedDeadline = persistedDelivery
|
|
11538
11935
|
? this.createPersistedDeliveryDeadline(
|
|
11539
11936
|
persistedDelivery,
|
|
@@ -11545,14 +11942,17 @@ export class SharedLog<
|
|
|
11545
11942
|
ownershipLifecycleController,
|
|
11546
11943
|
);
|
|
11547
11944
|
throwIfDeliveryAborted();
|
|
11548
|
-
|
|
11945
|
+
const processingEntry =
|
|
11946
|
+
persistedPlanningRecord?.createFullPlanningSource?.() ?? result.entry;
|
|
11947
|
+
await this.processLocalAppend(processingEntry, result.removed, options, {
|
|
11549
11948
|
minReplicasValue,
|
|
11949
|
+
appendFacts: persistedAppendCommit,
|
|
11550
11950
|
ownershipLifecycleController,
|
|
11551
11951
|
});
|
|
11552
11952
|
throwIfDeliveryAborted();
|
|
11553
11953
|
if (persistedDelivery && persistedDeadline) {
|
|
11554
11954
|
await this.settlePersistedDelivery(
|
|
11555
|
-
[
|
|
11955
|
+
[persistedPlanningRecord!],
|
|
11556
11956
|
minReplicasValue,
|
|
11557
11957
|
persistedDelivery,
|
|
11558
11958
|
ownershipLifecycleController,
|
|
@@ -11576,7 +11976,7 @@ export class SharedLog<
|
|
|
11576
11976
|
private rejectPersistedDeliveryOnTrustedLocalAppend(
|
|
11577
11977
|
options?: SharedAppendOptions<T>,
|
|
11578
11978
|
): void {
|
|
11579
|
-
if (this.
|
|
11979
|
+
if (this.capturePersistedAppendInvocation(options)) {
|
|
11580
11980
|
throw new Error(
|
|
11581
11981
|
"trusted local append paths require delivery=false; call deliverPersistedEntries after the local commit",
|
|
11582
11982
|
);
|
|
@@ -14726,7 +15126,7 @@ export class SharedLog<
|
|
|
14726
15126
|
removed: ShallowOrFullEntry<T>[];
|
|
14727
15127
|
}> {
|
|
14728
15128
|
this.throwIfNativeDurableCommitFailed();
|
|
14729
|
-
const persistedDelivery = this.
|
|
15129
|
+
const persistedDelivery = this.capturePersistedAppendInvocation(options);
|
|
14730
15130
|
if (data.length === 0) {
|
|
14731
15131
|
return { entries: [], removed: [] };
|
|
14732
15132
|
}
|
|
@@ -14818,18 +15218,22 @@ export class SharedLog<
|
|
|
14818
15218
|
* rolls back or hides their successful local commit.
|
|
14819
15219
|
*/
|
|
14820
15220
|
private async deliverPersistedPlanningEntries(
|
|
14821
|
-
|
|
14822
|
-
|
|
15221
|
+
resolveRecords: () => PersistedDeliveryPlanningRecord<T, R>[],
|
|
15222
|
+
committedHashesInput: readonly string[],
|
|
14823
15223
|
options: SharedAppendOptions<T>,
|
|
14824
15224
|
): Promise<void> {
|
|
15225
|
+
const committedHashes = Object.freeze([...committedHashesInput]);
|
|
14825
15226
|
let persistedDeadline: PersistedDeliveryDeadline | undefined;
|
|
14826
15227
|
try {
|
|
14827
|
-
const
|
|
14828
|
-
|
|
15228
|
+
const persistedInvocation =
|
|
15229
|
+
this.capturePersistedAppendInvocation(options);
|
|
15230
|
+
if (!persistedInvocation) {
|
|
14829
15231
|
throw new Error(
|
|
14830
15232
|
'deliverPersistedEntries requires reliability="persisted"',
|
|
14831
15233
|
);
|
|
14832
15234
|
}
|
|
15235
|
+
options = persistedInvocation.options;
|
|
15236
|
+
const delivery = persistedInvocation.delivery;
|
|
14833
15237
|
const ownershipLifecycleController =
|
|
14834
15238
|
this.captureReplicationOwnershipLifecycle();
|
|
14835
15239
|
const deadline = this.createPersistedDeliveryDeadline(
|
|
@@ -14848,16 +15252,26 @@ export class SharedLog<
|
|
|
14848
15252
|
);
|
|
14849
15253
|
}
|
|
14850
15254
|
};
|
|
14851
|
-
const
|
|
15255
|
+
const records = resolveRecords();
|
|
14852
15256
|
throwIfDeliveryAborted();
|
|
14853
|
-
if (
|
|
15257
|
+
if (records.length !== committedHashes.length) {
|
|
15258
|
+
throw new Error("Persisted delivery planning evidence count mismatch");
|
|
15259
|
+
}
|
|
15260
|
+
for (let index = 0; index < records.length; index++) {
|
|
15261
|
+
if (records[index]!.canonicalHash !== committedHashes[index]) {
|
|
15262
|
+
throw new Error(
|
|
15263
|
+
`Persisted delivery planning evidence did not match committed hash ${committedHashes[index]}`,
|
|
15264
|
+
);
|
|
15265
|
+
}
|
|
15266
|
+
}
|
|
15267
|
+
if (records.length === 0) return;
|
|
14854
15268
|
const { minReplicasValue } = this.createLogAppendOptions(
|
|
14855
15269
|
options,
|
|
14856
15270
|
ownershipLifecycleController,
|
|
14857
15271
|
);
|
|
14858
15272
|
throwIfDeliveryAborted();
|
|
14859
15273
|
await this.settlePersistedDelivery(
|
|
14860
|
-
|
|
15274
|
+
records,
|
|
14861
15275
|
minReplicasValue,
|
|
14862
15276
|
delivery,
|
|
14863
15277
|
ownershipLifecycleController,
|
|
@@ -14871,20 +15285,21 @@ export class SharedLog<
|
|
|
14871
15285
|
}
|
|
14872
15286
|
}
|
|
14873
15287
|
|
|
14874
|
-
private
|
|
15288
|
+
private createPersistedDeliveryPlanningRecords(
|
|
14875
15289
|
appendCommits: PreparedLocalAppendCommit<R>[],
|
|
14876
15290
|
materializeEntries: () => Entry<T>[],
|
|
14877
|
-
):
|
|
15291
|
+
): PersistedDeliveryPlanningRecord<T, R>[] {
|
|
14878
15292
|
let materializedEntries: Entry<T>[] | undefined;
|
|
14879
15293
|
const requiresFullEntries =
|
|
14880
15294
|
this.findLeadersFromEntry !== SharedLog.prototype.findLeadersFromEntry;
|
|
14881
|
-
const
|
|
15295
|
+
const getCommittedEntry = (
|
|
14882
15296
|
appendCommit: PreparedLocalAppendCommit<R>,
|
|
14883
15297
|
index: number,
|
|
14884
15298
|
) => {
|
|
14885
15299
|
materializedEntries ??= materializeEntries();
|
|
14886
15300
|
const entry = materializedEntries[index];
|
|
14887
|
-
|
|
15301
|
+
const materializedHash = entry?.hash;
|
|
15302
|
+
if (!entry || materializedHash !== appendCommit.hash) {
|
|
14888
15303
|
throw new Error(
|
|
14889
15304
|
`Persisted delivery materializer did not return committed entry ${appendCommit.hash}`,
|
|
14890
15305
|
);
|
|
@@ -14892,12 +15307,14 @@ export class SharedLog<
|
|
|
14892
15307
|
return entry;
|
|
14893
15308
|
};
|
|
14894
15309
|
return appendCommits.map((appendCommit, index) => {
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
15310
|
+
let capturedCommit = this.snapshotPreparedLocalAppendCommit(appendCommit);
|
|
15311
|
+
if (requiresFullEntries && !capturedCommit.storageBytes) {
|
|
15312
|
+
capturedCommit = this.capturePersistedLocalAppendCommit(
|
|
15313
|
+
capturedCommit.hash,
|
|
15314
|
+
getCommittedEntry(capturedCommit, index),
|
|
14898
15315
|
);
|
|
14899
15316
|
}
|
|
14900
|
-
return
|
|
15317
|
+
return this.createPersistedDeliveryPlanningRecord(capturedCommit);
|
|
14901
15318
|
});
|
|
14902
15319
|
}
|
|
14903
15320
|
|
|
@@ -14908,7 +15325,7 @@ export class SharedLog<
|
|
|
14908
15325
|
): Promise<void> {
|
|
14909
15326
|
return this.deliverPersistedPlanningEntries(
|
|
14910
15327
|
() =>
|
|
14911
|
-
this.
|
|
15328
|
+
this.createPersistedDeliveryPlanningRecords(
|
|
14912
15329
|
appendCommits,
|
|
14913
15330
|
materializeEntries,
|
|
14914
15331
|
),
|
|
@@ -14921,9 +15338,12 @@ export class SharedLog<
|
|
|
14921
15338
|
entries: Entry<T>[],
|
|
14922
15339
|
options: SharedAppendOptions<T>,
|
|
14923
15340
|
): Promise<void> {
|
|
15341
|
+
const records = entries.map((entry) =>
|
|
15342
|
+
this.snapshotPersistedDeliveryPlanningEntry(entry),
|
|
15343
|
+
);
|
|
14924
15344
|
return this.deliverPersistedPlanningEntries(
|
|
14925
|
-
() =>
|
|
14926
|
-
|
|
15345
|
+
() => records,
|
|
15346
|
+
records.map((record) => record.canonicalHash),
|
|
14927
15347
|
options,
|
|
14928
15348
|
);
|
|
14929
15349
|
}
|
|
@@ -15871,7 +16291,11 @@ export class SharedLog<
|
|
|
15871
16291
|
next: entry.meta.next,
|
|
15872
16292
|
wallTime: entry.meta.clock.timestamp.wallTime,
|
|
15873
16293
|
logical: entry.meta.clock.timestamp.logical,
|
|
16294
|
+
clockId: entry.meta.clock.id,
|
|
16295
|
+
type: entry.meta.type,
|
|
16296
|
+
metaData: entry.meta.data,
|
|
15874
16297
|
payloadSize: entry.payload.byteLength,
|
|
16298
|
+
entrySize: entry.size,
|
|
15875
16299
|
metaBytes: (entry as EntryWithMetaBytes).getMetaBytes?.(),
|
|
15876
16300
|
hashNumber: nativeAppendPlan?.hashNumber,
|
|
15877
16301
|
coordinateFields: nativeAppendPlan?.preparedCoordinate.fields,
|
|
@@ -15892,6 +16316,9 @@ export class SharedLog<
|
|
|
15892
16316
|
next: appendFacts.next,
|
|
15893
16317
|
wallTime: appendFacts.wallTime,
|
|
15894
16318
|
logical: appendFacts.logical,
|
|
16319
|
+
clockId: appendFacts.clockId,
|
|
16320
|
+
type: appendFacts.type,
|
|
16321
|
+
metaData: appendFacts.metaData,
|
|
15895
16322
|
payloadSize: appendFacts.payloadSize,
|
|
15896
16323
|
metaBytes: appendFacts.metaBytes,
|
|
15897
16324
|
hashNumber: nativeAppendPlan?.hashNumber,
|
|
@@ -15901,6 +16328,134 @@ export class SharedLog<
|
|
|
15901
16328
|
};
|
|
15902
16329
|
}
|
|
15903
16330
|
|
|
16331
|
+
private snapshotPreparedLocalAppendCommit(
|
|
16332
|
+
appendCommit: PreparedLocalAppendCommit<R>,
|
|
16333
|
+
): PreparedLocalAppendCommit<R> {
|
|
16334
|
+
const coordinateFields = appendCommit.coordinateFields;
|
|
16335
|
+
return Object.freeze({
|
|
16336
|
+
...appendCommit,
|
|
16337
|
+
next: [...appendCommit.next],
|
|
16338
|
+
clockId: appendCommit.clockId && Uint8Array.from(appendCommit.clockId),
|
|
16339
|
+
metaData: appendCommit.metaData && Uint8Array.from(appendCommit.metaData),
|
|
16340
|
+
metaBytes:
|
|
16341
|
+
appendCommit.metaBytes && Uint8Array.from(appendCommit.metaBytes),
|
|
16342
|
+
storageBytes:
|
|
16343
|
+
appendCommit.storageBytes && Uint8Array.from(appendCommit.storageBytes),
|
|
16344
|
+
coordinateFields: coordinateFields
|
|
16345
|
+
? {
|
|
16346
|
+
...coordinateFields,
|
|
16347
|
+
coordinates: [...coordinateFields.coordinates],
|
|
16348
|
+
coordinateStrings: coordinateFields.coordinateStrings && [
|
|
16349
|
+
...coordinateFields.coordinateStrings,
|
|
16350
|
+
],
|
|
16351
|
+
metaBytes: Uint8Array.from(coordinateFields.metaBytes),
|
|
16352
|
+
}
|
|
16353
|
+
: undefined,
|
|
16354
|
+
documentPreviousContext: appendCommit.documentPreviousContext
|
|
16355
|
+
? { ...appendCommit.documentPreviousContext }
|
|
16356
|
+
: undefined,
|
|
16357
|
+
});
|
|
16358
|
+
}
|
|
16359
|
+
|
|
16360
|
+
private capturePersistedLocalAppendCommit(
|
|
16361
|
+
canonicalHash: string,
|
|
16362
|
+
entry: Entry<T>,
|
|
16363
|
+
): PreparedLocalAppendCommit<R> {
|
|
16364
|
+
const sourceHash = entry.hash;
|
|
16365
|
+
if (sourceHash !== canonicalHash) {
|
|
16366
|
+
throw new Error(
|
|
16367
|
+
`Lower-log commit evidence entry did not match committed hash ${canonicalHash}`,
|
|
16368
|
+
);
|
|
16369
|
+
}
|
|
16370
|
+
const storageBytes =
|
|
16371
|
+
Entry.getPreparedStorageBytes(entry) ?? entry.getStorageBytes();
|
|
16372
|
+
return this.snapshotPreparedLocalAppendCommit({
|
|
16373
|
+
...this.createPreparedLocalAppendCommit(entry),
|
|
16374
|
+
hash: canonicalHash,
|
|
16375
|
+
storageBytes,
|
|
16376
|
+
});
|
|
16377
|
+
}
|
|
16378
|
+
|
|
16379
|
+
private createPersistedDeliveryPlanningRecord(
|
|
16380
|
+
appendCommitInput: PreparedLocalAppendCommit<R>,
|
|
16381
|
+
): PersistedDeliveryPlanningRecord<T, R> {
|
|
16382
|
+
// Callers hand this helper an invocation-owned snapshot. Do not duplicate
|
|
16383
|
+
// full entry bytes here; a fresh copy is made only when a custom planner is
|
|
16384
|
+
// actually invoked (and again for each replan so planner mutation cannot
|
|
16385
|
+
// persist between rounds).
|
|
16386
|
+
const appendCommit = appendCommitInput;
|
|
16387
|
+
const canonicalHash = appendCommit.hash;
|
|
16388
|
+
const coordinateFields = appendCommit.coordinateFields;
|
|
16389
|
+
const createDefaultPlanningSource = coordinateFields
|
|
16390
|
+
? () =>
|
|
16391
|
+
this._coordinates.materializeResidentCoordinateEntry({
|
|
16392
|
+
...coordinateFields,
|
|
16393
|
+
coordinates: [...coordinateFields.coordinates],
|
|
16394
|
+
coordinateStrings: coordinateFields.coordinateStrings && [
|
|
16395
|
+
...coordinateFields.coordinateStrings,
|
|
16396
|
+
],
|
|
16397
|
+
metaBytes: Uint8Array.from(coordinateFields.metaBytes),
|
|
16398
|
+
})
|
|
16399
|
+
: () =>
|
|
16400
|
+
new ShallowEntry({
|
|
16401
|
+
hash: canonicalHash,
|
|
16402
|
+
head: true,
|
|
16403
|
+
payloadSize: appendCommit.payloadSize,
|
|
16404
|
+
meta: new ShallowMeta({
|
|
16405
|
+
gid: appendCommit.gid,
|
|
16406
|
+
next: [...appendCommit.next],
|
|
16407
|
+
type: appendCommit.type ?? EntryType.APPEND,
|
|
16408
|
+
data:
|
|
16409
|
+
appendCommit.metaData && Uint8Array.from(appendCommit.metaData),
|
|
16410
|
+
clock: new LamportClock({
|
|
16411
|
+
id: Uint8Array.from(appendCommit.clockId ?? new Uint8Array()),
|
|
16412
|
+
timestamp: new Timestamp({
|
|
16413
|
+
wallTime: appendCommit.wallTime,
|
|
16414
|
+
logical: appendCommit.logical,
|
|
16415
|
+
}),
|
|
16416
|
+
}),
|
|
16417
|
+
}),
|
|
16418
|
+
});
|
|
16419
|
+
const storageBytes = appendCommit.storageBytes;
|
|
16420
|
+
const createFullPlanningSource = storageBytes
|
|
16421
|
+
? () => {
|
|
16422
|
+
const bytes = Uint8Array.from(storageBytes);
|
|
16423
|
+
const entry = deserialize(bytes, Entry) as Entry<T>;
|
|
16424
|
+
const decodedHash = entry.hash;
|
|
16425
|
+
if (decodedHash && decodedHash !== canonicalHash) {
|
|
16426
|
+
throw new Error(
|
|
16427
|
+
`Persisted delivery planning bytes did not match committed hash ${canonicalHash}`,
|
|
16428
|
+
);
|
|
16429
|
+
}
|
|
16430
|
+
if (!decodedHash) {
|
|
16431
|
+
Entry.prepareMultihashBytes(entry, bytes, canonicalHash);
|
|
16432
|
+
entry.hash = canonicalHash;
|
|
16433
|
+
}
|
|
16434
|
+
entry.size = appendCommit.entrySize ?? bytes.byteLength;
|
|
16435
|
+
entry.createdLocally = true;
|
|
16436
|
+
entry.init({
|
|
16437
|
+
encoding: this.log.encoding,
|
|
16438
|
+
keychain: this.log.keychain,
|
|
16439
|
+
});
|
|
16440
|
+
return entry;
|
|
16441
|
+
}
|
|
16442
|
+
: undefined;
|
|
16443
|
+
return Object.freeze({
|
|
16444
|
+
canonicalHash,
|
|
16445
|
+
createDefaultPlanningSource,
|
|
16446
|
+
createFullPlanningSource,
|
|
16447
|
+
});
|
|
16448
|
+
}
|
|
16449
|
+
|
|
16450
|
+
private snapshotPersistedDeliveryPlanningEntry(
|
|
16451
|
+
value: Entry<T>,
|
|
16452
|
+
): PersistedDeliveryPlanningRecord<T, R> {
|
|
16453
|
+
const canonicalHash = value.hash;
|
|
16454
|
+
return this.createPersistedDeliveryPlanningRecord(
|
|
16455
|
+
this.capturePersistedLocalAppendCommit(canonicalHash, value),
|
|
16456
|
+
);
|
|
16457
|
+
}
|
|
16458
|
+
|
|
15904
16459
|
private createPreparedLocalAppendCommits(
|
|
15905
16460
|
entries: Entry<T>[],
|
|
15906
16461
|
nativeAppendPlans?: Array<NativeAppendEntryPlan<R> | undefined>,
|