@peerbit/native-backbone 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -20
- package/dist/src/index.d.ts +59 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +942 -74
- package/dist/src/index.js.map +1 -1
- package/dist/wasm/README.md +55 -20
- package/dist/wasm/native_backbone.d.ts +118 -79
- package/dist/wasm/native_backbone.js +227 -0
- package/dist/wasm/native_backbone_bg.wasm +0 -0
- package/dist/wasm/native_backbone_bg.wasm.d.ts +87 -79
- package/package.json +1 -1
- package/src/append_tx/committed_latest.rs +10 -5
- package/src/append_tx/committed_no_next.rs +6 -3
- package/src/append_tx/facts.rs +427 -1
- package/src/append_tx/mod.rs +51 -18
- package/src/append_tx/storage.rs +12 -2
- package/src/index.ts +1388 -223
package/src/index.ts
CHANGED
|
@@ -9,6 +9,94 @@ export * from "./durability/storage.js";
|
|
|
9
9
|
|
|
10
10
|
export type RangeResolution = "u32" | "u64";
|
|
11
11
|
|
|
12
|
+
type NativePrepareNoNextDocumentIndexCompactTrimFacts = (
|
|
13
|
+
wallTime: bigint,
|
|
14
|
+
logical: number,
|
|
15
|
+
gid: string,
|
|
16
|
+
type: number,
|
|
17
|
+
metaData: Uint8Array | undefined,
|
|
18
|
+
payloadData: Uint8Array,
|
|
19
|
+
trimLengthTo: number,
|
|
20
|
+
documentKey: string,
|
|
21
|
+
documentValuePrefixBytes: Uint8Array,
|
|
22
|
+
documentExistingCreated: string,
|
|
23
|
+
documentByteElementIndexLimit: number,
|
|
24
|
+
documentDeleteTrimmedHeads: boolean,
|
|
25
|
+
documentProjectionPlan:
|
|
26
|
+
| NativeBackboneSimpleDocumentProjectionPlan
|
|
27
|
+
| undefined,
|
|
28
|
+
documentProjectionEncodedDocument: Uint8Array | undefined,
|
|
29
|
+
documentProjectionSigner: Uint8Array | undefined,
|
|
30
|
+
) => unknown[];
|
|
31
|
+
|
|
32
|
+
type NativePrepareNoNextCachedDocumentIndexCompactTrimFacts = (
|
|
33
|
+
wallTime: bigint,
|
|
34
|
+
logical: number,
|
|
35
|
+
gid: string,
|
|
36
|
+
type: number,
|
|
37
|
+
metaData: Uint8Array | undefined,
|
|
38
|
+
payloadData: Uint8Array,
|
|
39
|
+
trimLengthTo: number,
|
|
40
|
+
documentKey: string,
|
|
41
|
+
documentExistingCreated: string,
|
|
42
|
+
documentByteElementIndexLimit: number,
|
|
43
|
+
documentDeleteTrimmedHeads: boolean,
|
|
44
|
+
documentProjectionPlanId: number,
|
|
45
|
+
documentProjectionEncodedDocument: Uint8Array,
|
|
46
|
+
documentProjectionSigner: Uint8Array | undefined,
|
|
47
|
+
) => unknown[];
|
|
48
|
+
|
|
49
|
+
type NativePrepareNoNextCachedPlainPutPayloadDocumentIndexCompactTrimFacts = (
|
|
50
|
+
wallTime: bigint,
|
|
51
|
+
logical: number,
|
|
52
|
+
gid: string,
|
|
53
|
+
type: number,
|
|
54
|
+
metaData: Uint8Array | undefined,
|
|
55
|
+
payloadData: Uint8Array,
|
|
56
|
+
trimLengthTo: number,
|
|
57
|
+
documentKey: string,
|
|
58
|
+
documentExistingCreated: string,
|
|
59
|
+
documentByteElementIndexLimit: number,
|
|
60
|
+
documentDeleteTrimmedHeads: boolean,
|
|
61
|
+
documentProjectionPlanId: number,
|
|
62
|
+
documentProjectionSigner: Uint8Array | undefined,
|
|
63
|
+
) => unknown[];
|
|
64
|
+
|
|
65
|
+
type NativePrepareLatestDocumentIndexTrimFacts = (
|
|
66
|
+
wallTime: bigint,
|
|
67
|
+
logical: number,
|
|
68
|
+
gid: string,
|
|
69
|
+
type: number,
|
|
70
|
+
metaData: Uint8Array | undefined,
|
|
71
|
+
payloadData: Uint8Array,
|
|
72
|
+
trimLengthTo: number | undefined,
|
|
73
|
+
documentKey: string,
|
|
74
|
+
documentValuePrefixBytes: Uint8Array,
|
|
75
|
+
documentByteElementIndexLimit: number,
|
|
76
|
+
documentDeleteTrimmedHeads: boolean,
|
|
77
|
+
documentProjectionPlan:
|
|
78
|
+
| NativeBackboneSimpleDocumentProjectionPlan
|
|
79
|
+
| undefined,
|
|
80
|
+
documentProjectionEncodedDocument: Uint8Array | undefined,
|
|
81
|
+
documentProjectionSigner: Uint8Array | undefined,
|
|
82
|
+
) => unknown[];
|
|
83
|
+
|
|
84
|
+
type NativePrepareLatestCachedDocumentIndexTrimFacts = (
|
|
85
|
+
wallTime: bigint,
|
|
86
|
+
logical: number,
|
|
87
|
+
gid: string,
|
|
88
|
+
type: number,
|
|
89
|
+
metaData: Uint8Array | undefined,
|
|
90
|
+
payloadData: Uint8Array,
|
|
91
|
+
trimLengthTo: number | undefined,
|
|
92
|
+
documentKey: string,
|
|
93
|
+
documentByteElementIndexLimit: number,
|
|
94
|
+
documentDeleteTrimmedHeads: boolean,
|
|
95
|
+
documentProjectionPlanId: number,
|
|
96
|
+
documentProjectionEncodedDocument: Uint8Array,
|
|
97
|
+
documentProjectionSigner: Uint8Array | undefined,
|
|
98
|
+
) => unknown[];
|
|
99
|
+
|
|
12
100
|
type NativePeerbitBackboneHandle = {
|
|
13
101
|
log_len: () => number;
|
|
14
102
|
block_len: () => number;
|
|
@@ -942,6 +1030,16 @@ type NativePeerbitBackboneHandle = {
|
|
|
942
1030
|
payloadData: Uint8Array,
|
|
943
1031
|
trimLengthTo: number | undefined,
|
|
944
1032
|
) => unknown[];
|
|
1033
|
+
prepare_plain_entry_commit_facts_trim_refs?: (
|
|
1034
|
+
wallTime: bigint,
|
|
1035
|
+
logical: number,
|
|
1036
|
+
gid: string,
|
|
1037
|
+
next: string[],
|
|
1038
|
+
type: number,
|
|
1039
|
+
metaData: Uint8Array | undefined,
|
|
1040
|
+
payloadData: Uint8Array,
|
|
1041
|
+
trimLengthTo: number,
|
|
1042
|
+
) => unknown[];
|
|
945
1043
|
prepare_plain_entry_commit_facts_document_index: (
|
|
946
1044
|
wallTime: bigint,
|
|
947
1045
|
logical: number,
|
|
@@ -1045,25 +1143,8 @@ type NativePeerbitBackboneHandle = {
|
|
|
1045
1143
|
documentProjectionEncodedDocument: Uint8Array | undefined,
|
|
1046
1144
|
documentProjectionSigner: Uint8Array | undefined,
|
|
1047
1145
|
) => unknown[];
|
|
1048
|
-
prepare_plain_entry_commit_no_next_facts_document_index_compact_trim_hashes:
|
|
1049
|
-
|
|
1050
|
-
logical: number,
|
|
1051
|
-
gid: string,
|
|
1052
|
-
type: number,
|
|
1053
|
-
metaData: Uint8Array | undefined,
|
|
1054
|
-
payloadData: Uint8Array,
|
|
1055
|
-
trimLengthTo: number,
|
|
1056
|
-
documentKey: string,
|
|
1057
|
-
documentValuePrefixBytes: Uint8Array,
|
|
1058
|
-
documentExistingCreated: string,
|
|
1059
|
-
documentByteElementIndexLimit: number,
|
|
1060
|
-
documentDeleteTrimmedHeads: boolean,
|
|
1061
|
-
documentProjectionPlan:
|
|
1062
|
-
| NativeBackboneSimpleDocumentProjectionPlan
|
|
1063
|
-
| undefined,
|
|
1064
|
-
documentProjectionEncodedDocument: Uint8Array | undefined,
|
|
1065
|
-
documentProjectionSigner: Uint8Array | undefined,
|
|
1066
|
-
) => unknown[];
|
|
1146
|
+
prepare_plain_entry_commit_no_next_facts_document_index_compact_trim_hashes: NativePrepareNoNextDocumentIndexCompactTrimFacts;
|
|
1147
|
+
prepare_plain_entry_commit_no_next_facts_document_index_compact_trim_refs?: NativePrepareNoNextDocumentIndexCompactTrimFacts;
|
|
1067
1148
|
prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_trim_hashes: (
|
|
1068
1149
|
wallTime: bigint,
|
|
1069
1150
|
logical: number,
|
|
@@ -1080,70 +1161,14 @@ type NativePeerbitBackboneHandle = {
|
|
|
1080
1161
|
documentProjectionEncodedDocument: Uint8Array,
|
|
1081
1162
|
documentProjectionSigner: Uint8Array | undefined,
|
|
1082
1163
|
) => unknown[];
|
|
1083
|
-
prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes:
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
documentKey: string,
|
|
1092
|
-
documentExistingCreated: string,
|
|
1093
|
-
documentByteElementIndexLimit: number,
|
|
1094
|
-
documentDeleteTrimmedHeads: boolean,
|
|
1095
|
-
documentProjectionPlanId: number,
|
|
1096
|
-
documentProjectionEncodedDocument: Uint8Array,
|
|
1097
|
-
documentProjectionSigner: Uint8Array | undefined,
|
|
1098
|
-
) => unknown[];
|
|
1099
|
-
prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes_plain_put_payload?: (
|
|
1100
|
-
wallTime: bigint,
|
|
1101
|
-
logical: number,
|
|
1102
|
-
gid: string,
|
|
1103
|
-
type: number,
|
|
1104
|
-
metaData: Uint8Array | undefined,
|
|
1105
|
-
payloadData: Uint8Array,
|
|
1106
|
-
trimLengthTo: number,
|
|
1107
|
-
documentKey: string,
|
|
1108
|
-
documentExistingCreated: string,
|
|
1109
|
-
documentByteElementIndexLimit: number,
|
|
1110
|
-
documentDeleteTrimmedHeads: boolean,
|
|
1111
|
-
documentProjectionPlanId: number,
|
|
1112
|
-
documentProjectionSigner: Uint8Array | undefined,
|
|
1113
|
-
) => unknown[];
|
|
1114
|
-
prepare_plain_entry_commit_latest_facts_document_index_trim_hashes: (
|
|
1115
|
-
wallTime: bigint,
|
|
1116
|
-
logical: number,
|
|
1117
|
-
gid: string,
|
|
1118
|
-
type: number,
|
|
1119
|
-
metaData: Uint8Array | undefined,
|
|
1120
|
-
payloadData: Uint8Array,
|
|
1121
|
-
trimLengthTo: number | undefined,
|
|
1122
|
-
documentKey: string,
|
|
1123
|
-
documentValuePrefixBytes: Uint8Array,
|
|
1124
|
-
documentByteElementIndexLimit: number,
|
|
1125
|
-
documentDeleteTrimmedHeads: boolean,
|
|
1126
|
-
documentProjectionPlan:
|
|
1127
|
-
| NativeBackboneSimpleDocumentProjectionPlan
|
|
1128
|
-
| undefined,
|
|
1129
|
-
documentProjectionEncodedDocument: Uint8Array | undefined,
|
|
1130
|
-
documentProjectionSigner: Uint8Array | undefined,
|
|
1131
|
-
) => unknown[];
|
|
1132
|
-
prepare_plain_entry_commit_latest_facts_document_index_cached_plan_trim_hashes: (
|
|
1133
|
-
wallTime: bigint,
|
|
1134
|
-
logical: number,
|
|
1135
|
-
gid: string,
|
|
1136
|
-
type: number,
|
|
1137
|
-
metaData: Uint8Array | undefined,
|
|
1138
|
-
payloadData: Uint8Array,
|
|
1139
|
-
trimLengthTo: number | undefined,
|
|
1140
|
-
documentKey: string,
|
|
1141
|
-
documentByteElementIndexLimit: number,
|
|
1142
|
-
documentDeleteTrimmedHeads: boolean,
|
|
1143
|
-
documentProjectionPlanId: number,
|
|
1144
|
-
documentProjectionEncodedDocument: Uint8Array,
|
|
1145
|
-
documentProjectionSigner: Uint8Array | undefined,
|
|
1146
|
-
) => unknown[];
|
|
1164
|
+
prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes: NativePrepareNoNextCachedDocumentIndexCompactTrimFacts;
|
|
1165
|
+
prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_refs?: NativePrepareNoNextCachedDocumentIndexCompactTrimFacts;
|
|
1166
|
+
prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes_plain_put_payload?: NativePrepareNoNextCachedPlainPutPayloadDocumentIndexCompactTrimFacts;
|
|
1167
|
+
prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_refs_plain_put_payload?: NativePrepareNoNextCachedPlainPutPayloadDocumentIndexCompactTrimFacts;
|
|
1168
|
+
prepare_plain_entry_commit_latest_facts_document_index_trim_hashes: NativePrepareLatestDocumentIndexTrimFacts;
|
|
1169
|
+
prepare_plain_entry_commit_latest_facts_document_index_trim_refs?: NativePrepareLatestDocumentIndexTrimFacts;
|
|
1170
|
+
prepare_plain_entry_commit_latest_facts_document_index_cached_plan_trim_hashes: NativePrepareLatestCachedDocumentIndexTrimFacts;
|
|
1171
|
+
prepare_plain_entry_commit_latest_facts_document_index_cached_plan_trim_refs?: NativePrepareLatestCachedDocumentIndexTrimFacts;
|
|
1147
1172
|
prepare_plain_entry_storage_facts_and_put: (
|
|
1148
1173
|
wallTime: bigint,
|
|
1149
1174
|
logical: number,
|
|
@@ -2727,6 +2752,7 @@ export type NativeBackboneAppendResult = {
|
|
|
2727
2752
|
assignedToRangeBoundary: boolean;
|
|
2728
2753
|
trimmed: NativeBackboneTrimmedEntry[];
|
|
2729
2754
|
trimmedHashes?: string[];
|
|
2755
|
+
trimmedGids?: string[];
|
|
2730
2756
|
documentTrimmedHeadsProcessed?: boolean;
|
|
2731
2757
|
documentPreviousContext?: NativeBackboneDocumentContextFacts;
|
|
2732
2758
|
};
|
|
@@ -2739,6 +2765,7 @@ type NativeBackboneStorageAppendResult = {
|
|
|
2739
2765
|
assignedToRangeBoundary: boolean;
|
|
2740
2766
|
trimmed: NativeBackboneTrimmedEntry[];
|
|
2741
2767
|
trimmedHashes?: string[];
|
|
2768
|
+
trimmedGids?: string[];
|
|
2742
2769
|
documentTrimmedHeadsProcessed?: boolean;
|
|
2743
2770
|
documentPreviousContext?: NativeBackboneDocumentContextFacts;
|
|
2744
2771
|
};
|
|
@@ -2982,6 +3009,12 @@ export type NativeBackboneCoordinatePersistenceStore = {
|
|
|
2982
3009
|
*/
|
|
2983
3010
|
readLimited?(name: string, maxBytes: number): Promise<Uint8Array | undefined>;
|
|
2984
3011
|
write(name: string, bytes: Uint8Array): Promise<void>;
|
|
3012
|
+
/**
|
|
3013
|
+
* Durably replace one named file without exposing a torn destination. The
|
|
3014
|
+
* replacement is not visible until its bytes and containing directory have
|
|
3015
|
+
* crossed their physical durability barriers.
|
|
3016
|
+
*/
|
|
3017
|
+
atomicReplace?(name: string, bytes: Uint8Array): Promise<void>;
|
|
2985
3018
|
/**
|
|
2986
3019
|
* Appends `bytes` to the named file. Callers hand over ownership of
|
|
2987
3020
|
* `bytes` and must not mutate it afterwards: buffering stores keep the
|
|
@@ -3097,12 +3130,12 @@ export const nativeBackboneCoordinateDropTombstoneFile =
|
|
|
3097
3130
|
"native-backbone-drop.tombstone";
|
|
3098
3131
|
|
|
3099
3132
|
export const defaultNativeBackboneCoordinateFlushMaxPendingBytes = 1024 * 1024;
|
|
3100
|
-
/**
|
|
3133
|
+
/** Recommended explicit byte threshold for crash-safe Node checkpointing. */
|
|
3101
3134
|
export const defaultNativeBackboneCoordinateCompactMaxJournalBytes =
|
|
3102
3135
|
64 * 1024 * 1024;
|
|
3103
3136
|
|
|
3104
3137
|
const nativeBackboneCoordinateCompactionDisabledMessage =
|
|
3105
|
-
"Native backbone coordinate persistence compaction is disabled
|
|
3138
|
+
"Native backbone coordinate persistence compaction is disabled for stores without crash-safe atomic replacement";
|
|
3106
3139
|
|
|
3107
3140
|
const nativeBackboneCoordinateJournalMagic = Uint8Array.from([
|
|
3108
3141
|
0x50, 0x42, 0x52, 0x49, 0x44, 0x58, 0x57, 0x31,
|
|
@@ -3123,6 +3156,260 @@ const coordinateJournalChecksum = (bytes: Uint8Array): number => {
|
|
|
3123
3156
|
return checksum;
|
|
3124
3157
|
};
|
|
3125
3158
|
|
|
3159
|
+
type NativeBackboneCoordinateCheckpointSlot = "a" | "b";
|
|
3160
|
+
|
|
3161
|
+
type NativeBackboneCoordinateCheckpointAuthority = {
|
|
3162
|
+
generation: bigint;
|
|
3163
|
+
byteLength: number;
|
|
3164
|
+
checksum: number;
|
|
3165
|
+
};
|
|
3166
|
+
|
|
3167
|
+
type NativeBackboneCoordinateCheckpointState = {
|
|
3168
|
+
configurationChecksum: number;
|
|
3169
|
+
highwater: bigint;
|
|
3170
|
+
pending?: NativeBackboneCoordinateCheckpointAuthority;
|
|
3171
|
+
completed?: NativeBackboneCoordinateCheckpointAuthority;
|
|
3172
|
+
};
|
|
3173
|
+
|
|
3174
|
+
type NativeBackboneCoordinateCheckpoint = {
|
|
3175
|
+
configurationChecksum: number;
|
|
3176
|
+
generation: bigint;
|
|
3177
|
+
coordinateSnapshot: Uint8Array;
|
|
3178
|
+
documentSnapshot: Uint8Array;
|
|
3179
|
+
documentSignerSnapshot: Uint8Array;
|
|
3180
|
+
};
|
|
3181
|
+
|
|
3182
|
+
const nativeBackboneCoordinateCheckpointMagic = Uint8Array.from([
|
|
3183
|
+
0x50, 0x42, 0x52, 0x49, 0x44, 0x58, 0x43, 0x31,
|
|
3184
|
+
]);
|
|
3185
|
+
const nativeBackboneCoordinateCheckpointStateMagic = Uint8Array.from([
|
|
3186
|
+
0x50, 0x42, 0x52, 0x49, 0x44, 0x58, 0x53, 0x31,
|
|
3187
|
+
]);
|
|
3188
|
+
const nativeBackboneCoordinateLegacyMigrationSentinel = (() => {
|
|
3189
|
+
const payload = Uint8Array.of(0xff);
|
|
3190
|
+
const bytes = new Uint8Array(
|
|
3191
|
+
nativeBackboneCoordinateJournalMagic.byteLength + 8 + payload.byteLength,
|
|
3192
|
+
);
|
|
3193
|
+
bytes.set(nativeBackboneCoordinateJournalMagic, 0);
|
|
3194
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3195
|
+
view.setUint32(nativeBackboneCoordinateJournalMagic.byteLength, 1, true);
|
|
3196
|
+
view.setUint32(
|
|
3197
|
+
nativeBackboneCoordinateJournalMagic.byteLength + 4,
|
|
3198
|
+
coordinateJournalChecksum(payload),
|
|
3199
|
+
true,
|
|
3200
|
+
);
|
|
3201
|
+
bytes.set(payload, nativeBackboneCoordinateJournalMagic.byteLength + 8);
|
|
3202
|
+
return bytes;
|
|
3203
|
+
})();
|
|
3204
|
+
const nativeBackboneCoordinateCheckpointVersion = 1;
|
|
3205
|
+
const nativeBackboneCoordinateCheckpointHeaderBytes = 56;
|
|
3206
|
+
const nativeBackboneCoordinateCheckpointStateBytes = 60;
|
|
3207
|
+
const nativeBackboneCoordinateMaxU64 = (1n << 64n) - 1n;
|
|
3208
|
+
|
|
3209
|
+
const hasBytesPrefix = (bytes: Uint8Array, prefix: Uint8Array): boolean =>
|
|
3210
|
+
bytes.byteLength >= prefix.byteLength &&
|
|
3211
|
+
prefix.every((byte, index) => bytes[index] === byte);
|
|
3212
|
+
|
|
3213
|
+
const writeCoordinateCheckpointU64 = (
|
|
3214
|
+
view: DataView,
|
|
3215
|
+
offset: number,
|
|
3216
|
+
value: bigint,
|
|
3217
|
+
): void => {
|
|
3218
|
+
if (value < 0n || value > nativeBackboneCoordinateMaxU64) {
|
|
3219
|
+
throw new RangeError("Native backbone checkpoint generation exceeds u64");
|
|
3220
|
+
}
|
|
3221
|
+
view.setUint32(offset, Number(value & 0xffff_ffffn), true);
|
|
3222
|
+
view.setUint32(offset + 4, Number(value >> 32n), true);
|
|
3223
|
+
};
|
|
3224
|
+
|
|
3225
|
+
const readCoordinateCheckpointU64 = (view: DataView, offset: number): bigint =>
|
|
3226
|
+
BigInt(view.getUint32(offset, true)) |
|
|
3227
|
+
(BigInt(view.getUint32(offset + 4, true)) << 32n);
|
|
3228
|
+
|
|
3229
|
+
const encodeNativeBackboneCoordinateCheckpoint = (
|
|
3230
|
+
checkpoint: NativeBackboneCoordinateCheckpoint,
|
|
3231
|
+
): Uint8Array => {
|
|
3232
|
+
const coordinateLength = checkpoint.coordinateSnapshot.byteLength;
|
|
3233
|
+
const documentLength = checkpoint.documentSnapshot.byteLength;
|
|
3234
|
+
const signerLength = checkpoint.documentSignerSnapshot.byteLength;
|
|
3235
|
+
const byteLength =
|
|
3236
|
+
nativeBackboneCoordinateCheckpointHeaderBytes +
|
|
3237
|
+
coordinateLength +
|
|
3238
|
+
documentLength +
|
|
3239
|
+
signerLength;
|
|
3240
|
+
if (!Number.isSafeInteger(byteLength) || byteLength > 0xffff_ffff) {
|
|
3241
|
+
throw new RangeError("Native backbone coordinate checkpoint is too large");
|
|
3242
|
+
}
|
|
3243
|
+
const bytes = new Uint8Array(byteLength);
|
|
3244
|
+
bytes.set(nativeBackboneCoordinateCheckpointMagic, 0);
|
|
3245
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3246
|
+
view.setUint32(8, nativeBackboneCoordinateCheckpointVersion, true);
|
|
3247
|
+
view.setUint32(12, nativeBackboneCoordinateCheckpointHeaderBytes, true);
|
|
3248
|
+
writeCoordinateCheckpointU64(view, 16, checkpoint.generation);
|
|
3249
|
+
view.setUint32(24, coordinateLength, true);
|
|
3250
|
+
view.setUint32(28, documentLength, true);
|
|
3251
|
+
view.setUint32(32, signerLength, true);
|
|
3252
|
+
view.setUint32(
|
|
3253
|
+
36,
|
|
3254
|
+
coordinateJournalChecksum(checkpoint.coordinateSnapshot),
|
|
3255
|
+
true,
|
|
3256
|
+
);
|
|
3257
|
+
view.setUint32(
|
|
3258
|
+
40,
|
|
3259
|
+
coordinateJournalChecksum(checkpoint.documentSnapshot),
|
|
3260
|
+
true,
|
|
3261
|
+
);
|
|
3262
|
+
view.setUint32(
|
|
3263
|
+
44,
|
|
3264
|
+
coordinateJournalChecksum(checkpoint.documentSignerSnapshot),
|
|
3265
|
+
true,
|
|
3266
|
+
);
|
|
3267
|
+
view.setUint32(48, checkpoint.configurationChecksum, true);
|
|
3268
|
+
view.setUint32(52, coordinateJournalChecksum(bytes.subarray(8, 52)), true);
|
|
3269
|
+
let offset = nativeBackboneCoordinateCheckpointHeaderBytes;
|
|
3270
|
+
bytes.set(checkpoint.coordinateSnapshot, offset);
|
|
3271
|
+
offset += coordinateLength;
|
|
3272
|
+
bytes.set(checkpoint.documentSnapshot, offset);
|
|
3273
|
+
offset += documentLength;
|
|
3274
|
+
bytes.set(checkpoint.documentSignerSnapshot, offset);
|
|
3275
|
+
return bytes;
|
|
3276
|
+
};
|
|
3277
|
+
|
|
3278
|
+
const parseNativeBackboneCoordinateCheckpoint = (
|
|
3279
|
+
bytes: Uint8Array,
|
|
3280
|
+
name: string,
|
|
3281
|
+
): NativeBackboneCoordinateCheckpoint => {
|
|
3282
|
+
if (
|
|
3283
|
+
bytes.byteLength < nativeBackboneCoordinateCheckpointHeaderBytes ||
|
|
3284
|
+
!hasBytesPrefix(bytes, nativeBackboneCoordinateCheckpointMagic)
|
|
3285
|
+
) {
|
|
3286
|
+
throw new Error(`Native backbone ${name} has an invalid checkpoint header`);
|
|
3287
|
+
}
|
|
3288
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3289
|
+
if (
|
|
3290
|
+
view.getUint32(8, true) !== nativeBackboneCoordinateCheckpointVersion ||
|
|
3291
|
+
view.getUint32(12, true) !==
|
|
3292
|
+
nativeBackboneCoordinateCheckpointHeaderBytes ||
|
|
3293
|
+
view.getUint32(52, true) !==
|
|
3294
|
+
coordinateJournalChecksum(bytes.subarray(8, 52))
|
|
3295
|
+
) {
|
|
3296
|
+
throw new Error(`Native backbone ${name} has invalid checkpoint metadata`);
|
|
3297
|
+
}
|
|
3298
|
+
const coordinateLength = view.getUint32(24, true);
|
|
3299
|
+
const documentLength = view.getUint32(28, true);
|
|
3300
|
+
const signerLength = view.getUint32(32, true);
|
|
3301
|
+
const expectedLength =
|
|
3302
|
+
nativeBackboneCoordinateCheckpointHeaderBytes +
|
|
3303
|
+
coordinateLength +
|
|
3304
|
+
documentLength +
|
|
3305
|
+
signerLength;
|
|
3306
|
+
if (
|
|
3307
|
+
!Number.isSafeInteger(expectedLength) ||
|
|
3308
|
+
expectedLength !== bytes.byteLength
|
|
3309
|
+
) {
|
|
3310
|
+
throw new Error(`Native backbone ${name} has an invalid checkpoint length`);
|
|
3311
|
+
}
|
|
3312
|
+
let offset = nativeBackboneCoordinateCheckpointHeaderBytes;
|
|
3313
|
+
const coordinateSnapshot = bytes.subarray(offset, offset + coordinateLength);
|
|
3314
|
+
offset += coordinateLength;
|
|
3315
|
+
const documentSnapshot = bytes.subarray(offset, offset + documentLength);
|
|
3316
|
+
offset += documentLength;
|
|
3317
|
+
const documentSignerSnapshot = bytes.subarray(offset, offset + signerLength);
|
|
3318
|
+
if (
|
|
3319
|
+
coordinateJournalChecksum(coordinateSnapshot) !==
|
|
3320
|
+
view.getUint32(36, true) ||
|
|
3321
|
+
coordinateJournalChecksum(documentSnapshot) !== view.getUint32(40, true) ||
|
|
3322
|
+
coordinateJournalChecksum(documentSignerSnapshot) !==
|
|
3323
|
+
view.getUint32(44, true)
|
|
3324
|
+
) {
|
|
3325
|
+
throw new Error(
|
|
3326
|
+
`Native backbone ${name} has a checkpoint checksum mismatch`,
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
return {
|
|
3330
|
+
configurationChecksum: view.getUint32(48, true),
|
|
3331
|
+
generation: readCoordinateCheckpointU64(view, 16),
|
|
3332
|
+
coordinateSnapshot,
|
|
3333
|
+
documentSnapshot,
|
|
3334
|
+
documentSignerSnapshot,
|
|
3335
|
+
};
|
|
3336
|
+
};
|
|
3337
|
+
|
|
3338
|
+
const encodeNativeBackboneCoordinateCheckpointState = (
|
|
3339
|
+
state: NativeBackboneCoordinateCheckpointState,
|
|
3340
|
+
): Uint8Array => {
|
|
3341
|
+
const bytes = new Uint8Array(nativeBackboneCoordinateCheckpointStateBytes);
|
|
3342
|
+
bytes.set(nativeBackboneCoordinateCheckpointStateMagic, 0);
|
|
3343
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3344
|
+
view.setUint32(8, nativeBackboneCoordinateCheckpointVersion, true);
|
|
3345
|
+
view.setUint32(12, state.configurationChecksum, true);
|
|
3346
|
+
writeCoordinateCheckpointU64(view, 16, state.highwater);
|
|
3347
|
+
writeCoordinateCheckpointU64(view, 24, state.pending?.generation ?? 0n);
|
|
3348
|
+
view.setUint32(32, state.pending?.byteLength ?? 0, true);
|
|
3349
|
+
view.setUint32(36, state.pending?.checksum ?? 0, true);
|
|
3350
|
+
writeCoordinateCheckpointU64(view, 40, state.completed?.generation ?? 0n);
|
|
3351
|
+
view.setUint32(48, state.completed?.byteLength ?? 0, true);
|
|
3352
|
+
view.setUint32(52, state.completed?.checksum ?? 0, true);
|
|
3353
|
+
view.setUint32(56, coordinateJournalChecksum(bytes.subarray(8, 56)), true);
|
|
3354
|
+
return bytes;
|
|
3355
|
+
};
|
|
3356
|
+
|
|
3357
|
+
const parseNativeBackboneCoordinateCheckpointState = (
|
|
3358
|
+
bytes: Uint8Array,
|
|
3359
|
+
name: string,
|
|
3360
|
+
): NativeBackboneCoordinateCheckpointState => {
|
|
3361
|
+
if (
|
|
3362
|
+
bytes.byteLength !== nativeBackboneCoordinateCheckpointStateBytes ||
|
|
3363
|
+
!hasBytesPrefix(bytes, nativeBackboneCoordinateCheckpointStateMagic)
|
|
3364
|
+
) {
|
|
3365
|
+
throw new Error(
|
|
3366
|
+
`Native backbone ${name} has an invalid checkpoint authority`,
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3369
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3370
|
+
if (
|
|
3371
|
+
view.getUint32(8, true) !== nativeBackboneCoordinateCheckpointVersion ||
|
|
3372
|
+
view.getUint32(56, true) !==
|
|
3373
|
+
coordinateJournalChecksum(bytes.subarray(8, 56))
|
|
3374
|
+
) {
|
|
3375
|
+
throw new Error(`Native backbone ${name} has corrupt checkpoint authority`);
|
|
3376
|
+
}
|
|
3377
|
+
const configurationChecksum = view.getUint32(12, true);
|
|
3378
|
+
const highwater = readCoordinateCheckpointU64(view, 16);
|
|
3379
|
+
const pendingGeneration = readCoordinateCheckpointU64(view, 24);
|
|
3380
|
+
const completedGeneration = readCoordinateCheckpointU64(view, 40);
|
|
3381
|
+
const authority = (
|
|
3382
|
+
generation: bigint,
|
|
3383
|
+
lengthOffset: number,
|
|
3384
|
+
checksumOffset: number,
|
|
3385
|
+
): NativeBackboneCoordinateCheckpointAuthority | undefined => {
|
|
3386
|
+
const byteLength = view.getUint32(lengthOffset, true);
|
|
3387
|
+
const checksum = view.getUint32(checksumOffset, true);
|
|
3388
|
+
if (generation === 0n) {
|
|
3389
|
+
if (byteLength !== 0 || checksum !== 0) {
|
|
3390
|
+
throw new Error(`Native backbone ${name} has empty authority metadata`);
|
|
3391
|
+
}
|
|
3392
|
+
return undefined;
|
|
3393
|
+
}
|
|
3394
|
+
if (byteLength < nativeBackboneCoordinateCheckpointHeaderBytes) {
|
|
3395
|
+
throw new Error(`Native backbone ${name} has invalid authority length`);
|
|
3396
|
+
}
|
|
3397
|
+
return { generation, byteLength, checksum };
|
|
3398
|
+
};
|
|
3399
|
+
const pending = authority(pendingGeneration, 32, 36);
|
|
3400
|
+
const completed = authority(completedGeneration, 48, 52);
|
|
3401
|
+
if (
|
|
3402
|
+
(completed && completed.generation > highwater) ||
|
|
3403
|
+
(pending && pending.generation !== highwater) ||
|
|
3404
|
+
(pending && completed && pending.generation <= completed.generation) ||
|
|
3405
|
+
(!pending && completed && completed.generation !== highwater) ||
|
|
3406
|
+
(!pending && !completed && highwater !== 0n)
|
|
3407
|
+
) {
|
|
3408
|
+
throw new Error(`Native backbone ${name} has invalid generation authority`);
|
|
3409
|
+
}
|
|
3410
|
+
return { configurationChecksum, highwater, pending, completed };
|
|
3411
|
+
};
|
|
3412
|
+
|
|
3126
3413
|
const hasCoordinateJournalMagic = (bytes: Uint8Array): boolean =>
|
|
3127
3414
|
bytes.byteLength >= nativeBackboneCoordinateJournalMagic.byteLength &&
|
|
3128
3415
|
nativeBackboneCoordinateJournalMagic.every(
|
|
@@ -3172,11 +3459,24 @@ const resolveCoordinateFlushMaxPendingBytes = (
|
|
|
3172
3459
|
? defaultNativeBackboneCoordinateFlushMaxPendingBytes
|
|
3173
3460
|
: undefined;
|
|
3174
3461
|
|
|
3462
|
+
const validateCoordinateCompactionThreshold = (
|
|
3463
|
+
value: number,
|
|
3464
|
+
name: string,
|
|
3465
|
+
): number => {
|
|
3466
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
3467
|
+
throw new RangeError(
|
|
3468
|
+
`Native backbone ${name} must be a positive safe integer`,
|
|
3469
|
+
);
|
|
3470
|
+
}
|
|
3471
|
+
return value;
|
|
3472
|
+
};
|
|
3473
|
+
|
|
3175
3474
|
type NativeBackboneNodeFs = {
|
|
3176
3475
|
mkdir(path: string, options?: { recursive?: boolean }): Promise<unknown>;
|
|
3177
3476
|
readFile(path: string): Promise<Uint8Array>;
|
|
3178
3477
|
writeFile(path: string, data: Uint8Array): Promise<unknown>;
|
|
3179
3478
|
appendFile(path: string, data: Uint8Array): Promise<unknown>;
|
|
3479
|
+
rename?(from: string, to: string): Promise<unknown>;
|
|
3180
3480
|
/** Explicit capability for pre-allocation-bounded positional reads. */
|
|
3181
3481
|
openBoundedRead?(
|
|
3182
3482
|
path: string,
|
|
@@ -3947,6 +4247,8 @@ const storageAppendResultFromRow = (
|
|
|
3947
4247
|
trimHashRows,
|
|
3948
4248
|
documentTrimmedHeadsProcessed,
|
|
3949
4249
|
documentPreviousContextRow,
|
|
4250
|
+
_trimGidExtension,
|
|
4251
|
+
trimGidRows,
|
|
3950
4252
|
] = row as [
|
|
3951
4253
|
unknown[],
|
|
3952
4254
|
unknown[] | undefined,
|
|
@@ -3957,6 +4259,8 @@ const storageAppendResultFromRow = (
|
|
|
3957
4259
|
unknown[] | undefined,
|
|
3958
4260
|
boolean | undefined,
|
|
3959
4261
|
unknown[] | undefined,
|
|
4262
|
+
undefined,
|
|
4263
|
+
string[] | undefined,
|
|
3960
4264
|
];
|
|
3961
4265
|
return {
|
|
3962
4266
|
entry: storageFactsEntryFromRow(entryRow),
|
|
@@ -3965,6 +4269,8 @@ const storageAppendResultFromRow = (
|
|
|
3965
4269
|
assignedToRangeBoundary,
|
|
3966
4270
|
coordinate: appendCoordinatePlanFromRow(resolution, coordinateRow),
|
|
3967
4271
|
...trimmedRowsAndHashesResult(trimRows, trimHashRows),
|
|
4272
|
+
trimmedGids:
|
|
4273
|
+
trimGidRows ?? (trimRows as unknown[][]).map((trim) => trim[1] as string),
|
|
3968
4274
|
documentTrimmedHeadsProcessed,
|
|
3969
4275
|
documentPreviousContext: documentContextFactsFromRow(
|
|
3970
4276
|
documentPreviousContextRow,
|
|
@@ -3986,6 +4292,8 @@ const committedStorageAppendResultFromRow = (
|
|
|
3986
4292
|
trimHashRows,
|
|
3987
4293
|
documentTrimmedHeadsProcessed,
|
|
3988
4294
|
documentPreviousContextRow,
|
|
4295
|
+
_trimGidExtension,
|
|
4296
|
+
trimGidRows,
|
|
3989
4297
|
] = row as [
|
|
3990
4298
|
unknown[],
|
|
3991
4299
|
unknown[] | undefined,
|
|
@@ -3996,6 +4304,8 @@ const committedStorageAppendResultFromRow = (
|
|
|
3996
4304
|
unknown[] | undefined,
|
|
3997
4305
|
boolean | undefined,
|
|
3998
4306
|
unknown[] | undefined,
|
|
4307
|
+
undefined,
|
|
4308
|
+
string[] | undefined,
|
|
3999
4309
|
];
|
|
4000
4310
|
return {
|
|
4001
4311
|
entry: committedStorageFactsEntryFromRow(entryRow),
|
|
@@ -4004,6 +4314,8 @@ const committedStorageAppendResultFromRow = (
|
|
|
4004
4314
|
assignedToRangeBoundary,
|
|
4005
4315
|
coordinate: appendCoordinatePlanFromRow(resolution, coordinateRow),
|
|
4006
4316
|
...trimmedRowsAndHashesResult(trimRows, trimHashRows),
|
|
4317
|
+
trimmedGids:
|
|
4318
|
+
trimGidRows ?? (trimRows as unknown[][]).map((trim) => trim[1] as string),
|
|
4007
4319
|
documentTrimmedHeadsProcessed,
|
|
4008
4320
|
documentPreviousContext: documentContextFactsFromRow(
|
|
4009
4321
|
documentPreviousContextRow,
|
|
@@ -4015,7 +4327,14 @@ const compactCommittedNoNextStorageAppendResultFromRow = (
|
|
|
4015
4327
|
resolution: RangeResolution,
|
|
4016
4328
|
row: unknown[],
|
|
4017
4329
|
): NativeBackboneAppendResult => {
|
|
4018
|
-
const
|
|
4330
|
+
const appendedTrimGids =
|
|
4331
|
+
row.length >= 12 &&
|
|
4332
|
+
row[row.length - 2] === undefined &&
|
|
4333
|
+
Array.isArray(row[row.length - 1])
|
|
4334
|
+
? (row[row.length - 1] as string[])
|
|
4335
|
+
: undefined;
|
|
4336
|
+
const baseRow = appendedTrimGids ? row.slice(0, -2) : row;
|
|
4337
|
+
const [hash, byteLength, metaBytes, fourth] = baseRow as [
|
|
4019
4338
|
string,
|
|
4020
4339
|
number,
|
|
4021
4340
|
Uint8Array | undefined,
|
|
@@ -4023,7 +4342,7 @@ const compactCommittedNoNextStorageAppendResultFromRow = (
|
|
|
4023
4342
|
];
|
|
4024
4343
|
const hasDigestRow = fourth instanceof Uint8Array;
|
|
4025
4344
|
const hashDigestBytes = hasDigestRow ? (fourth as Uint8Array) : undefined;
|
|
4026
|
-
const rest =
|
|
4345
|
+
const rest = baseRow.slice(hasDigestRow ? 4 : 3);
|
|
4027
4346
|
const usesNestedCoordinateRow = Array.isArray(rest[0]);
|
|
4028
4347
|
let coordinate: NativeBackboneCoordinatePlan;
|
|
4029
4348
|
let leaderRows: unknown[] | undefined;
|
|
@@ -4106,6 +4425,7 @@ const compactCommittedNoNextStorageAppendResultFromRow = (
|
|
|
4106
4425
|
coordinate,
|
|
4107
4426
|
trimmed: [],
|
|
4108
4427
|
trimmedHashes: trimHashRows ?? [],
|
|
4428
|
+
trimmedGids: appendedTrimGids,
|
|
4109
4429
|
documentTrimmedHeadsProcessed,
|
|
4110
4430
|
};
|
|
4111
4431
|
};
|
|
@@ -4114,6 +4434,13 @@ const compactCommittedLatestStorageAppendResultFromRow = (
|
|
|
4114
4434
|
resolution: RangeResolution,
|
|
4115
4435
|
row: unknown[],
|
|
4116
4436
|
): NativeBackboneAppendResult => {
|
|
4437
|
+
const appendedTrimGids =
|
|
4438
|
+
row.length >= 13 &&
|
|
4439
|
+
row[row.length - 2] === undefined &&
|
|
4440
|
+
Array.isArray(row[row.length - 1])
|
|
4441
|
+
? (row[row.length - 1] as string[])
|
|
4442
|
+
: undefined;
|
|
4443
|
+
const baseRow = appendedTrimGids ? row.slice(0, -2) : row;
|
|
4117
4444
|
const [
|
|
4118
4445
|
hash,
|
|
4119
4446
|
byteLength,
|
|
@@ -4127,7 +4454,7 @@ const compactCommittedLatestStorageAppendResultFromRow = (
|
|
|
4127
4454
|
tenth,
|
|
4128
4455
|
eleventh,
|
|
4129
4456
|
twelfth,
|
|
4130
|
-
] =
|
|
4457
|
+
] = baseRow as [
|
|
4131
4458
|
string,
|
|
4132
4459
|
number,
|
|
4133
4460
|
Uint8Array | undefined,
|
|
@@ -4172,6 +4499,7 @@ const compactCommittedLatestStorageAppendResultFromRow = (
|
|
|
4172
4499
|
coordinate,
|
|
4173
4500
|
trimmed: [],
|
|
4174
4501
|
trimmedHashes: trimHashRows ?? [],
|
|
4502
|
+
trimmedGids: appendedTrimGids,
|
|
4175
4503
|
documentTrimmedHeadsProcessed,
|
|
4176
4504
|
documentPreviousContext: documentContextFactsFromRow(
|
|
4177
4505
|
documentPreviousContextRow,
|
|
@@ -4200,6 +4528,20 @@ const preparedCommitFactsFromRow = (
|
|
|
4200
4528
|
return prepared;
|
|
4201
4529
|
};
|
|
4202
4530
|
|
|
4531
|
+
const preparedCommitFactsWithTrimRefsFromRow = (
|
|
4532
|
+
row: unknown[],
|
|
4533
|
+
): NativeBackboneCommittedEntry & {
|
|
4534
|
+
trimmedEntryHashes: string[];
|
|
4535
|
+
trimmedEntryGids: string[];
|
|
4536
|
+
} => {
|
|
4537
|
+
const prepared = committedStorageFactsEntryFromRow(row[0] as unknown[]);
|
|
4538
|
+
return {
|
|
4539
|
+
...prepared,
|
|
4540
|
+
trimmedEntryHashes: row[1] as string[],
|
|
4541
|
+
trimmedEntryGids: row[2] as string[],
|
|
4542
|
+
};
|
|
4543
|
+
};
|
|
4544
|
+
|
|
4203
4545
|
const preparedCommitFactsWithLatestDocumentContextFromRow = (
|
|
4204
4546
|
row: unknown[],
|
|
4205
4547
|
): NativeBackboneCommittedEntry & {
|
|
@@ -4222,12 +4564,39 @@ const preparedCommitFactsWithLatestDocumentContextFromRow = (
|
|
|
4222
4564
|
};
|
|
4223
4565
|
};
|
|
4224
4566
|
|
|
4225
|
-
const
|
|
4567
|
+
const preparedCommitFactsWithLatestDocumentContextAndTrimRefsFromRow = (
|
|
4226
4568
|
row: unknown[],
|
|
4227
4569
|
): NativeBackboneCommittedEntry & {
|
|
4228
|
-
trimmedEntryHashes
|
|
4570
|
+
trimmedEntryHashes: string[];
|
|
4571
|
+
trimmedEntryGids: string[];
|
|
4229
4572
|
documentTrimmedHeadsProcessed?: boolean;
|
|
4573
|
+
documentPreviousContext?: NativeBackboneDocumentContextFacts;
|
|
4230
4574
|
} => {
|
|
4575
|
+
const [
|
|
4576
|
+
entryRow,
|
|
4577
|
+
trimHashRows,
|
|
4578
|
+
trimGidRows,
|
|
4579
|
+
documentTrimmedHeadsProcessed,
|
|
4580
|
+
contextRow,
|
|
4581
|
+
] = row as [
|
|
4582
|
+
unknown[],
|
|
4583
|
+
string[],
|
|
4584
|
+
string[],
|
|
4585
|
+
boolean | undefined,
|
|
4586
|
+
unknown[] | undefined,
|
|
4587
|
+
];
|
|
4588
|
+
return {
|
|
4589
|
+
...committedStorageFactsEntryFromRow(entryRow),
|
|
4590
|
+
trimmedEntryHashes: trimHashRows,
|
|
4591
|
+
trimmedEntryGids: trimGidRows,
|
|
4592
|
+
documentTrimmedHeadsProcessed,
|
|
4593
|
+
documentPreviousContext: documentContextFactsFromRow(contextRow),
|
|
4594
|
+
};
|
|
4595
|
+
};
|
|
4596
|
+
|
|
4597
|
+
const compactPreparedCommitFactsBaseFromRow = (
|
|
4598
|
+
row: unknown[],
|
|
4599
|
+
): { entry: NativeBackboneCommittedEntry; trimRowOffset: number } => {
|
|
4231
4600
|
const [hash, byteLength, metaBytes, fourth] = row as [
|
|
4232
4601
|
string,
|
|
4233
4602
|
number,
|
|
@@ -4235,24 +4604,58 @@ const compactPreparedCommitFactsWithTrimHashesFromRow = (
|
|
|
4235
4604
|
Uint8Array | string[] | undefined,
|
|
4236
4605
|
];
|
|
4237
4606
|
const hasDigestRow = fourth instanceof Uint8Array;
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4607
|
+
return {
|
|
4608
|
+
entry: {
|
|
4609
|
+
cid: hash,
|
|
4610
|
+
hash,
|
|
4611
|
+
next: [],
|
|
4612
|
+
metaBytes,
|
|
4613
|
+
byteLength,
|
|
4614
|
+
hashDigestBytes: hasDigestRow ? fourth : undefined,
|
|
4615
|
+
},
|
|
4616
|
+
trimRowOffset: hasDigestRow ? 4 : 3,
|
|
4617
|
+
};
|
|
4618
|
+
};
|
|
4619
|
+
|
|
4620
|
+
const compactPreparedCommitFactsWithTrimHashesFromRow = (
|
|
4621
|
+
row: unknown[],
|
|
4622
|
+
): NativeBackboneCommittedEntry & {
|
|
4623
|
+
trimmedEntryHashes?: string[];
|
|
4624
|
+
documentTrimmedHeadsProcessed?: boolean;
|
|
4625
|
+
} => {
|
|
4626
|
+
const { entry, trimRowOffset } = compactPreparedCommitFactsBaseFromRow(row);
|
|
4627
|
+
const trimHashRows = row[trimRowOffset] as string[] | undefined;
|
|
4628
|
+
const documentTrimmedHeadsProcessed = row[trimRowOffset + 1] as
|
|
4242
4629
|
| boolean
|
|
4243
4630
|
| undefined;
|
|
4244
4631
|
return {
|
|
4245
|
-
|
|
4246
|
-
hash,
|
|
4247
|
-
next: [],
|
|
4248
|
-
metaBytes,
|
|
4249
|
-
byteLength,
|
|
4250
|
-
hashDigestBytes,
|
|
4632
|
+
...entry,
|
|
4251
4633
|
trimmedEntryHashes: trimHashRows ?? [],
|
|
4252
4634
|
documentTrimmedHeadsProcessed,
|
|
4253
4635
|
};
|
|
4254
4636
|
};
|
|
4255
4637
|
|
|
4638
|
+
const compactPreparedCommitFactsWithTrimRefsFromRow = (
|
|
4639
|
+
row: unknown[],
|
|
4640
|
+
): NativeBackboneCommittedEntry & {
|
|
4641
|
+
trimmedEntryHashes: string[];
|
|
4642
|
+
trimmedEntryGids: string[];
|
|
4643
|
+
documentTrimmedHeadsProcessed?: boolean;
|
|
4644
|
+
} => {
|
|
4645
|
+
const { entry, trimRowOffset } = compactPreparedCommitFactsBaseFromRow(row);
|
|
4646
|
+
const trimHashRows = row[trimRowOffset] as string[];
|
|
4647
|
+
const trimGidRows = row[trimRowOffset + 1] as string[];
|
|
4648
|
+
const documentTrimmedHeadsProcessed = row[trimRowOffset + 2] as
|
|
4649
|
+
| boolean
|
|
4650
|
+
| undefined;
|
|
4651
|
+
return {
|
|
4652
|
+
...entry,
|
|
4653
|
+
trimmedEntryHashes: trimHashRows,
|
|
4654
|
+
trimmedEntryGids: trimGidRows,
|
|
4655
|
+
documentTrimmedHeadsProcessed,
|
|
4656
|
+
};
|
|
4657
|
+
};
|
|
4658
|
+
|
|
4256
4659
|
const nativeLogCommitEntryColumns = (
|
|
4257
4660
|
entries: NativeBackboneLogCommitEntry[],
|
|
4258
4661
|
) => {
|
|
@@ -5080,7 +5483,9 @@ class NativeBackboneLogGraph {
|
|
|
5080
5483
|
| (NativeBackboneCommittedEntry & {
|
|
5081
5484
|
trimmedEntries?: NativeBackboneLogEntry[];
|
|
5082
5485
|
trimmedEntryHashes?: string[];
|
|
5486
|
+
trimmedEntryGids?: string[];
|
|
5083
5487
|
documentTrimmedHeadsProcessed?: boolean;
|
|
5488
|
+
documentPreviousContext?: NativeBackboneDocumentContextFacts;
|
|
5084
5489
|
})
|
|
5085
5490
|
| undefined {
|
|
5086
5491
|
if (this.options?.commitBlocks === false) {
|
|
@@ -5099,32 +5504,32 @@ class NativeBackboneLogGraph {
|
|
|
5099
5504
|
const documentIndex = input.documentIndex;
|
|
5100
5505
|
const documentIndexArgs = nativeDocumentIndexArgs(documentIndex);
|
|
5101
5506
|
const projection = documentIndex?.projection;
|
|
5507
|
+
if (
|
|
5508
|
+
!documentIndexArgs &&
|
|
5509
|
+
input.resolveTrimmedEntries === false &&
|
|
5510
|
+
input.trimLengthTo != null &&
|
|
5511
|
+
this.native.prepare_plain_entry_commit_facts_trim_refs
|
|
5512
|
+
) {
|
|
5513
|
+
return preparedCommitFactsWithTrimRefsFromRow(
|
|
5514
|
+
this.native.prepare_plain_entry_commit_facts_trim_refs(
|
|
5515
|
+
wallTime,
|
|
5516
|
+
logical,
|
|
5517
|
+
input.gid,
|
|
5518
|
+
input.next ?? [],
|
|
5519
|
+
entryType,
|
|
5520
|
+
input.metaData,
|
|
5521
|
+
input.payloadData,
|
|
5522
|
+
input.trimLengthTo,
|
|
5523
|
+
),
|
|
5524
|
+
);
|
|
5525
|
+
}
|
|
5102
5526
|
if (
|
|
5103
5527
|
documentIndex?.useLatestContext &&
|
|
5104
5528
|
documentIndexArgs &&
|
|
5105
5529
|
input.resolveTrimmedEntries === false
|
|
5106
5530
|
) {
|
|
5107
5531
|
if (projection && this.options?.documentProjectionPlanId) {
|
|
5108
|
-
|
|
5109
|
-
this.native.prepare_plain_entry_commit_latest_facts_document_index_cached_plan_trim_hashes(
|
|
5110
|
-
wallTime,
|
|
5111
|
-
logical,
|
|
5112
|
-
input.gid,
|
|
5113
|
-
entryType,
|
|
5114
|
-
input.metaData,
|
|
5115
|
-
input.payloadData,
|
|
5116
|
-
input.trimLengthTo,
|
|
5117
|
-
documentIndex.key,
|
|
5118
|
-
documentIndex.byteElementIndexLimit ?? 0,
|
|
5119
|
-
documentIndex.deleteTrimmedHeads === true,
|
|
5120
|
-
this.options.documentProjectionPlanId(projection.plan),
|
|
5121
|
-
projection.encodedDocument,
|
|
5122
|
-
projection.signer,
|
|
5123
|
-
),
|
|
5124
|
-
);
|
|
5125
|
-
}
|
|
5126
|
-
return preparedCommitFactsWithLatestDocumentContextFromRow(
|
|
5127
|
-
this.native.prepare_plain_entry_commit_latest_facts_document_index_trim_hashes(
|
|
5532
|
+
const args = [
|
|
5128
5533
|
wallTime,
|
|
5129
5534
|
logical,
|
|
5130
5535
|
input.gid,
|
|
@@ -5133,12 +5538,53 @@ class NativeBackboneLogGraph {
|
|
|
5133
5538
|
input.payloadData,
|
|
5134
5539
|
input.trimLengthTo,
|
|
5135
5540
|
documentIndex.key,
|
|
5136
|
-
documentIndex.valuePrefixBytes ?? EMPTY_UINT8_ARRAY,
|
|
5137
5541
|
documentIndex.byteElementIndexLimit ?? 0,
|
|
5138
5542
|
documentIndex.deleteTrimmedHeads === true,
|
|
5139
|
-
projection
|
|
5140
|
-
projection
|
|
5141
|
-
projection
|
|
5543
|
+
this.options.documentProjectionPlanId(projection.plan),
|
|
5544
|
+
projection.encodedDocument,
|
|
5545
|
+
projection.signer,
|
|
5546
|
+
] as const;
|
|
5547
|
+
const prepareTrimRefs =
|
|
5548
|
+
this.native
|
|
5549
|
+
.prepare_plain_entry_commit_latest_facts_document_index_cached_plan_trim_refs;
|
|
5550
|
+
if (prepareTrimRefs) {
|
|
5551
|
+
return preparedCommitFactsWithLatestDocumentContextAndTrimRefsFromRow(
|
|
5552
|
+
prepareTrimRefs.call(this.native, ...args),
|
|
5553
|
+
);
|
|
5554
|
+
}
|
|
5555
|
+
return preparedCommitFactsWithLatestDocumentContextFromRow(
|
|
5556
|
+
this.native.prepare_plain_entry_commit_latest_facts_document_index_cached_plan_trim_hashes(
|
|
5557
|
+
...args,
|
|
5558
|
+
),
|
|
5559
|
+
);
|
|
5560
|
+
}
|
|
5561
|
+
const args = [
|
|
5562
|
+
wallTime,
|
|
5563
|
+
logical,
|
|
5564
|
+
input.gid,
|
|
5565
|
+
entryType,
|
|
5566
|
+
input.metaData,
|
|
5567
|
+
input.payloadData,
|
|
5568
|
+
input.trimLengthTo,
|
|
5569
|
+
documentIndex.key,
|
|
5570
|
+
documentIndex.valuePrefixBytes ?? EMPTY_UINT8_ARRAY,
|
|
5571
|
+
documentIndex.byteElementIndexLimit ?? 0,
|
|
5572
|
+
documentIndex.deleteTrimmedHeads === true,
|
|
5573
|
+
projection?.plan,
|
|
5574
|
+
projection?.encodedDocument,
|
|
5575
|
+
projection?.signer,
|
|
5576
|
+
] as const;
|
|
5577
|
+
const prepareTrimRefs =
|
|
5578
|
+
this.native
|
|
5579
|
+
.prepare_plain_entry_commit_latest_facts_document_index_trim_refs;
|
|
5580
|
+
if (prepareTrimRefs) {
|
|
5581
|
+
return preparedCommitFactsWithLatestDocumentContextAndTrimRefsFromRow(
|
|
5582
|
+
prepareTrimRefs.call(this.native, ...args),
|
|
5583
|
+
);
|
|
5584
|
+
}
|
|
5585
|
+
return preparedCommitFactsWithLatestDocumentContextFromRow(
|
|
5586
|
+
this.native.prepare_plain_entry_commit_latest_facts_document_index_trim_hashes(
|
|
5587
|
+
...args,
|
|
5142
5588
|
),
|
|
5143
5589
|
);
|
|
5144
5590
|
}
|
|
@@ -5153,49 +5599,71 @@ class NativeBackboneLogGraph {
|
|
|
5153
5599
|
const projectionPlanId = this.options.documentProjectionPlanId(
|
|
5154
5600
|
projection.plan,
|
|
5155
5601
|
);
|
|
5602
|
+
const plainPutPayloadArgs = [
|
|
5603
|
+
wallTime,
|
|
5604
|
+
logical,
|
|
5605
|
+
input.gid,
|
|
5606
|
+
entryType,
|
|
5607
|
+
input.metaData,
|
|
5608
|
+
input.payloadData,
|
|
5609
|
+
input.trimLengthTo,
|
|
5610
|
+
documentIndex.key,
|
|
5611
|
+
documentIndex.existingCreated == null
|
|
5612
|
+
? ""
|
|
5613
|
+
: integerString(documentIndex.existingCreated),
|
|
5614
|
+
documentIndex.byteElementIndexLimit ?? 0,
|
|
5615
|
+
documentIndex.deleteTrimmedHeads === true,
|
|
5616
|
+
projectionPlanId,
|
|
5617
|
+
projection.signer,
|
|
5618
|
+
] as const;
|
|
5619
|
+
const plainPutPayloadCommitWithTrimRefs =
|
|
5620
|
+
this.native
|
|
5621
|
+
.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_refs_plain_put_payload;
|
|
5622
|
+
if (plainPutPayloadCommitWithTrimRefs) {
|
|
5623
|
+
return compactPreparedCommitFactsWithTrimRefsFromRow(
|
|
5624
|
+
plainPutPayloadCommitWithTrimRefs.call(
|
|
5625
|
+
this.native,
|
|
5626
|
+
...plainPutPayloadArgs,
|
|
5627
|
+
),
|
|
5628
|
+
);
|
|
5629
|
+
}
|
|
5156
5630
|
const plainPutPayloadCommit =
|
|
5157
5631
|
this.native
|
|
5158
5632
|
.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes_plain_put_payload;
|
|
5159
5633
|
if (plainPutPayloadCommit) {
|
|
5160
5634
|
return compactPreparedCommitFactsWithTrimHashesFromRow(
|
|
5161
|
-
plainPutPayloadCommit.call(
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5635
|
+
plainPutPayloadCommit.call(this.native, ...plainPutPayloadArgs),
|
|
5636
|
+
);
|
|
5637
|
+
}
|
|
5638
|
+
const args = [
|
|
5639
|
+
wallTime,
|
|
5640
|
+
logical,
|
|
5641
|
+
input.gid,
|
|
5642
|
+
entryType,
|
|
5643
|
+
input.metaData,
|
|
5644
|
+
input.payloadData,
|
|
5645
|
+
input.trimLengthTo,
|
|
5646
|
+
documentIndex.key,
|
|
5647
|
+
documentIndex.existingCreated == null
|
|
5648
|
+
? ""
|
|
5649
|
+
: integerString(documentIndex.existingCreated),
|
|
5650
|
+
documentIndex.byteElementIndexLimit ?? 0,
|
|
5651
|
+
documentIndex.deleteTrimmedHeads === true,
|
|
5652
|
+
projectionPlanId,
|
|
5653
|
+
projection.encodedDocument,
|
|
5654
|
+
projection.signer,
|
|
5655
|
+
] as const;
|
|
5656
|
+
const prepareTrimRefs =
|
|
5657
|
+
this.native
|
|
5658
|
+
.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_refs;
|
|
5659
|
+
if (prepareTrimRefs) {
|
|
5660
|
+
return compactPreparedCommitFactsWithTrimRefsFromRow(
|
|
5661
|
+
prepareTrimRefs.call(this.native, ...args),
|
|
5179
5662
|
);
|
|
5180
5663
|
}
|
|
5181
5664
|
return compactPreparedCommitFactsWithTrimHashesFromRow(
|
|
5182
5665
|
this.native.prepare_plain_entry_commit_no_next_facts_document_index_cached_plan_compact_trim_hashes(
|
|
5183
|
-
|
|
5184
|
-
logical,
|
|
5185
|
-
input.gid,
|
|
5186
|
-
entryType,
|
|
5187
|
-
input.metaData,
|
|
5188
|
-
input.payloadData,
|
|
5189
|
-
input.trimLengthTo,
|
|
5190
|
-
documentIndex.key,
|
|
5191
|
-
documentIndex.existingCreated == null
|
|
5192
|
-
? ""
|
|
5193
|
-
: integerString(documentIndex.existingCreated),
|
|
5194
|
-
documentIndex.byteElementIndexLimit ?? 0,
|
|
5195
|
-
documentIndex.deleteTrimmedHeads === true,
|
|
5196
|
-
projectionPlanId,
|
|
5197
|
-
projection.encodedDocument,
|
|
5198
|
-
projection.signer,
|
|
5666
|
+
...args,
|
|
5199
5667
|
),
|
|
5200
5668
|
);
|
|
5201
5669
|
}
|
|
@@ -5302,6 +5770,24 @@ class NativeBackboneLogGraph {
|
|
|
5302
5770
|
input.trimLengthTo != null &&
|
|
5303
5771
|
hasNoNext
|
|
5304
5772
|
) {
|
|
5773
|
+
const prepareTrimRefs =
|
|
5774
|
+
this.native
|
|
5775
|
+
.prepare_plain_entry_commit_no_next_facts_document_index_compact_trim_refs;
|
|
5776
|
+
if (prepareTrimRefs) {
|
|
5777
|
+
return compactPreparedCommitFactsWithTrimRefsFromRow(
|
|
5778
|
+
prepareTrimRefs.call(
|
|
5779
|
+
this.native,
|
|
5780
|
+
wallTime,
|
|
5781
|
+
logical,
|
|
5782
|
+
input.gid,
|
|
5783
|
+
entryType,
|
|
5784
|
+
input.metaData,
|
|
5785
|
+
input.payloadData,
|
|
5786
|
+
input.trimLengthTo,
|
|
5787
|
+
...documentIndexArgs,
|
|
5788
|
+
),
|
|
5789
|
+
);
|
|
5790
|
+
}
|
|
5305
5791
|
return compactPreparedCommitFactsWithTrimHashesFromRow(
|
|
5306
5792
|
this.native.prepare_plain_entry_commit_no_next_facts_document_index_compact_trim_hashes(
|
|
5307
5793
|
wallTime,
|
|
@@ -6455,9 +6941,7 @@ export class NativePeerbitBackbone {
|
|
|
6455
6941
|
end2: bigint | number | string;
|
|
6456
6942
|
limit: number;
|
|
6457
6943
|
}): bigint[] | undefined {
|
|
6458
|
-
if (
|
|
6459
|
-
typeof this.native.entry_hash_numbers_in_range_limited !== "function"
|
|
6460
|
-
) {
|
|
6944
|
+
if (typeof this.native.entry_hash_numbers_in_range_limited !== "function") {
|
|
6461
6945
|
return undefined;
|
|
6462
6946
|
}
|
|
6463
6947
|
return rowsToNumbers(
|
|
@@ -8768,6 +9252,7 @@ export class NativeBackboneNodeCoordinatePersistenceStore
|
|
|
8768
9252
|
maxBytes: number,
|
|
8769
9253
|
) => Promise<Uint8Array | undefined>;
|
|
8770
9254
|
readonly durableBarrier?: (name?: string) => Promise<void>;
|
|
9255
|
+
readonly atomicReplace?: (name: string, bytes: Uint8Array) => Promise<void>;
|
|
8771
9256
|
|
|
8772
9257
|
constructor(
|
|
8773
9258
|
private readonly directory: string,
|
|
@@ -8783,6 +9268,12 @@ export class NativeBackboneNodeCoordinatePersistenceStore
|
|
|
8783
9268
|
this.readLimited = (name, maxBytes) =>
|
|
8784
9269
|
this.readWithinLimit(name, maxBytes);
|
|
8785
9270
|
}
|
|
9271
|
+
if (
|
|
9272
|
+
!fs ||
|
|
9273
|
+
(typeof fs.open === "function" && typeof fs.rename === "function")
|
|
9274
|
+
) {
|
|
9275
|
+
this.atomicReplace = (name, bytes) => this.replaceAtomically(name, bytes);
|
|
9276
|
+
}
|
|
8786
9277
|
}
|
|
8787
9278
|
|
|
8788
9279
|
private async nodeFs(): Promise<NativeBackboneNodeFs> {
|
|
@@ -8965,6 +9456,51 @@ export class NativeBackboneNodeCoordinatePersistenceStore
|
|
|
8965
9456
|
await fs.writeFile(path, bytes);
|
|
8966
9457
|
}
|
|
8967
9458
|
|
|
9459
|
+
private async replaceAtomically(
|
|
9460
|
+
name: string,
|
|
9461
|
+
bytes: Uint8Array,
|
|
9462
|
+
): Promise<void> {
|
|
9463
|
+
const fs = await this.ensureDirectory();
|
|
9464
|
+
if (!fs.open || !fs.rename) {
|
|
9465
|
+
throw new Error(
|
|
9466
|
+
"Node coordinate persistence does not expose atomic file replacement",
|
|
9467
|
+
);
|
|
9468
|
+
}
|
|
9469
|
+
const validName = validateCoordinatePersistenceName(name);
|
|
9470
|
+
const temporaryName = validateCoordinatePersistenceName(`${validName}.tmp`);
|
|
9471
|
+
const path = await this.filePath(validName);
|
|
9472
|
+
const temporaryPath = await this.filePath(temporaryName);
|
|
9473
|
+
await this.closeAppendHandle(path);
|
|
9474
|
+
await this.closeAppendHandle(temporaryPath);
|
|
9475
|
+
await fs.rm(temporaryPath, { force: true });
|
|
9476
|
+
await fs.writeFile(temporaryPath, bytes);
|
|
9477
|
+
let temporaryHandle: NativeBackboneNodeAppendFileHandle | undefined;
|
|
9478
|
+
try {
|
|
9479
|
+
temporaryHandle = await fs.open(temporaryPath, "r");
|
|
9480
|
+
if (typeof temporaryHandle.sync !== "function") {
|
|
9481
|
+
throw new Error(
|
|
9482
|
+
"Node coordinate persistence atomic replacement does not expose FileHandle.sync",
|
|
9483
|
+
);
|
|
9484
|
+
}
|
|
9485
|
+
await temporaryHandle.sync();
|
|
9486
|
+
} finally {
|
|
9487
|
+
await temporaryHandle?.close();
|
|
9488
|
+
}
|
|
9489
|
+
await fs.rename(temporaryPath, path);
|
|
9490
|
+
let directoryHandle: NativeBackboneNodeAppendFileHandle | undefined;
|
|
9491
|
+
try {
|
|
9492
|
+
directoryHandle = await fs.open(this.directory, "r");
|
|
9493
|
+
if (typeof directoryHandle.sync !== "function") {
|
|
9494
|
+
throw new Error(
|
|
9495
|
+
"Node coordinate persistence atomic replacement does not expose directory sync",
|
|
9496
|
+
);
|
|
9497
|
+
}
|
|
9498
|
+
await directoryHandle.sync();
|
|
9499
|
+
} finally {
|
|
9500
|
+
await directoryHandle?.close();
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9503
|
+
|
|
8968
9504
|
async append(name: string, bytes: Uint8Array): Promise<void> {
|
|
8969
9505
|
if (this.appendFailure !== undefined) {
|
|
8970
9506
|
throw this.appendFailure;
|
|
@@ -9317,6 +9853,7 @@ export class NativeBackboneBufferedCoordinatePersistenceStore
|
|
|
9317
9853
|
) => Promise<Uint8Array | undefined>;
|
|
9318
9854
|
readonly supportsRemoval: boolean;
|
|
9319
9855
|
readonly durableBarrier?: (name?: string) => Promise<void>;
|
|
9856
|
+
readonly atomicReplace?: (name: string, bytes: Uint8Array) => Promise<void>;
|
|
9320
9857
|
|
|
9321
9858
|
constructor(
|
|
9322
9859
|
private readonly inner: NativeBackboneCoordinatePersistenceStore,
|
|
@@ -9335,6 +9872,12 @@ export class NativeBackboneBufferedCoordinatePersistenceStore
|
|
|
9335
9872
|
await inner.durableBarrier!(name);
|
|
9336
9873
|
};
|
|
9337
9874
|
}
|
|
9875
|
+
if (typeof inner.atomicReplace === "function") {
|
|
9876
|
+
this.atomicReplace = async (name, bytes) => {
|
|
9877
|
+
await this.flush(name);
|
|
9878
|
+
await inner.atomicReplace!(name, bytes);
|
|
9879
|
+
};
|
|
9880
|
+
}
|
|
9338
9881
|
}
|
|
9339
9882
|
|
|
9340
9883
|
private buffer(name: string): Uint8Array[] {
|
|
@@ -9480,7 +10023,7 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9480
10023
|
readonly flushIntervalMs?: number;
|
|
9481
10024
|
readonly compactMaxJournalBytes?: number;
|
|
9482
10025
|
readonly compactMaxJournalRecords?: number;
|
|
9483
|
-
readonly crashSafeCompaction
|
|
10026
|
+
readonly crashSafeCompaction: boolean;
|
|
9484
10027
|
readonly durableBarrier: boolean;
|
|
9485
10028
|
readonly supportsDrop: boolean;
|
|
9486
10029
|
readonly dropIsTerminal = true;
|
|
@@ -9490,9 +10033,32 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9490
10033
|
private readonly documentJournalFile: string;
|
|
9491
10034
|
private readonly documentSignerSnapshotFile: string;
|
|
9492
10035
|
private readonly documentSignerJournalFile: string;
|
|
10036
|
+
private readonly checkpointConfigurationChecksum: number;
|
|
10037
|
+
private readonly checkpointStateFile: string;
|
|
10038
|
+
private readonly checkpointFiles: Record<
|
|
10039
|
+
NativeBackboneCoordinateCheckpointSlot,
|
|
10040
|
+
string
|
|
10041
|
+
>;
|
|
10042
|
+
private readonly checkpointJournalFiles: Record<
|
|
10043
|
+
NativeBackboneCoordinateCheckpointSlot,
|
|
10044
|
+
{
|
|
10045
|
+
coordinate: string;
|
|
10046
|
+
document: string;
|
|
10047
|
+
signer: string;
|
|
10048
|
+
}
|
|
10049
|
+
>;
|
|
9493
10050
|
private journalInitialized: boolean | undefined;
|
|
10051
|
+
private journalByteLength = 0;
|
|
10052
|
+
private journalRecordCount = 0;
|
|
9494
10053
|
private documentJournalInitialized: boolean | undefined;
|
|
10054
|
+
private documentJournalByteLength = 0;
|
|
10055
|
+
private documentJournalRecordCount = 0;
|
|
9495
10056
|
private documentSignerJournalInitialized: boolean | undefined;
|
|
10057
|
+
private documentSignerJournalByteLength = 0;
|
|
10058
|
+
private documentSignerJournalRecordCount = 0;
|
|
10059
|
+
private checkpointHighwater = 0n;
|
|
10060
|
+
private checkpointCompleted?: NativeBackboneCoordinateCheckpointAuthority;
|
|
10061
|
+
private checkpointPending?: NativeBackboneCoordinateCheckpointAuthority;
|
|
9496
10062
|
private lastFlushMs = Date.now();
|
|
9497
10063
|
private persistenceQueue: Promise<unknown> | undefined;
|
|
9498
10064
|
private persistenceLifecycle:
|
|
@@ -9539,24 +10105,87 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9539
10105
|
options.documentSignerJournal ??
|
|
9540
10106
|
nativeBackboneCoordinatePersistenceFiles.documentSignerJournal,
|
|
9541
10107
|
);
|
|
10108
|
+
this.checkpointConfigurationChecksum = coordinateJournalChecksum(
|
|
10109
|
+
new TextEncoder().encode(
|
|
10110
|
+
JSON.stringify([
|
|
10111
|
+
this.snapshotFile,
|
|
10112
|
+
this.journalFile,
|
|
10113
|
+
this.documentSnapshotFile,
|
|
10114
|
+
this.documentJournalFile,
|
|
10115
|
+
this.documentSignerSnapshotFile,
|
|
10116
|
+
this.documentSignerJournalFile,
|
|
10117
|
+
]),
|
|
10118
|
+
),
|
|
10119
|
+
);
|
|
10120
|
+
this.checkpointStateFile = validateCoordinatePersistenceName(
|
|
10121
|
+
`${this.snapshotFile}.checkpoint-state`,
|
|
10122
|
+
);
|
|
10123
|
+
this.checkpointFiles = {
|
|
10124
|
+
a: validateCoordinatePersistenceName(`${this.snapshotFile}.checkpoint-a`),
|
|
10125
|
+
b: validateCoordinatePersistenceName(`${this.snapshotFile}.checkpoint-b`),
|
|
10126
|
+
};
|
|
10127
|
+
this.checkpointJournalFiles = {
|
|
10128
|
+
a: {
|
|
10129
|
+
coordinate: validateCoordinatePersistenceName(
|
|
10130
|
+
`${this.journalFile}.checkpoint-a`,
|
|
10131
|
+
),
|
|
10132
|
+
document: validateCoordinatePersistenceName(
|
|
10133
|
+
`${this.documentJournalFile}.checkpoint-a`,
|
|
10134
|
+
),
|
|
10135
|
+
signer: validateCoordinatePersistenceName(
|
|
10136
|
+
`${this.documentSignerJournalFile}.checkpoint-a`,
|
|
10137
|
+
),
|
|
10138
|
+
},
|
|
10139
|
+
b: {
|
|
10140
|
+
coordinate: validateCoordinatePersistenceName(
|
|
10141
|
+
`${this.journalFile}.checkpoint-b`,
|
|
10142
|
+
),
|
|
10143
|
+
document: validateCoordinatePersistenceName(
|
|
10144
|
+
`${this.documentJournalFile}.checkpoint-b`,
|
|
10145
|
+
),
|
|
10146
|
+
signer: validateCoordinatePersistenceName(
|
|
10147
|
+
`${this.documentSignerJournalFile}.checkpoint-b`,
|
|
10148
|
+
),
|
|
10149
|
+
},
|
|
10150
|
+
};
|
|
9542
10151
|
if (
|
|
9543
|
-
this.configuredFiles().includes(
|
|
10152
|
+
this.configuredFiles().includes(
|
|
10153
|
+
nativeBackboneCoordinateDropTombstoneFile,
|
|
10154
|
+
) ||
|
|
10155
|
+
new Set(this.configuredFiles()).size !== this.configuredFiles().length
|
|
9544
10156
|
) {
|
|
9545
10157
|
throw new Error(
|
|
9546
|
-
"Native backbone coordinate persistence
|
|
10158
|
+
"Native backbone coordinate persistence files conflict with one another or with its drop tombstone",
|
|
9547
10159
|
);
|
|
9548
10160
|
}
|
|
10161
|
+
this.crashSafeCompaction =
|
|
10162
|
+
typeof store.atomicReplace === "function" &&
|
|
10163
|
+
this.supportsDrop &&
|
|
10164
|
+
this.durableBarrier;
|
|
9549
10165
|
this.flushOnAppend = options.flushOnAppend ?? true;
|
|
9550
10166
|
this.flushMaxPendingBytes = resolveCoordinateFlushMaxPendingBytes(options);
|
|
9551
10167
|
if (options.flushIntervalMs != null) {
|
|
9552
10168
|
this.flushIntervalMs = Math.max(0, options.flushIntervalMs);
|
|
9553
10169
|
}
|
|
9554
10170
|
if (
|
|
9555
|
-
options.compactMaxJournalBytes != null ||
|
|
9556
|
-
|
|
10171
|
+
(options.compactMaxJournalBytes != null ||
|
|
10172
|
+
options.compactMaxJournalRecords != null) &&
|
|
10173
|
+
!this.crashSafeCompaction
|
|
9557
10174
|
) {
|
|
9558
10175
|
throw new Error(nativeBackboneCoordinateCompactionDisabledMessage);
|
|
9559
10176
|
}
|
|
10177
|
+
if (options.compactMaxJournalBytes != null) {
|
|
10178
|
+
this.compactMaxJournalBytes = validateCoordinateCompactionThreshold(
|
|
10179
|
+
options.compactMaxJournalBytes,
|
|
10180
|
+
"compactMaxJournalBytes",
|
|
10181
|
+
);
|
|
10182
|
+
}
|
|
10183
|
+
if (options.compactMaxJournalRecords != null) {
|
|
10184
|
+
this.compactMaxJournalRecords = validateCoordinateCompactionThreshold(
|
|
10185
|
+
options.compactMaxJournalRecords,
|
|
10186
|
+
"compactMaxJournalRecords",
|
|
10187
|
+
);
|
|
10188
|
+
}
|
|
9560
10189
|
}
|
|
9561
10190
|
|
|
9562
10191
|
/** Durable operation-intent capability for strict shared-log transactions. */
|
|
@@ -9564,7 +10193,7 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9564
10193
|
return this.store;
|
|
9565
10194
|
}
|
|
9566
10195
|
|
|
9567
|
-
private
|
|
10196
|
+
private legacyConfiguredFiles(): string[] {
|
|
9568
10197
|
return [
|
|
9569
10198
|
this.snapshotFile,
|
|
9570
10199
|
this.journalFile,
|
|
@@ -9575,6 +10204,47 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9575
10204
|
];
|
|
9576
10205
|
}
|
|
9577
10206
|
|
|
10207
|
+
private configuredFiles(): string[] {
|
|
10208
|
+
return [
|
|
10209
|
+
...this.legacyConfiguredFiles(),
|
|
10210
|
+
this.checkpointStateFile,
|
|
10211
|
+
this.checkpointFiles.a,
|
|
10212
|
+
this.checkpointFiles.b,
|
|
10213
|
+
this.checkpointJournalFiles.a.coordinate,
|
|
10214
|
+
this.checkpointJournalFiles.a.document,
|
|
10215
|
+
this.checkpointJournalFiles.a.signer,
|
|
10216
|
+
this.checkpointJournalFiles.b.coordinate,
|
|
10217
|
+
this.checkpointJournalFiles.b.document,
|
|
10218
|
+
this.checkpointJournalFiles.b.signer,
|
|
10219
|
+
`${this.checkpointStateFile}.tmp`,
|
|
10220
|
+
`${this.checkpointFiles.a}.tmp`,
|
|
10221
|
+
`${this.checkpointFiles.b}.tmp`,
|
|
10222
|
+
`${this.journalFile}.tmp`,
|
|
10223
|
+
`${nativeBackboneCoordinateDropTombstoneFile}.tmp`,
|
|
10224
|
+
];
|
|
10225
|
+
}
|
|
10226
|
+
|
|
10227
|
+
private checkpointSlot(
|
|
10228
|
+
generation: bigint,
|
|
10229
|
+
): NativeBackboneCoordinateCheckpointSlot {
|
|
10230
|
+
return generation % 2n === 1n ? "a" : "b";
|
|
10231
|
+
}
|
|
10232
|
+
|
|
10233
|
+
private activeJournalFiles(): {
|
|
10234
|
+
coordinate: string;
|
|
10235
|
+
document: string;
|
|
10236
|
+
signer: string;
|
|
10237
|
+
} {
|
|
10238
|
+
const completed = this.checkpointCompleted;
|
|
10239
|
+
return completed
|
|
10240
|
+
? this.checkpointJournalFiles[this.checkpointSlot(completed.generation)]
|
|
10241
|
+
: {
|
|
10242
|
+
coordinate: this.journalFile,
|
|
10243
|
+
document: this.documentJournalFile,
|
|
10244
|
+
signer: this.documentSignerJournalFile,
|
|
10245
|
+
};
|
|
10246
|
+
}
|
|
10247
|
+
|
|
9578
10248
|
private assertLifecycleActive(operation: string): void {
|
|
9579
10249
|
if (this.persistenceLifecycle !== "active") {
|
|
9580
10250
|
throw new Error(
|
|
@@ -9584,9 +10254,7 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9584
10254
|
}
|
|
9585
10255
|
|
|
9586
10256
|
private assertActive(operation: string): void {
|
|
9587
|
-
|
|
9588
|
-
throw this.persistenceFailure;
|
|
9589
|
-
}
|
|
10257
|
+
this.assertPersistenceHealthy();
|
|
9590
10258
|
this.assertLifecycleActive(operation);
|
|
9591
10259
|
if (this.dropInitiatedOnGeneration) {
|
|
9592
10260
|
throw new Error(
|
|
@@ -9595,13 +10263,44 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9595
10263
|
}
|
|
9596
10264
|
}
|
|
9597
10265
|
|
|
10266
|
+
private assertPersistenceHealthy(): void {
|
|
10267
|
+
if (this.persistenceFailure !== undefined) {
|
|
10268
|
+
throw this.persistenceFailure;
|
|
10269
|
+
}
|
|
10270
|
+
}
|
|
10271
|
+
|
|
9598
10272
|
private resetJournalTracking(): void {
|
|
9599
10273
|
this.journalInitialized = undefined;
|
|
10274
|
+
this.journalByteLength = 0;
|
|
10275
|
+
this.journalRecordCount = 0;
|
|
9600
10276
|
this.documentJournalInitialized = undefined;
|
|
10277
|
+
this.documentJournalByteLength = 0;
|
|
10278
|
+
this.documentJournalRecordCount = 0;
|
|
9601
10279
|
this.documentSignerJournalInitialized = undefined;
|
|
10280
|
+
this.documentSignerJournalByteLength = 0;
|
|
10281
|
+
this.documentSignerJournalRecordCount = 0;
|
|
10282
|
+
this.checkpointHighwater = 0n;
|
|
10283
|
+
this.checkpointCompleted = undefined;
|
|
10284
|
+
this.checkpointPending = undefined;
|
|
9602
10285
|
this.lastFlushMs = Date.now();
|
|
9603
10286
|
}
|
|
9604
10287
|
|
|
10288
|
+
private async persistDropTombstone(
|
|
10289
|
+
files: readonly string[],
|
|
10290
|
+
preferAtomicReplace = false,
|
|
10291
|
+
): Promise<void> {
|
|
10292
|
+
const bytes = nativeBackboneCoordinateDropTombstoneBytes(files);
|
|
10293
|
+
if (preferAtomicReplace && this.store.atomicReplace) {
|
|
10294
|
+
await this.store.atomicReplace(
|
|
10295
|
+
nativeBackboneCoordinateDropTombstoneFile,
|
|
10296
|
+
bytes,
|
|
10297
|
+
);
|
|
10298
|
+
return;
|
|
10299
|
+
}
|
|
10300
|
+
await this.store.write(nativeBackboneCoordinateDropTombstoneFile, bytes);
|
|
10301
|
+
await this.barrierFile(nativeBackboneCoordinateDropTombstoneFile);
|
|
10302
|
+
}
|
|
10303
|
+
|
|
9605
10304
|
private async eraseDropFiles(files: readonly string[]): Promise<void> {
|
|
9606
10305
|
if (!this.supportsDrop || !this.store.remove) {
|
|
9607
10306
|
throw new Error(
|
|
@@ -9651,17 +10350,7 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9651
10350
|
this.dropInitiatedOnGeneration = true;
|
|
9652
10351
|
this.persistenceLifecycle = "dropping";
|
|
9653
10352
|
await this.enqueuePersistence(async () => {
|
|
9654
|
-
await this.
|
|
9655
|
-
nativeBackboneCoordinateDropTombstoneFile,
|
|
9656
|
-
nativeBackboneCoordinateDropTombstoneBytes(files),
|
|
9657
|
-
);
|
|
9658
|
-
if (this.store.durableBarrier) {
|
|
9659
|
-
await this.store.durableBarrier(
|
|
9660
|
-
nativeBackboneCoordinateDropTombstoneFile,
|
|
9661
|
-
);
|
|
9662
|
-
} else {
|
|
9663
|
-
await this.store.flush?.(nativeBackboneCoordinateDropTombstoneFile);
|
|
9664
|
-
}
|
|
10353
|
+
await this.persistDropTombstone(files);
|
|
9665
10354
|
await this.eraseDropFiles(files);
|
|
9666
10355
|
this.resetJournalTracking();
|
|
9667
10356
|
this.persistenceLifecycle = "dropped";
|
|
@@ -9699,6 +10388,57 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9699
10388
|
});
|
|
9700
10389
|
}
|
|
9701
10390
|
|
|
10391
|
+
private async barrierFile(name: string): Promise<void> {
|
|
10392
|
+
if (this.store.durableBarrier) {
|
|
10393
|
+
await this.store.durableBarrier(name);
|
|
10394
|
+
} else {
|
|
10395
|
+
await this.store.flush?.(name);
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
|
|
10399
|
+
private async hasDurableLegacyMigrationSentinel(): Promise<boolean> {
|
|
10400
|
+
const existing = await this.store.read(this.journalFile);
|
|
10401
|
+
if (
|
|
10402
|
+
existing &&
|
|
10403
|
+
existing.byteLength ===
|
|
10404
|
+
nativeBackboneCoordinateLegacyMigrationSentinel.byteLength &&
|
|
10405
|
+
nativeBackboneCoordinateLegacyMigrationSentinel.every(
|
|
10406
|
+
(byte, index) => existing[index] === byte,
|
|
10407
|
+
)
|
|
10408
|
+
) {
|
|
10409
|
+
// A previous process may have observed the bytes from page cache before
|
|
10410
|
+
// its durability barrier rejected. Cross a fresh barrier on every reopen
|
|
10411
|
+
// before any post-checkpoint WAL append can be admitted.
|
|
10412
|
+
await this.barrierFile(this.journalFile);
|
|
10413
|
+
return true;
|
|
10414
|
+
}
|
|
10415
|
+
return false;
|
|
10416
|
+
}
|
|
10417
|
+
|
|
10418
|
+
private async installLegacyMigrationSentinel(): Promise<void> {
|
|
10419
|
+
if (await this.hasDurableLegacyMigrationSentinel()) {
|
|
10420
|
+
return;
|
|
10421
|
+
}
|
|
10422
|
+
if (!this.store.atomicReplace) {
|
|
10423
|
+
throw new Error(
|
|
10424
|
+
"Native backbone completed checkpoint can not install its downgrade sentinel atomically",
|
|
10425
|
+
);
|
|
10426
|
+
}
|
|
10427
|
+
await this.store.atomicReplace(
|
|
10428
|
+
this.journalFile,
|
|
10429
|
+
nativeBackboneCoordinateLegacyMigrationSentinel,
|
|
10430
|
+
);
|
|
10431
|
+
}
|
|
10432
|
+
|
|
10433
|
+
private async requireLegacyMigrationSentinel(): Promise<void> {
|
|
10434
|
+
if (await this.hasDurableLegacyMigrationSentinel()) {
|
|
10435
|
+
return;
|
|
10436
|
+
}
|
|
10437
|
+
throw new Error(
|
|
10438
|
+
"Native backbone completed checkpoint downgrade sentinel is missing or has been replaced; refusing to overwrite possible legacy writes",
|
|
10439
|
+
);
|
|
10440
|
+
}
|
|
10441
|
+
|
|
9702
10442
|
async hydrate(backbone: NativePeerbitBackbone): Promise<number> {
|
|
9703
10443
|
this.assertActive("hydrate");
|
|
9704
10444
|
// Claim the lifecycle synchronously. close() queues after this complete
|
|
@@ -9709,33 +10449,175 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9709
10449
|
this.assertHydrating();
|
|
9710
10450
|
await this.resumeDropInternal("hydrating");
|
|
9711
10451
|
this.assertHydrating();
|
|
9712
|
-
const
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
documentSnapshot,
|
|
9716
|
-
documentJournal,
|
|
9717
|
-
documentSignerSnapshot,
|
|
9718
|
-
documentSignerJournal,
|
|
9719
|
-
] = await Promise.all([
|
|
9720
|
-
this.store.read(this.snapshotFile),
|
|
9721
|
-
this.store.read(this.journalFile),
|
|
9722
|
-
this.store.read(this.documentSnapshotFile),
|
|
9723
|
-
this.store.read(this.documentJournalFile),
|
|
9724
|
-
this.store.read(this.documentSignerSnapshotFile),
|
|
9725
|
-
this.store.read(this.documentSignerJournalFile),
|
|
9726
|
-
]);
|
|
10452
|
+
const checkpointStateBytes = await this.store.read(
|
|
10453
|
+
this.checkpointStateFile,
|
|
10454
|
+
);
|
|
9727
10455
|
this.assertHydrating();
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
10456
|
+
let checkpointState:
|
|
10457
|
+
| NativeBackboneCoordinateCheckpointState
|
|
10458
|
+
| undefined;
|
|
10459
|
+
if (checkpointStateBytes) {
|
|
10460
|
+
try {
|
|
10461
|
+
checkpointState = parseNativeBackboneCoordinateCheckpointState(
|
|
10462
|
+
checkpointStateBytes,
|
|
10463
|
+
this.checkpointStateFile,
|
|
10464
|
+
);
|
|
10465
|
+
if (
|
|
10466
|
+
checkpointState.configurationChecksum !==
|
|
10467
|
+
this.checkpointConfigurationChecksum
|
|
10468
|
+
) {
|
|
10469
|
+
throw new Error(
|
|
10470
|
+
"Native backbone checkpoint authority does not match the configured persistence files",
|
|
10471
|
+
);
|
|
10472
|
+
}
|
|
10473
|
+
} catch (error) {
|
|
10474
|
+
this.persistenceFailure ??= error;
|
|
10475
|
+
throw this.persistenceFailure;
|
|
10476
|
+
}
|
|
10477
|
+
}
|
|
10478
|
+
this.checkpointHighwater = checkpointState?.highwater ?? 0n;
|
|
10479
|
+
this.checkpointPending = checkpointState?.pending;
|
|
10480
|
+
this.checkpointCompleted = checkpointState?.completed;
|
|
10481
|
+
let checkpointAuthority = this.checkpointCompleted;
|
|
10482
|
+
let promotePendingCheckpoint = false;
|
|
10483
|
+
if (!checkpointAuthority && this.checkpointPending) {
|
|
10484
|
+
try {
|
|
10485
|
+
// On the first generation only, the sentinel is the durable stage
|
|
10486
|
+
// boundary: publication writes it after the pending checkpoint and its
|
|
10487
|
+
// WAL headers, but before completed authority. A pre-checkpoint reader
|
|
10488
|
+
// can no longer consume legacy state, so recovery may validate and
|
|
10489
|
+
// promote the staged generation. Once a completed generation exists the
|
|
10490
|
+
// sentinel predates later pending work and is not a promotion signal.
|
|
10491
|
+
if (await this.hasDurableLegacyMigrationSentinel()) {
|
|
10492
|
+
checkpointAuthority = this.checkpointPending;
|
|
10493
|
+
promotePendingCheckpoint = true;
|
|
10494
|
+
}
|
|
10495
|
+
} catch (error) {
|
|
10496
|
+
this.persistenceFailure ??= error;
|
|
10497
|
+
throw this.persistenceFailure;
|
|
10498
|
+
}
|
|
10499
|
+
}
|
|
10500
|
+
|
|
10501
|
+
let snapshot: Uint8Array | undefined;
|
|
10502
|
+
let journal: Uint8Array | undefined;
|
|
10503
|
+
let documentSnapshot: Uint8Array | undefined;
|
|
10504
|
+
let documentJournal: Uint8Array | undefined;
|
|
10505
|
+
let documentSignerSnapshot: Uint8Array | undefined;
|
|
10506
|
+
let documentSignerJournal: Uint8Array | undefined;
|
|
10507
|
+
let journalNames = {
|
|
10508
|
+
coordinate: this.journalFile,
|
|
10509
|
+
document: this.documentJournalFile,
|
|
10510
|
+
signer: this.documentSignerJournalFile,
|
|
10511
|
+
};
|
|
10512
|
+
if (checkpointAuthority) {
|
|
10513
|
+
const authority = checkpointAuthority;
|
|
10514
|
+
const authorityKind = promotePendingCheckpoint
|
|
10515
|
+
? "promotable pending"
|
|
10516
|
+
: "completed";
|
|
10517
|
+
const slot = this.checkpointSlot(authority.generation);
|
|
10518
|
+
const checkpointFile = this.checkpointFiles[slot];
|
|
10519
|
+
journalNames = this.checkpointJournalFiles[slot];
|
|
10520
|
+
const [
|
|
10521
|
+
checkpointBytes,
|
|
10522
|
+
coordinateJournal,
|
|
10523
|
+
valueJournal,
|
|
10524
|
+
signerJournal,
|
|
10525
|
+
] = await Promise.all([
|
|
10526
|
+
this.store.read(checkpointFile),
|
|
10527
|
+
this.store.read(journalNames.coordinate),
|
|
10528
|
+
this.store.read(journalNames.document),
|
|
10529
|
+
this.store.read(journalNames.signer),
|
|
10530
|
+
]);
|
|
10531
|
+
this.assertHydrating();
|
|
10532
|
+
try {
|
|
10533
|
+
if (
|
|
10534
|
+
!checkpointBytes ||
|
|
10535
|
+
checkpointBytes.byteLength !== authority.byteLength ||
|
|
10536
|
+
coordinateJournalChecksum(checkpointBytes) !== authority.checksum
|
|
10537
|
+
) {
|
|
10538
|
+
throw new Error(
|
|
10539
|
+
`Native backbone ${authorityKind} checkpoint authority is missing or corrupt`,
|
|
10540
|
+
);
|
|
10541
|
+
}
|
|
10542
|
+
if (!coordinateJournal || !valueJournal || !signerJournal) {
|
|
10543
|
+
throw new Error(
|
|
10544
|
+
`Native backbone ${authorityKind} checkpoint WAL generation is incomplete`,
|
|
10545
|
+
);
|
|
10546
|
+
}
|
|
10547
|
+
const checkpoint = parseNativeBackboneCoordinateCheckpoint(
|
|
10548
|
+
checkpointBytes,
|
|
10549
|
+
checkpointFile,
|
|
10550
|
+
);
|
|
10551
|
+
if (
|
|
10552
|
+
checkpoint.generation !== authority.generation ||
|
|
10553
|
+
checkpoint.configurationChecksum !==
|
|
10554
|
+
this.checkpointConfigurationChecksum
|
|
10555
|
+
) {
|
|
10556
|
+
throw new Error(
|
|
10557
|
+
`Native backbone checkpoint generation or configuration does not match its ${authorityKind} authority`,
|
|
10558
|
+
);
|
|
10559
|
+
}
|
|
10560
|
+
snapshot = checkpoint.coordinateSnapshot;
|
|
10561
|
+
documentSnapshot = checkpoint.documentSnapshot;
|
|
10562
|
+
documentSignerSnapshot = checkpoint.documentSignerSnapshot;
|
|
10563
|
+
journal = coordinateJournal;
|
|
10564
|
+
documentJournal = valueJournal;
|
|
10565
|
+
documentSignerJournal = signerJournal;
|
|
10566
|
+
} catch (error) {
|
|
10567
|
+
this.persistenceFailure ??= error;
|
|
10568
|
+
throw this.persistenceFailure;
|
|
10569
|
+
}
|
|
10570
|
+
} else {
|
|
10571
|
+
[
|
|
10572
|
+
snapshot,
|
|
10573
|
+
journal,
|
|
10574
|
+
documentSnapshot,
|
|
10575
|
+
documentJournal,
|
|
10576
|
+
documentSignerSnapshot,
|
|
9732
10577
|
documentSignerJournal,
|
|
9733
|
-
|
|
9734
|
-
|
|
10578
|
+
] = await Promise.all([
|
|
10579
|
+
this.store.read(this.snapshotFile),
|
|
10580
|
+
this.store.read(this.journalFile),
|
|
10581
|
+
this.store.read(this.documentSnapshotFile),
|
|
10582
|
+
this.store.read(this.documentJournalFile),
|
|
10583
|
+
this.store.read(this.documentSignerSnapshotFile),
|
|
10584
|
+
this.store.read(this.documentSignerJournalFile),
|
|
10585
|
+
]);
|
|
10586
|
+
this.assertHydrating();
|
|
10587
|
+
}
|
|
10588
|
+
try {
|
|
10589
|
+
if (
|
|
10590
|
+
checkpointAuthority &&
|
|
10591
|
+
(!journal ||
|
|
10592
|
+
!hasCoordinateJournalMagic(journal) ||
|
|
10593
|
+
!documentJournal ||
|
|
10594
|
+
!hasCoordinateJournalMagic(documentJournal) ||
|
|
10595
|
+
!documentSignerJournal ||
|
|
10596
|
+
!hasCoordinateJournalMagic(documentSignerJournal))
|
|
10597
|
+
) {
|
|
10598
|
+
throw new Error(
|
|
10599
|
+
`Native backbone ${promotePendingCheckpoint ? "promotable pending" : "completed"} checkpoint WAL generation has a missing or invalid header`,
|
|
10600
|
+
);
|
|
10601
|
+
}
|
|
10602
|
+
validateCoordinateJournal(journal, journalNames.coordinate);
|
|
10603
|
+
validateCoordinateJournal(documentJournal, journalNames.document);
|
|
10604
|
+
validateCoordinateJournal(documentSignerJournal, journalNames.signer);
|
|
9735
10605
|
} catch (error) {
|
|
9736
10606
|
this.persistenceFailure ??= error;
|
|
9737
10607
|
throw this.persistenceFailure;
|
|
9738
10608
|
}
|
|
10609
|
+
if (checkpointAuthority && !promotePendingCheckpoint) {
|
|
10610
|
+
try {
|
|
10611
|
+
// Completed authority is never permission to overwrite an ordinary
|
|
10612
|
+
// legacy WAL: it may contain writes made by a downgraded process in the
|
|
10613
|
+
// publication cut. Require the exact durable sentinel before loading.
|
|
10614
|
+
await this.requireLegacyMigrationSentinel();
|
|
10615
|
+
this.assertHydrating();
|
|
10616
|
+
} catch (error) {
|
|
10617
|
+
this.persistenceFailure ??= error;
|
|
10618
|
+
throw this.persistenceFailure;
|
|
10619
|
+
}
|
|
10620
|
+
}
|
|
9739
10621
|
let operations: number;
|
|
9740
10622
|
let documentOperations: number;
|
|
9741
10623
|
let documentSignerOperations: number;
|
|
@@ -9767,12 +10649,52 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9767
10649
|
}
|
|
9768
10650
|
throw error;
|
|
9769
10651
|
}
|
|
10652
|
+
if (promotePendingCheckpoint) {
|
|
10653
|
+
try {
|
|
10654
|
+
const authority = checkpointAuthority!;
|
|
10655
|
+
if (!this.store.atomicReplace) {
|
|
10656
|
+
throw new Error(
|
|
10657
|
+
"Native backbone pending checkpoint can not publish completed authority atomically",
|
|
10658
|
+
);
|
|
10659
|
+
}
|
|
10660
|
+
await this.store.atomicReplace(
|
|
10661
|
+
this.checkpointStateFile,
|
|
10662
|
+
encodeNativeBackboneCoordinateCheckpointState({
|
|
10663
|
+
configurationChecksum: this.checkpointConfigurationChecksum,
|
|
10664
|
+
highwater: authority.generation,
|
|
10665
|
+
completed: authority,
|
|
10666
|
+
}),
|
|
10667
|
+
);
|
|
10668
|
+
this.checkpointCompleted = authority;
|
|
10669
|
+
this.checkpointPending = undefined;
|
|
10670
|
+
this.assertHydrating();
|
|
10671
|
+
} catch (error) {
|
|
10672
|
+
this.persistenceFailure ??= error;
|
|
10673
|
+
throw this.persistenceFailure;
|
|
10674
|
+
}
|
|
10675
|
+
}
|
|
10676
|
+
if (this.checkpointCompleted || this.checkpointPending) {
|
|
10677
|
+
try {
|
|
10678
|
+
await this.cleanupRetiredCheckpointFiles();
|
|
10679
|
+
this.assertHydrating();
|
|
10680
|
+
} catch (error) {
|
|
10681
|
+
this.persistenceFailure ??= error;
|
|
10682
|
+
throw this.persistenceFailure;
|
|
10683
|
+
}
|
|
10684
|
+
}
|
|
9770
10685
|
this.assertHydrating();
|
|
9771
10686
|
this.journalInitialized = !!journal && journal.byteLength > 0;
|
|
10687
|
+
this.journalByteLength = journal?.byteLength ?? 0;
|
|
10688
|
+
this.journalRecordCount = operations;
|
|
9772
10689
|
this.documentJournalInitialized =
|
|
9773
10690
|
!!documentJournal && documentJournal.byteLength > 0;
|
|
10691
|
+
this.documentJournalByteLength = documentJournal?.byteLength ?? 0;
|
|
10692
|
+
this.documentJournalRecordCount = documentOperations;
|
|
9774
10693
|
this.documentSignerJournalInitialized =
|
|
9775
10694
|
!!documentSignerJournal && documentSignerJournal.byteLength > 0;
|
|
10695
|
+
this.documentSignerJournalByteLength =
|
|
10696
|
+
documentSignerJournal?.byteLength ?? 0;
|
|
10697
|
+
this.documentSignerJournalRecordCount = documentSignerOperations;
|
|
9776
10698
|
backbone.setCoordinateJournalEnabled(true);
|
|
9777
10699
|
backbone.setDocumentJournalEnabled(true);
|
|
9778
10700
|
backbone.setDocumentSignerJournalEnabled(true);
|
|
@@ -9815,15 +10737,19 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9815
10737
|
return false;
|
|
9816
10738
|
}
|
|
9817
10739
|
let tombstone: NativeBackboneCoordinateDropTombstoneBody;
|
|
10740
|
+
let expandedFiles: string[];
|
|
9818
10741
|
try {
|
|
9819
10742
|
tombstone = parseNativeBackboneCoordinateDropTombstone(bytes);
|
|
9820
|
-
for (const file of this.
|
|
10743
|
+
for (const file of this.legacyConfiguredFiles()) {
|
|
9821
10744
|
if (!tombstone.files.includes(file)) {
|
|
9822
10745
|
throw new Error(
|
|
9823
10746
|
"Native backbone drop tombstone does not cover the configured namespace",
|
|
9824
10747
|
);
|
|
9825
10748
|
}
|
|
9826
10749
|
}
|
|
10750
|
+
expandedFiles = [
|
|
10751
|
+
...new Set([...tombstone.files, ...this.configuredFiles()]),
|
|
10752
|
+
];
|
|
9827
10753
|
} catch (error) {
|
|
9828
10754
|
// Corruption must never hydrate stale files, but an explicit drop must
|
|
9829
10755
|
// remain available to overwrite the bad marker and erase the namespace.
|
|
@@ -9831,6 +10757,17 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9831
10757
|
this.persistenceLifecycle = this.closePromise ? "closing" : "active";
|
|
9832
10758
|
throw this.persistenceFailure;
|
|
9833
10759
|
}
|
|
10760
|
+
if (expandedFiles.length !== tombstone.files.length) {
|
|
10761
|
+
// Older valid markers only knew the six snapshot/WAL files. Widen the
|
|
10762
|
+
// destructive intent durably before erasing any checkpoint generation,
|
|
10763
|
+
// while preserving additional namespace files recorded by that version.
|
|
10764
|
+
await this.persistDropTombstone(expandedFiles, true);
|
|
10765
|
+
tombstone = { ...tombstone, files: expandedFiles };
|
|
10766
|
+
}
|
|
10767
|
+
// A prior replacement attempt may have made the expanded bytes visible before
|
|
10768
|
+
// its durability barrier rejected. Cross a fresh barrier on every recovery
|
|
10769
|
+
// before allowing any target removal.
|
|
10770
|
+
await this.barrierFile(nativeBackboneCoordinateDropTombstoneFile);
|
|
9834
10771
|
await this.eraseDropFiles(tombstone.files);
|
|
9835
10772
|
this.resetJournalTracking();
|
|
9836
10773
|
this.persistenceLifecycle =
|
|
@@ -9887,7 +10824,13 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9887
10824
|
this.assertActive("flush");
|
|
9888
10825
|
// Serialized with compact() so a flush never clears records appended to
|
|
9889
10826
|
// the wasm journal while a previous flush was awaiting its disk write.
|
|
9890
|
-
return this.enqueuePersistence(() =>
|
|
10827
|
+
return this.enqueuePersistence(() => {
|
|
10828
|
+
// A call can be admitted while an earlier queued write is still in flight.
|
|
10829
|
+
// Re-check the sticky failure at execution time so no suffix can ACK after
|
|
10830
|
+
// that earlier write or authority switch failed.
|
|
10831
|
+
this.assertPersistenceHealthy();
|
|
10832
|
+
return this.flushJournalInternal(backbone);
|
|
10833
|
+
});
|
|
9891
10834
|
}
|
|
9892
10835
|
|
|
9893
10836
|
private enqueuePersistence<T>(fn: () => Promise<T>): Promise<T> {
|
|
@@ -9908,9 +10851,185 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9908
10851
|
return next;
|
|
9909
10852
|
}
|
|
9910
10853
|
|
|
10854
|
+
private shouldCompactJournal(
|
|
10855
|
+
additionalBytes: number,
|
|
10856
|
+
additionalRecords: number,
|
|
10857
|
+
): boolean {
|
|
10858
|
+
return (
|
|
10859
|
+
(this.compactMaxJournalBytes != null &&
|
|
10860
|
+
this.journalByteLength +
|
|
10861
|
+
this.documentJournalByteLength +
|
|
10862
|
+
this.documentSignerJournalByteLength +
|
|
10863
|
+
additionalBytes >=
|
|
10864
|
+
this.compactMaxJournalBytes) ||
|
|
10865
|
+
(this.compactMaxJournalRecords != null &&
|
|
10866
|
+
this.journalRecordCount +
|
|
10867
|
+
this.documentJournalRecordCount +
|
|
10868
|
+
this.documentSignerJournalRecordCount +
|
|
10869
|
+
additionalRecords >=
|
|
10870
|
+
this.compactMaxJournalRecords)
|
|
10871
|
+
);
|
|
10872
|
+
}
|
|
10873
|
+
|
|
10874
|
+
private nextCheckpointGeneration(): bigint {
|
|
10875
|
+
let generation = this.checkpointHighwater + 1n;
|
|
10876
|
+
const active = this.checkpointCompleted;
|
|
10877
|
+
if (
|
|
10878
|
+
active &&
|
|
10879
|
+
this.checkpointSlot(generation) === this.checkpointSlot(active.generation)
|
|
10880
|
+
) {
|
|
10881
|
+
generation += 1n;
|
|
10882
|
+
}
|
|
10883
|
+
if (generation > nativeBackboneCoordinateMaxU64) {
|
|
10884
|
+
throw new RangeError(
|
|
10885
|
+
"Native backbone checkpoint generation highwater is exhausted",
|
|
10886
|
+
);
|
|
10887
|
+
}
|
|
10888
|
+
return generation;
|
|
10889
|
+
}
|
|
10890
|
+
|
|
10891
|
+
private async publishCheckpoint(checkpoint: {
|
|
10892
|
+
coordinateSnapshot: Uint8Array;
|
|
10893
|
+
documentSnapshot: Uint8Array;
|
|
10894
|
+
documentSignerSnapshot: Uint8Array;
|
|
10895
|
+
}): Promise<void> {
|
|
10896
|
+
if (
|
|
10897
|
+
!this.crashSafeCompaction ||
|
|
10898
|
+
!this.store.atomicReplace ||
|
|
10899
|
+
!this.store.remove
|
|
10900
|
+
) {
|
|
10901
|
+
throw new Error(nativeBackboneCoordinateCompactionDisabledMessage);
|
|
10902
|
+
}
|
|
10903
|
+
const generation = this.nextCheckpointGeneration();
|
|
10904
|
+
const slot = this.checkpointSlot(generation);
|
|
10905
|
+
const checkpointBytes = encodeNativeBackboneCoordinateCheckpoint({
|
|
10906
|
+
configurationChecksum: this.checkpointConfigurationChecksum,
|
|
10907
|
+
generation,
|
|
10908
|
+
...checkpoint,
|
|
10909
|
+
});
|
|
10910
|
+
const authority: NativeBackboneCoordinateCheckpointAuthority = {
|
|
10911
|
+
generation,
|
|
10912
|
+
byteLength: checkpointBytes.byteLength,
|
|
10913
|
+
checksum: coordinateJournalChecksum(checkpointBytes),
|
|
10914
|
+
};
|
|
10915
|
+
const pendingState: NativeBackboneCoordinateCheckpointState = {
|
|
10916
|
+
configurationChecksum: this.checkpointConfigurationChecksum,
|
|
10917
|
+
highwater: generation,
|
|
10918
|
+
pending: authority,
|
|
10919
|
+
completed: this.checkpointCompleted,
|
|
10920
|
+
};
|
|
10921
|
+
await this.store.atomicReplace(
|
|
10922
|
+
this.checkpointStateFile,
|
|
10923
|
+
encodeNativeBackboneCoordinateCheckpointState(pendingState),
|
|
10924
|
+
);
|
|
10925
|
+
this.checkpointHighwater = generation;
|
|
10926
|
+
this.checkpointPending = authority;
|
|
10927
|
+
|
|
10928
|
+
const targetJournals = this.checkpointJournalFiles[slot];
|
|
10929
|
+
for (const file of [
|
|
10930
|
+
targetJournals.coordinate,
|
|
10931
|
+
targetJournals.document,
|
|
10932
|
+
targetJournals.signer,
|
|
10933
|
+
]) {
|
|
10934
|
+
await this.store.remove(file);
|
|
10935
|
+
}
|
|
10936
|
+
const journalHeaders = [
|
|
10937
|
+
[targetJournals.coordinate, nativeBackboneCoordinateJournalMagic],
|
|
10938
|
+
[targetJournals.document, nativeBackboneCoordinateJournalMagic],
|
|
10939
|
+
[targetJournals.signer, nativeBackboneCoordinateJournalMagic],
|
|
10940
|
+
] as const;
|
|
10941
|
+
for (const [file, header] of journalHeaders) {
|
|
10942
|
+
await this.store.write(file, header);
|
|
10943
|
+
await this.barrierFile(file);
|
|
10944
|
+
}
|
|
10945
|
+
await this.store.atomicReplace(this.checkpointFiles[slot], checkpointBytes);
|
|
10946
|
+
|
|
10947
|
+
// A prior release does not understand checkpoint authority. The sentinel is
|
|
10948
|
+
// the first-generation publication boundary: install and durably revalidate
|
|
10949
|
+
// it before completed authority becomes visible. On later generations it
|
|
10950
|
+
// must already be exact; never overwrite possible writes from a downgrade.
|
|
10951
|
+
if (this.checkpointCompleted) {
|
|
10952
|
+
await this.requireLegacyMigrationSentinel();
|
|
10953
|
+
} else {
|
|
10954
|
+
await this.installLegacyMigrationSentinel();
|
|
10955
|
+
}
|
|
10956
|
+
|
|
10957
|
+
const completedState: NativeBackboneCoordinateCheckpointState = {
|
|
10958
|
+
configurationChecksum: this.checkpointConfigurationChecksum,
|
|
10959
|
+
highwater: generation,
|
|
10960
|
+
completed: authority,
|
|
10961
|
+
};
|
|
10962
|
+
await this.store.atomicReplace(
|
|
10963
|
+
this.checkpointStateFile,
|
|
10964
|
+
encodeNativeBackboneCoordinateCheckpointState(completedState),
|
|
10965
|
+
);
|
|
10966
|
+
|
|
10967
|
+
// The completed authority is now the only writable generation. Update all
|
|
10968
|
+
// in-memory routing synchronously before another queued flush can start.
|
|
10969
|
+
this.checkpointCompleted = authority;
|
|
10970
|
+
this.checkpointPending = undefined;
|
|
10971
|
+
this.journalInitialized = true;
|
|
10972
|
+
this.documentJournalInitialized = true;
|
|
10973
|
+
this.documentSignerJournalInitialized = true;
|
|
10974
|
+
this.journalByteLength = nativeBackboneCoordinateJournalMagic.byteLength;
|
|
10975
|
+
this.documentJournalByteLength =
|
|
10976
|
+
nativeBackboneCoordinateJournalMagic.byteLength;
|
|
10977
|
+
this.documentSignerJournalByteLength =
|
|
10978
|
+
nativeBackboneCoordinateJournalMagic.byteLength;
|
|
10979
|
+
this.journalRecordCount = 0;
|
|
10980
|
+
this.documentJournalRecordCount = 0;
|
|
10981
|
+
this.documentSignerJournalRecordCount = 0;
|
|
10982
|
+
}
|
|
10983
|
+
|
|
10984
|
+
private async cleanupRetiredCheckpointFiles(): Promise<void> {
|
|
10985
|
+
if (
|
|
10986
|
+
!this.store.remove ||
|
|
10987
|
+
(!this.checkpointCompleted && !this.checkpointPending)
|
|
10988
|
+
) {
|
|
10989
|
+
return;
|
|
10990
|
+
}
|
|
10991
|
+
const retiredSlot: NativeBackboneCoordinateCheckpointSlot = this
|
|
10992
|
+
.checkpointCompleted
|
|
10993
|
+
? this.checkpointSlot(this.checkpointCompleted.generation) === "a"
|
|
10994
|
+
? "b"
|
|
10995
|
+
: "a"
|
|
10996
|
+
: this.checkpointSlot(this.checkpointPending!.generation);
|
|
10997
|
+
const retiredJournals = this.checkpointJournalFiles[retiredSlot];
|
|
10998
|
+
const files = [
|
|
10999
|
+
...(this.checkpointCompleted
|
|
11000
|
+
? [
|
|
11001
|
+
this.snapshotFile,
|
|
11002
|
+
this.documentSnapshotFile,
|
|
11003
|
+
this.documentJournalFile,
|
|
11004
|
+
this.documentSignerSnapshotFile,
|
|
11005
|
+
this.documentSignerJournalFile,
|
|
11006
|
+
]
|
|
11007
|
+
: []),
|
|
11008
|
+
this.checkpointFiles[retiredSlot],
|
|
11009
|
+
`${this.checkpointFiles[retiredSlot]}.tmp`,
|
|
11010
|
+
retiredJournals.coordinate,
|
|
11011
|
+
retiredJournals.document,
|
|
11012
|
+
retiredJournals.signer,
|
|
11013
|
+
];
|
|
11014
|
+
const removals = await Promise.allSettled(
|
|
11015
|
+
files.map((file) => this.store.remove!(file)),
|
|
11016
|
+
);
|
|
11017
|
+
if (removals.every((result) => result.status === "fulfilled")) {
|
|
11018
|
+
try {
|
|
11019
|
+
await this.store.durableBarrier?.();
|
|
11020
|
+
} catch {
|
|
11021
|
+
// Cleanup is retryable debt. Publication and the caller's ACK already
|
|
11022
|
+
// belong to the new generation, so never turn this into an ambiguous
|
|
11023
|
+
// append failure after the authority switch.
|
|
11024
|
+
}
|
|
11025
|
+
}
|
|
11026
|
+
}
|
|
11027
|
+
|
|
9911
11028
|
private async flushJournalInternal(
|
|
9912
11029
|
backbone: NativePeerbitBackbone,
|
|
11030
|
+
forceCheckpoint = false,
|
|
9913
11031
|
): Promise<number> {
|
|
11032
|
+
this.assertPersistenceHealthy();
|
|
9914
11033
|
let written = 0;
|
|
9915
11034
|
let persistenceMutationStarted = false;
|
|
9916
11035
|
let coordinateBytes: Uint8Array | undefined;
|
|
@@ -9922,10 +11041,42 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9922
11041
|
const documentRecordCount = backbone.documentPendingJournalLength;
|
|
9923
11042
|
const signerRecords = backbone.documentSignerJournal();
|
|
9924
11043
|
const signerRecordCount = backbone.documentSignerPendingJournalLength;
|
|
11044
|
+
const additionalBytes =
|
|
11045
|
+
coordinateRecords.byteLength +
|
|
11046
|
+
documentRecords.byteLength +
|
|
11047
|
+
signerRecords.byteLength +
|
|
11048
|
+
(coordinateRecords.byteLength > 0 && this.journalInitialized !== true
|
|
11049
|
+
? backbone.coordinateJournalHeader().byteLength
|
|
11050
|
+
: 0) +
|
|
11051
|
+
(documentRecords.byteLength > 0 &&
|
|
11052
|
+
this.documentJournalInitialized !== true
|
|
11053
|
+
? backbone.documentJournalHeader().byteLength
|
|
11054
|
+
: 0) +
|
|
11055
|
+
(signerRecords.byteLength > 0 &&
|
|
11056
|
+
this.documentSignerJournalInitialized !== true
|
|
11057
|
+
? backbone.documentSignerJournalHeader().byteLength
|
|
11058
|
+
: 0);
|
|
11059
|
+
const additionalRecords =
|
|
11060
|
+
coordinateRecordCount + documentRecordCount + signerRecordCount;
|
|
11061
|
+
const checkpointRequested =
|
|
11062
|
+
forceCheckpoint ||
|
|
11063
|
+
(this.crashSafeCompaction &&
|
|
11064
|
+
this.shouldCompactJournal(additionalBytes, additionalRecords));
|
|
11065
|
+
// These three synchronous copies and the journal prefixes above are one
|
|
11066
|
+
// logical cut. No mutation can slip into the checkpoint without also being
|
|
11067
|
+
// represented by one of the captured prefixes.
|
|
11068
|
+
const checkpoint = checkpointRequested
|
|
11069
|
+
? {
|
|
11070
|
+
coordinateSnapshot: backbone.coordinateSnapshot(),
|
|
11071
|
+
documentSnapshot: backbone.documentSnapshot(),
|
|
11072
|
+
documentSignerSnapshot: backbone.documentSignerSnapshot(),
|
|
11073
|
+
}
|
|
11074
|
+
: undefined;
|
|
9925
11075
|
try {
|
|
11076
|
+
const activeJournals = this.activeJournalFiles();
|
|
9926
11077
|
if (coordinateRecords.byteLength > 0) {
|
|
9927
11078
|
if (this.journalInitialized === undefined) {
|
|
9928
|
-
const existing = await this.store.read(
|
|
11079
|
+
const existing = await this.store.read(activeJournals.coordinate);
|
|
9929
11080
|
this.journalInitialized = !!existing && existing.byteLength > 0;
|
|
9930
11081
|
}
|
|
9931
11082
|
coordinateBytes = this.journalInitialized
|
|
@@ -9934,28 +11085,26 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9934
11085
|
backbone.coordinateJournalHeader(),
|
|
9935
11086
|
coordinateRecords,
|
|
9936
11087
|
]);
|
|
9937
|
-
await this.store.append(
|
|
11088
|
+
await this.store.append(activeJournals.coordinate, coordinateBytes);
|
|
9938
11089
|
persistenceMutationStarted = true;
|
|
9939
11090
|
written += coordinateRecords.byteLength;
|
|
9940
11091
|
}
|
|
9941
11092
|
if (documentRecords.byteLength > 0) {
|
|
9942
11093
|
if (this.documentJournalInitialized === undefined) {
|
|
9943
|
-
const existing = await this.store.read(
|
|
11094
|
+
const existing = await this.store.read(activeJournals.document);
|
|
9944
11095
|
this.documentJournalInitialized =
|
|
9945
11096
|
!!existing && existing.byteLength > 0;
|
|
9946
11097
|
}
|
|
9947
11098
|
documentBytes = this.documentJournalInitialized
|
|
9948
11099
|
? documentRecords
|
|
9949
11100
|
: concatBytes([backbone.documentJournalHeader(), documentRecords]);
|
|
9950
|
-
await this.store.append(
|
|
11101
|
+
await this.store.append(activeJournals.document, documentBytes);
|
|
9951
11102
|
persistenceMutationStarted = true;
|
|
9952
11103
|
written += documentRecords.byteLength;
|
|
9953
11104
|
}
|
|
9954
11105
|
if (signerRecords.byteLength > 0) {
|
|
9955
11106
|
if (this.documentSignerJournalInitialized === undefined) {
|
|
9956
|
-
const existing = await this.store.read(
|
|
9957
|
-
this.documentSignerJournalFile,
|
|
9958
|
-
);
|
|
11107
|
+
const existing = await this.store.read(activeJournals.signer);
|
|
9959
11108
|
this.documentSignerJournalInitialized =
|
|
9960
11109
|
!!existing && existing.byteLength > 0;
|
|
9961
11110
|
}
|
|
@@ -9965,11 +11114,11 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9965
11114
|
backbone.documentSignerJournalHeader(),
|
|
9966
11115
|
signerRecords,
|
|
9967
11116
|
]);
|
|
9968
|
-
await this.store.append(
|
|
11117
|
+
await this.store.append(activeJournals.signer, signerBytes);
|
|
9969
11118
|
persistenceMutationStarted = true;
|
|
9970
11119
|
written += signerRecords.byteLength;
|
|
9971
11120
|
}
|
|
9972
|
-
if (written === 0) {
|
|
11121
|
+
if (written === 0 && !checkpoint) {
|
|
9973
11122
|
this.lastFlushMs = Date.now();
|
|
9974
11123
|
return 0;
|
|
9975
11124
|
}
|
|
@@ -9977,27 +11126,34 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
9977
11126
|
// `append` may only enqueue bytes in a buffered store. Drain and fsync
|
|
9978
11127
|
// every affected WAL before clearing its wasm prefix or returning an ACK.
|
|
9979
11128
|
for (const file of [
|
|
9980
|
-
coordinateBytes ?
|
|
9981
|
-
documentBytes ?
|
|
9982
|
-
signerBytes ?
|
|
11129
|
+
coordinateBytes ? activeJournals.coordinate : undefined,
|
|
11130
|
+
documentBytes ? activeJournals.document : undefined,
|
|
11131
|
+
signerBytes ? activeJournals.signer : undefined,
|
|
9983
11132
|
]) {
|
|
9984
11133
|
if (file) {
|
|
9985
|
-
|
|
9986
|
-
await this.store.durableBarrier(file);
|
|
9987
|
-
} else {
|
|
9988
|
-
await this.store.flush?.(file);
|
|
9989
|
-
}
|
|
11134
|
+
await this.barrierFile(file);
|
|
9990
11135
|
}
|
|
9991
11136
|
}
|
|
9992
11137
|
|
|
9993
11138
|
if (coordinateBytes) {
|
|
9994
11139
|
this.journalInitialized = true;
|
|
11140
|
+
this.journalByteLength += coordinateBytes.byteLength;
|
|
11141
|
+
this.journalRecordCount += coordinateRecordCount;
|
|
9995
11142
|
}
|
|
9996
11143
|
if (documentBytes) {
|
|
9997
11144
|
this.documentJournalInitialized = true;
|
|
11145
|
+
this.documentJournalByteLength += documentBytes.byteLength;
|
|
11146
|
+
this.documentJournalRecordCount += documentRecordCount;
|
|
9998
11147
|
}
|
|
9999
11148
|
if (signerBytes) {
|
|
10000
11149
|
this.documentSignerJournalInitialized = true;
|
|
11150
|
+
this.documentSignerJournalByteLength += signerBytes.byteLength;
|
|
11151
|
+
this.documentSignerJournalRecordCount += signerRecordCount;
|
|
11152
|
+
}
|
|
11153
|
+
|
|
11154
|
+
if (checkpoint) {
|
|
11155
|
+
persistenceMutationStarted = true;
|
|
11156
|
+
await this.publishCheckpoint(checkpoint);
|
|
10001
11157
|
}
|
|
10002
11158
|
|
|
10003
11159
|
backbone.clearCoordinateJournalPrefix(
|
|
@@ -10012,6 +11168,9 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
10012
11168
|
signerRecords.byteLength,
|
|
10013
11169
|
signerRecordCount,
|
|
10014
11170
|
);
|
|
11171
|
+
if (checkpoint) {
|
|
11172
|
+
await this.cleanupRetiredCheckpointFiles();
|
|
11173
|
+
}
|
|
10015
11174
|
this.lastFlushMs = Date.now();
|
|
10016
11175
|
return written;
|
|
10017
11176
|
} catch (error) {
|
|
@@ -10023,9 +11182,15 @@ export class NativeBackboneCoordinatePersistence {
|
|
|
10023
11182
|
}
|
|
10024
11183
|
}
|
|
10025
11184
|
|
|
10026
|
-
async compact(
|
|
11185
|
+
async compact(backbone: NativePeerbitBackbone): Promise<void> {
|
|
10027
11186
|
this.assertActive("compact");
|
|
10028
|
-
|
|
11187
|
+
if (!this.crashSafeCompaction) {
|
|
11188
|
+
throw new Error(nativeBackboneCoordinateCompactionDisabledMessage);
|
|
11189
|
+
}
|
|
11190
|
+
await this.enqueuePersistence(async () => {
|
|
11191
|
+
this.assertPersistenceHealthy();
|
|
11192
|
+
await this.flushJournalInternal(backbone, true);
|
|
11193
|
+
});
|
|
10029
11194
|
}
|
|
10030
11195
|
|
|
10031
11196
|
close(): Promise<void> {
|