@peerbit/shared-log 13.2.24 → 13.2.25

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.
Files changed (81) hide show
  1. package/dist/src/checked-prune.d.ts +12 -0
  2. package/dist/src/checked-prune.d.ts.map +1 -1
  3. package/dist/src/checked-prune.js +33 -0
  4. package/dist/src/checked-prune.js.map +1 -1
  5. package/dist/src/coordinate-persistence.d.ts +211 -0
  6. package/dist/src/coordinate-persistence.d.ts.map +1 -0
  7. package/dist/src/coordinate-persistence.js +1133 -0
  8. package/dist/src/coordinate-persistence.js.map +1 -0
  9. package/dist/src/errors.d.ts +1 -0
  10. package/dist/src/errors.d.ts.map +1 -1
  11. package/dist/src/errors.js +19 -0
  12. package/dist/src/errors.js.map +1 -1
  13. package/dist/src/index.d.ts +127 -133
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/index.js +1400 -4269
  16. package/dist/src/index.js.map +1 -1
  17. package/dist/src/instance-lifecycle.d.ts +117 -0
  18. package/dist/src/instance-lifecycle.d.ts.map +1 -0
  19. package/dist/src/instance-lifecycle.js +254 -0
  20. package/dist/src/instance-lifecycle.js.map +1 -0
  21. package/dist/src/join-warmup.d.ts +86 -0
  22. package/dist/src/join-warmup.d.ts.map +1 -0
  23. package/dist/src/join-warmup.js +351 -0
  24. package/dist/src/join-warmup.js.map +1 -0
  25. package/dist/src/native-write-through-block-store.d.ts +123 -0
  26. package/dist/src/native-write-through-block-store.d.ts.map +1 -0
  27. package/dist/src/native-write-through-block-store.js +989 -0
  28. package/dist/src/native-write-through-block-store.js.map +1 -0
  29. package/dist/src/peer-session.d.ts +117 -0
  30. package/dist/src/peer-session.d.ts.map +1 -0
  31. package/dist/src/peer-session.js +259 -0
  32. package/dist/src/peer-session.js.map +1 -0
  33. package/dist/src/replication-announcement.d.ts +116 -0
  34. package/dist/src/replication-announcement.d.ts.map +1 -0
  35. package/dist/src/replication-announcement.js +515 -0
  36. package/dist/src/replication-announcement.js.map +1 -0
  37. package/dist/src/replicator-liveness.d.ts +59 -0
  38. package/dist/src/replicator-liveness.d.ts.map +1 -0
  39. package/dist/src/replicator-liveness.js +304 -0
  40. package/dist/src/replicator-liveness.js.map +1 -0
  41. package/dist/src/sync/dispatch-lifecycle.d.ts +48 -0
  42. package/dist/src/sync/dispatch-lifecycle.d.ts.map +1 -0
  43. package/dist/src/sync/dispatch-lifecycle.js +141 -0
  44. package/dist/src/sync/dispatch-lifecycle.js.map +1 -0
  45. package/dist/src/sync/factory.d.ts +29 -0
  46. package/dist/src/sync/factory.d.ts.map +1 -0
  47. package/dist/src/sync/factory.js +87 -0
  48. package/dist/src/sync/factory.js.map +1 -0
  49. package/dist/src/sync/pending-sync-store.d.ts +105 -0
  50. package/dist/src/sync/pending-sync-store.d.ts.map +1 -0
  51. package/dist/src/sync/pending-sync-store.js +877 -0
  52. package/dist/src/sync/pending-sync-store.js.map +1 -0
  53. package/dist/src/sync/rateless-iblt.d.ts +4 -1
  54. package/dist/src/sync/rateless-iblt.d.ts.map +1 -1
  55. package/dist/src/sync/rateless-iblt.js +102 -106
  56. package/dist/src/sync/rateless-iblt.js.map +1 -1
  57. package/dist/src/sync/simple.d.ts +10 -41
  58. package/dist/src/sync/simple.d.ts.map +1 -1
  59. package/dist/src/sync/simple.js +259 -952
  60. package/dist/src/sync/simple.js.map +1 -1
  61. package/dist/src/sync/sync-peer-state.d.ts +16 -0
  62. package/dist/src/sync/sync-peer-state.d.ts.map +1 -0
  63. package/dist/src/sync/sync-peer-state.js +82 -0
  64. package/dist/src/sync/sync-peer-state.js.map +1 -0
  65. package/package.json +9 -9
  66. package/src/checked-prune.ts +35 -0
  67. package/src/coordinate-persistence.ts +1796 -0
  68. package/src/errors.ts +21 -0
  69. package/src/index.ts +4014 -7858
  70. package/src/instance-lifecycle.ts +351 -0
  71. package/src/join-warmup.ts +507 -0
  72. package/src/native-write-through-block-store.ts +1170 -0
  73. package/src/peer-session.ts +336 -0
  74. package/src/replication-announcement.ts +744 -0
  75. package/src/replicator-liveness.ts +439 -0
  76. package/src/sync/dispatch-lifecycle.ts +230 -0
  77. package/src/sync/factory.ts +154 -0
  78. package/src/sync/pending-sync-store.ts +1085 -0
  79. package/src/sync/rateless-iblt.ts +122 -132
  80. package/src/sync/simple.ts +334 -1144
  81. package/src/sync/sync-peer-state.ts +107 -0
@@ -0,0 +1,1796 @@
1
+ import type { RemoteBlocks } from "@peerbit/blocks";
2
+ import type { Cache } from "@peerbit/cache";
3
+ import {
4
+ type DeleteOptions,
5
+ type Index,
6
+ Or,
7
+ StringMatch,
8
+ toId,
9
+ } from "@peerbit/indexer-interface";
10
+ import {
11
+ Entry,
12
+ EntryType,
13
+ type Log,
14
+ type PreparedAppendJoinFacts,
15
+ type ShallowOrFullEntry,
16
+ } from "@peerbit/log";
17
+ import type {
18
+ NativeBackboneAppendProfile,
19
+ NativeBackboneCoordinateCommitColumns,
20
+ NativeBackboneLogCommitEntry,
21
+ NativePeerbitBackbone,
22
+ } from "@peerbit/native-backbone";
23
+ import type {
24
+ NativeAppendCoordinatePlan,
25
+ SharedLogNativeState,
26
+ SharedLogRangePlanner,
27
+ } from "@peerbit/shared-log-rust";
28
+ import { getPreparedRawExchangeTimestamp } from "./exchange-heads.js";
29
+ import type {
30
+ CoordinatePersistBatchItem,
31
+ DecodedReplicaCountMap,
32
+ EntryLeaderPlan,
33
+ EntryWithMetaBytes,
34
+ IndexableDomain,
35
+ NativeBackboneCoordinatePersistenceAdapter,
36
+ NativeBackboneCoordinateRollback,
37
+ NativeBackboneReceiveCoordinateBatch,
38
+ NativeBackboneReceiveCoordinateRow,
39
+ PreparedCoordinatePersistence,
40
+ PutAndDeleteIndex,
41
+ RepairDispatchEntry,
42
+ ResidentCoordinateEntry,
43
+ ReusableReceiveCoordinatePlan,
44
+ SharedLogCoordinateNativeFields,
45
+ } from "./index.js";
46
+ import type { NumberFromType } from "./integers.js";
47
+ import {
48
+ type EntryReplicated,
49
+ isEntryReplicated,
50
+ shouldAssigneToRangeBoundary as shouldAssignToRangeBoundary,
51
+ } from "./ranges.js";
52
+ import type { ReplicationDomain } from "./replication-domain.js";
53
+ import { decodeReplicas } from "./replication.js";
54
+ import {
55
+ type SyncProfileFn,
56
+ emitSyncProfileDuration,
57
+ emitSyncProfileEvent,
58
+ syncProfileStart,
59
+ } from "./sync/profile.js";
60
+
61
+ // Moved from src/index.ts with the stage-4.5 method move (byte-identical
62
+ // bodies): the maybe-promise idiom and the coordinate delete-hash helpers the
63
+ // moved methods depend on. index.ts re-imports the shared ones.
64
+ export type MaybePromise<T> = T | Promise<T>;
65
+
66
+ export const isPromiseLike = <T>(value: MaybePromise<T>): value is Promise<T> =>
67
+ !!value && typeof (value as Promise<T>).then === "function";
68
+
69
+ export const mapMaybePromise = <T, R>(
70
+ value: MaybePromise<T>,
71
+ fn: (value: T) => MaybePromise<R>,
72
+ ): MaybePromise<R> => (isPromiseLike(value) ? value.then(fn) : fn(value));
73
+
74
+ const EMPTY_HASHES: string[] = [];
75
+
76
+ export const normalizedHashValues = (hashes: Iterable<string>): string[] => {
77
+ if (Array.isArray(hashes)) {
78
+ if (hashes.length === 0) {
79
+ return EMPTY_HASHES;
80
+ }
81
+ if (hashes.length === 1) {
82
+ return hashes[0] ? hashes : EMPTY_HASHES;
83
+ }
84
+ }
85
+ const values: string[] = [];
86
+ const seen = new Set<string>();
87
+ for (const hash of hashes) {
88
+ if (!hash || seen.has(hash)) {
89
+ continue;
90
+ }
91
+ seen.add(hash);
92
+ values.push(hash);
93
+ }
94
+ return values;
95
+ };
96
+
97
+ export const combineCoordinateDeleteHashes = (
98
+ nextHashes: string[],
99
+ deleteHashes?: string[],
100
+ ): string[] => {
101
+ if (!deleteHashes || deleteHashes.length === 0) {
102
+ return nextHashes;
103
+ }
104
+ if (nextHashes.length === 0) {
105
+ return deleteHashes;
106
+ }
107
+ const combined: string[] = [];
108
+ const seen = new Set<string>();
109
+ for (const hash of nextHashes) {
110
+ if (!seen.has(hash)) {
111
+ seen.add(hash);
112
+ combined.push(hash);
113
+ }
114
+ }
115
+ for (const hash of deleteHashes) {
116
+ if (!seen.has(hash)) {
117
+ seen.add(hash);
118
+ combined.push(hash);
119
+ }
120
+ }
121
+ return combined;
122
+ };
123
+
124
+ /**
125
+ * Host dependencies of the moved methods, every one a LATE-BOUND closure into
126
+ * the live SharedLog instance: `_nativeBackbone`, `_nativeRangePlanner`,
127
+ * `_nativeSharedLogState`, the coordinate index, `remoteBlocks`,
128
+ * `coordinateToHash`, `timeUntilRoleMaturity`, and the durability/drop flags
129
+ * are all re-assigned across open/close/drop cycles, so nothing may be
130
+ * captured by value; the ownership-lifecycle helpers stay host methods so
131
+ * sinon spies and the fold onto InstanceLifecycle keep working. Prepared-join
132
+ * commit closures returned by createNativeBackbonePreparedJoinCommit execute
133
+ * later inside the ExchangeHeads branch; their lifecycle/poison checks route
134
+ * through these deps to HOST state, so the poison surface is unchanged.
135
+ */
136
+ export interface CoordinatePersistenceDeps<R extends "u32" | "u64"> {
137
+ /** Owner-routed so tests can stub the coordinator predicate while forcing
138
+ * the direct-fallback path. */
139
+ canUseNativeBackboneResidentCoordinateState: () => boolean;
140
+ /** The SharedLog instance itself — only for `decodeReplicas(x).getValue(host)`. */
141
+ host(): any;
142
+ nativeBackbone(): NativePeerbitBackbone | undefined;
143
+ nativeRangePlanner(): SharedLogRangePlanner | undefined;
144
+ nativeSharedLogState(): SharedLogNativeState | undefined;
145
+ /** The host getter (throws ClosedError when stopped, like every legacy read). */
146
+ entryCoordinatesIndex(): Index<EntryReplicated<R>>;
147
+ log(): Log<any>;
148
+ remoteBlocks(): RemoteBlocks | undefined;
149
+ domain(): ReplicationDomain<any, any, R>;
150
+ indexableDomain(): IndexableDomain<R>;
151
+ coordinateToHash(): Cache<string>;
152
+ timeUntilRoleMaturity(): number;
153
+ getEntryGid(entry: ShallowOrFullEntry<any> | EntryReplicated<R>): string;
154
+ getEntryNext(entry: ShallowOrFullEntry<any> | EntryReplicated<R>): string[];
155
+ getEntryHashNumber(
156
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>,
157
+ ): NumberFromType<R>;
158
+ canPlanNativeHashGid(
159
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>,
160
+ ): boolean;
161
+ hasCustomFindLeaders(): boolean;
162
+ captureReplicationOwnershipLifecycle(): AbortController;
163
+ throwIfReplicationOwnershipLifecycleInactive(
164
+ controller: AbortController,
165
+ ): void;
166
+ throwIfReplicationOwnershipPoisoned(): void;
167
+ isDropStarted(): boolean;
168
+ getDurableCommitFailure(): unknown;
169
+ isDurableRecoveryReadyForReopen(): boolean;
170
+ setDurableRecoveryReadyForReopen(value: boolean): void;
171
+ }
172
+
173
+ /**
174
+ * Stage 4.5 (PR-1): the coordinate-persistence module.
175
+ *
176
+ * The four coordinate state fields moved here in the state-ownership commit;
177
+ * this commit moves the 39 persistence methods (34 main-cluster + 5 early
178
+ * helpers) with byte-identical bodies — the only edits are the mechanical
179
+ * glue `this._coordinates.<field>` -> `this.<field>` for the owned state and
180
+ * `this.<hostDep>` -> `this.deps.<hostDep>()` for host state (plus the single
181
+ * `_nativeDurableRecoveryReadyForReopen = true` write, which becomes
182
+ * `deps.setDurableRecoveryReadyForReopen(true)`). SharedLog callers now use
183
+ * the coordinator directly; only the three compatibility state accessors
184
+ * remain on the host.
185
+ *
186
+ * Reset discipline is unchanged from the legacy host fields: each open/close
187
+ * site resets exactly the subset of fields it always reset (the resident
188
+ * mirror survives openNativeBackbone, the persistence adapter does not, and
189
+ * the mutation-generation ratchet deliberately survives everything but a
190
+ * fresh instance).
191
+ */
192
+ export class CoordinatePersistenceCoordinator<R extends "u32" | "u64"> {
193
+ /**
194
+ * Resident mirror of the coordinate rows (hash -> entry or native
195
+ * fields). Established by the native hydration paths; `undefined` means
196
+ * the resident fast path is unavailable.
197
+ */
198
+ _residentEntryCoordinatesByHash?: Map<string, ResidentCoordinateEntry<R>>;
199
+
200
+ /** The (optional) durable native-backbone coordinate journal adapter. */
201
+ _nativeBackboneCoordinatePersistence?: NativeBackboneCoordinatePersistenceAdapter;
202
+
203
+ // Moved from SharedLog src/index.ts (same name — the sanctioned
204
+ // file-to-file ratchet move; see scripts/ci/check-fence-ratchet.mjs
205
+ // TARGETS). Per-hash mutation-generation ratchet baseline: rollback
206
+ // snapshots capture the generation current at snapshot time and later
207
+ // roll back only while that generation is still current, so a rollback
208
+ // superseded by a newer mutation is a strict no-op. The map deliberately
209
+ // survives open/close cycles of the same instance.
210
+ _nativeCoordinateMutationGenerations?: Map<string, number>;
211
+
212
+ /**
213
+ * Wall-clock watermark of the last settled native coordinate journal
214
+ * flush; drives the `flushIntervalMs` on-append flush threshold.
215
+ */
216
+ _nativeBackboneCoordinateJournalLastFlushMs = 0;
217
+
218
+ constructor(private readonly deps: CoordinatePersistenceDeps<R>) {}
219
+
220
+ deleteCoordinatesForHashes(
221
+ hashes: Iterable<string>,
222
+ ownershipLifecycleController?: AbortController,
223
+ ): MaybePromise<void> {
224
+ if (ownershipLifecycleController) {
225
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
226
+ ownershipLifecycleController,
227
+ );
228
+ }
229
+ const values = normalizedHashValues(hashes);
230
+ if (values.length === 0) {
231
+ return;
232
+ }
233
+ this.forgetCoordinateStateForHashValues(values);
234
+ const coordinateIndex = this.deps.entryCoordinatesIndex() as PutAndDeleteIndex<
235
+ EntryReplicated<R>
236
+ >;
237
+ if (coordinateIndex.delIdsNoReturn) {
238
+ return mapMaybePromise(coordinateIndex.delIdsNoReturn(values), () => {
239
+ if (ownershipLifecycleController) {
240
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
241
+ ownershipLifecycleController,
242
+ );
243
+ }
244
+ });
245
+ }
246
+ if (coordinateIndex.delIds) {
247
+ return mapMaybePromise(coordinateIndex.delIds(values), () => {
248
+ if (ownershipLifecycleController) {
249
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
250
+ ownershipLifecycleController,
251
+ );
252
+ }
253
+ });
254
+ }
255
+ return mapMaybePromise(
256
+ this.deps.entryCoordinatesIndex().del({
257
+ query:
258
+ values.length === 1
259
+ ? { hash: values[0] }
260
+ : new Or(
261
+ values.map(
262
+ (hash) => new StringMatch({ key: "hash", value: hash }),
263
+ ),
264
+ ),
265
+ }),
266
+ () => {
267
+ if (ownershipLifecycleController) {
268
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
269
+ ownershipLifecycleController,
270
+ );
271
+ }
272
+ },
273
+ );
274
+ }
275
+
276
+ forgetCoordinateStateForHashes(hashes: Iterable<string>) {
277
+ const values = normalizedHashValues(hashes);
278
+ if (values.length === 0) {
279
+ return;
280
+ }
281
+ this.forgetCoordinateStateForHashValues(values);
282
+ }
283
+
284
+ forgetCoordinateStateForHashValues(values: string[]) {
285
+ this.deps.nativeSharedLogState()?.deleteEntryCoordinatesBatch(values);
286
+ this.deps.nativeBackbone()?.deleteEntryCoordinatesBatch(values);
287
+ this.forgetResidentCoordinateStateForHashValues(values);
288
+ }
289
+
290
+ forgetResidentCoordinateStateForHashes(hashes: Iterable<string>) {
291
+ const values = normalizedHashValues(hashes);
292
+ if (values.length === 0) {
293
+ return;
294
+ }
295
+ this.forgetResidentCoordinateStateForHashValues(values);
296
+ }
297
+
298
+ forgetResidentCoordinateStateForHashValues(values: string[]) {
299
+ if (this._residentEntryCoordinatesByHash) {
300
+ for (const hash of values) {
301
+ this._residentEntryCoordinatesByHash.delete(hash);
302
+ }
303
+ }
304
+ }
305
+ async createCoordinates(
306
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R> | NumberFromType<R>,
307
+ minReplicas: number,
308
+ ) {
309
+ if (
310
+ typeof entry !== "number" &&
311
+ typeof entry !== "bigint" &&
312
+ this.deps.canPlanNativeHashGid(entry)
313
+ ) {
314
+ const nativeCoordinates = (
315
+ this.deps.nativeBackbone() ?? this.deps.nativeRangePlanner()
316
+ )?.getGidCoordinates(entry.meta.gid, minReplicas) as
317
+ | NumberFromType<R>[]
318
+ | undefined;
319
+ if (nativeCoordinates) {
320
+ return nativeCoordinates;
321
+ }
322
+ }
323
+
324
+ const cursor =
325
+ typeof entry === "number" || typeof entry === "bigint"
326
+ ? entry
327
+ : await this.deps.domain().fromEntry(entry);
328
+ const nativeGrid = (
329
+ this.deps.nativeBackbone() ?? this.deps.nativeRangePlanner()
330
+ )?.getGrid(cursor, minReplicas) as NumberFromType<R>[] | undefined;
331
+ return (
332
+ nativeGrid ?? this.deps.indexableDomain().numbers.getGrid(cursor, minReplicas)
333
+ );
334
+ }
335
+
336
+ async getCoordinates(entry: { hash: string }) {
337
+ const nativeCoordinates = (
338
+ this.deps.nativeBackbone() ?? this.deps.nativeSharedLogState()
339
+ )?.getEntryCoordinates(entry.hash);
340
+ if (nativeCoordinates) {
341
+ return nativeCoordinates as NumberFromType<R>[];
342
+ }
343
+ const result = await this.deps.entryCoordinatesIndex()
344
+ .iterate({ query: { hash: entry.hash } })
345
+ .all();
346
+ return result[0].value.coordinates;
347
+ }
348
+
349
+ getNativeLogEntryMetadataBatch(hashes: Iterable<string>) {
350
+ const normalized = [...hashes];
351
+ if (normalized.length === 0) {
352
+ return [];
353
+ }
354
+ const backboneMetadata =
355
+ this.deps.nativeBackbone()?.graph.entryMetadataHintsBatch(normalized) ??
356
+ this.deps.nativeBackbone()?.graph.entryMetadataBatch(normalized);
357
+ if (backboneMetadata?.every((entry) => entry != null)) {
358
+ return backboneMetadata;
359
+ }
360
+ const indexMetadata =
361
+ this.deps.log().entryIndex.getNativeEntryMetadataHintsBatch(normalized) ??
362
+ this.deps.log().entryIndex.getNativeEntryMetadataBatch(normalized);
363
+ if (!backboneMetadata) {
364
+ return indexMetadata;
365
+ }
366
+ if (!indexMetadata) {
367
+ return backboneMetadata;
368
+ }
369
+ return backboneMetadata.map(
370
+ (entry, index) => entry ?? indexMetadata[index],
371
+ );
372
+ }
373
+
374
+ createReusableReceiveCoordinatePlans(
375
+ receiveGroups: Array<{
376
+ latestEntry: ShallowOrFullEntry<any>;
377
+ maxMaxReplicas: number;
378
+ leaderPlan?: EntryLeaderPlan<R>;
379
+ }>,
380
+ options?: {
381
+ decodedReplicaCounts?: DecodedReplicaCountMap;
382
+ allowRoleAgeZeroPlans?: boolean;
383
+ },
384
+ ): Map<string, ReusableReceiveCoordinatePlan<R>> {
385
+ const reusablePlans = new Map<string, ReusableReceiveCoordinatePlan<R>>();
386
+ if (this.deps.timeUntilRoleMaturity() > 0 && !options?.allowRoleAgeZeroPlans) {
387
+ return reusablePlans;
388
+ }
389
+
390
+ for (const group of receiveGroups) {
391
+ const plan = group.leaderPlan;
392
+ if (!plan) {
393
+ continue;
394
+ }
395
+ const replicas =
396
+ options?.decodedReplicaCounts?.get(group.latestEntry.hash) ??
397
+ decodeReplicas(group.latestEntry).getValue(this.deps.host());
398
+ if (replicas !== group.maxMaxReplicas) {
399
+ continue;
400
+ }
401
+ const prepared = this.createCoordinatePersistenceEntryFromLeaderPlan({
402
+ entry: group.latestEntry,
403
+ plan,
404
+ replicas,
405
+ });
406
+ if (!prepared) {
407
+ continue;
408
+ }
409
+ reusablePlans.set(group.latestEntry.hash, {
410
+ plan,
411
+ replicas,
412
+ prepared,
413
+ });
414
+ }
415
+ return reusablePlans;
416
+ }
417
+
418
+ createBackboneOnlyReceiveCoordinateBatch(
419
+ items: CoordinatePersistBatchItem<R>[],
420
+ ): NativeBackboneReceiveCoordinateBatch<R> | undefined {
421
+ if (
422
+ !this.deps.nativeBackbone() ||
423
+ items.length === 0 ||
424
+ !this.canUseBackboneOnlyCoordinatePersistence()
425
+ ) {
426
+ return undefined;
427
+ }
428
+
429
+ const rows = items
430
+ .filter((item) => item.prepared)
431
+ .map((item) => {
432
+ const prepared = item.prepared!;
433
+ const deleteHashes = this.deps.getEntryNext(item.entry);
434
+ return {
435
+ item,
436
+ prepared,
437
+ fields: prepared.fields,
438
+ deleteHashes,
439
+ };
440
+ });
441
+ if (rows.length === 0) {
442
+ return undefined;
443
+ }
444
+
445
+ return {
446
+ rows,
447
+ rollbackCoordinateEntries: this.snapshotResidentCoordinateEntries(
448
+ rows.flatMap((row) => [row.item.entry.hash, ...row.deleteHashes]),
449
+ ),
450
+ };
451
+ }
452
+
453
+ nativeBackboneReceiveCoordinateRowsToColumns(
454
+ rows: NativeBackboneReceiveCoordinateRow<R>[],
455
+ ): NativeBackboneCoordinateCommitColumns {
456
+ const hashes = new Array<string>(rows.length);
457
+ const gids = new Array<string>(rows.length);
458
+ const hashNumberValues = new BigUint64Array(rows.length);
459
+ const coordinateCounts = new Uint32Array(rows.length);
460
+ const coordinateValues = new BigUint64Array(
461
+ rows.reduce((sum, row) => sum + row.fields.coordinates.length, 0),
462
+ );
463
+ const nextHashBatches = new Array<string[]>(rows.length);
464
+ const assignedToRangeBoundaries = new Uint8Array(rows.length);
465
+ const requestedReplicaValues = new Uint32Array(rows.length);
466
+ let coordinateOffset = 0;
467
+ for (let i = 0; i < rows.length; i++) {
468
+ const { item, prepared, fields, deleteHashes } = rows[i]!;
469
+ hashes[i] = item.entry.hash;
470
+ gids[i] = fields.gid;
471
+ hashNumberValues[i] =
472
+ typeof fields.hashNumber === "bigint"
473
+ ? fields.hashNumber
474
+ : BigInt(fields.hashNumberString ?? fields.hashNumber);
475
+ coordinateCounts[i] = fields.coordinates.length;
476
+ for (const coordinate of fields.coordinates) {
477
+ coordinateValues[coordinateOffset++] =
478
+ typeof coordinate === "bigint" ? coordinate : BigInt(coordinate);
479
+ }
480
+ nextHashBatches[i] = deleteHashes;
481
+ assignedToRangeBoundaries[i] =
482
+ prepared.assignedToRangeBoundary === true ? 1 : 0;
483
+ requestedReplicaValues[i] = item.replicas;
484
+ }
485
+ return {
486
+ hashes,
487
+ gids,
488
+ hashNumberValues,
489
+ coordinateCounts,
490
+ coordinateValues,
491
+ nextHashBatches,
492
+ assignedToRangeBoundaries,
493
+ requestedReplicaValues,
494
+ };
495
+ }
496
+
497
+ async finishBackboneOnlyReceiveCoordinateBatch(
498
+ batch: NativeBackboneReceiveCoordinateBatch<R>,
499
+ profile?: SyncProfileFn,
500
+ ): Promise<Set<string>> {
501
+ const mirrorStartedAt = syncProfileStart(profile);
502
+ const persistedHashes = new Set<string>();
503
+ const coordinateToHashRows: [NumberFromType<R>, string][] = [];
504
+ let deleteCount = 0;
505
+ for (const { item, prepared, fields, deleteHashes } of batch.rows) {
506
+ persistedHashes.add(item.entry.hash);
507
+ this._residentEntryCoordinatesByHash?.set(
508
+ item.entry.hash,
509
+ prepared.coordinateEntry ?? fields,
510
+ );
511
+ for (const deletedHash of deleteHashes) {
512
+ this._residentEntryCoordinatesByHash?.delete(deletedHash);
513
+ deleteCount++;
514
+ }
515
+ for (const coordinate of item.coordinates) {
516
+ coordinateToHashRows.push([coordinate, item.entry.hash]);
517
+ }
518
+ }
519
+ this.deps.coordinateToHash().addMany(coordinateToHashRows);
520
+ emitSyncProfileDuration(profile, mirrorStartedAt, {
521
+ name: "sharedLog.receive.coordinateResidentMirror",
522
+ component: "shared-log",
523
+ entries: batch.rows.length,
524
+ count: coordinateToHashRows.length,
525
+ messages: 1,
526
+ details: { deletes: deleteCount },
527
+ });
528
+
529
+ const flushStartedAt = syncProfileStart(profile);
530
+ const flushed = this.flushNativeBackboneCoordinateJournalOnAppend();
531
+ if (isPromiseLike(flushed)) {
532
+ await flushed;
533
+ }
534
+ emitSyncProfileDuration(profile, flushStartedAt, {
535
+ name: "sharedLog.receive.coordinateJournalFlush",
536
+ component: "shared-log",
537
+ entries: batch.rows.length,
538
+ messages: 1,
539
+ });
540
+ return persistedHashes;
541
+ }
542
+
543
+ rollbackBackboneOnlyReceiveCoordinateBatch(
544
+ batch: NativeBackboneReceiveCoordinateBatch<R>,
545
+ ): void {
546
+ for (const { item } of batch.rows) {
547
+ this.rollbackNativeBackboneCoordinateAppend(
548
+ item.entry.hash,
549
+ batch.rollbackCoordinateEntries,
550
+ );
551
+ }
552
+ }
553
+
554
+ async persistBackboneOnlyReceiveCoordinateBatch(
555
+ items: CoordinatePersistBatchItem<R>[],
556
+ ): Promise<Set<string> | undefined> {
557
+ const backbone = this.deps.nativeBackbone();
558
+ const batch = this.createBackboneOnlyReceiveCoordinateBatch(items);
559
+ if (!backbone || !batch) {
560
+ return undefined;
561
+ }
562
+ try {
563
+ backbone.commitEntryCoordinatesColumnsBatch(
564
+ this.nativeBackboneReceiveCoordinateRowsToColumns(batch.rows),
565
+ );
566
+ return await this.finishBackboneOnlyReceiveCoordinateBatch(batch);
567
+ } catch (error) {
568
+ this.rollbackBackboneOnlyReceiveCoordinateBatch(batch);
569
+ throw error;
570
+ }
571
+ }
572
+
573
+ emitNativeBackboneRawCommitProfile(
574
+ profile: SyncProfileFn | undefined,
575
+ nativeProfile: NativeBackboneAppendProfile | undefined,
576
+ entries: number,
577
+ verifyCount: number,
578
+ ): void {
579
+ if (!profile || !nativeProfile) {
580
+ return;
581
+ }
582
+ const events: Array<[name: string, durationMs: number, count?: number]> = [
583
+ [
584
+ "sharedLog.receive.nativeRawCommit.pendingCheck",
585
+ nativeProfile.nativeBackboneRawReceivePendingCheckMs,
586
+ ],
587
+ [
588
+ "sharedLog.receive.nativeRawCommit.verify",
589
+ nativeProfile.nativeBackboneRawReceiveVerifyMs,
590
+ verifyCount,
591
+ ],
592
+ [
593
+ "sharedLog.receive.nativeRawCommit.verifyStatus",
594
+ nativeProfile.nativeBackboneRawReceiveVerifyStatusMs,
595
+ ],
596
+ [
597
+ "sharedLog.receive.nativeRawCommit.joinPlan",
598
+ nativeProfile.nativeBackboneRawReceiveJoinPlanMs,
599
+ ],
600
+ [
601
+ "sharedLog.receive.nativeRawCommit.removePending",
602
+ nativeProfile.nativeBackboneRawReceiveRemoveMs,
603
+ ],
604
+ [
605
+ "sharedLog.receive.nativeRawCommit.blockPut",
606
+ nativeProfile.nativeBackboneRawReceiveBlockPutMs,
607
+ ],
608
+ [
609
+ "sharedLog.receive.nativeRawCommit.graphPut",
610
+ nativeProfile.nativeBackboneRawReceiveGraphPutMs,
611
+ ],
612
+ [
613
+ "sharedLog.receive.nativeRawCommit.coordinateCommit",
614
+ nativeProfile.nativeBackboneRawReceiveCoordinateCommitMs,
615
+ ],
616
+ ];
617
+ for (const [name, durationMs, count] of events) {
618
+ if (durationMs > 0) {
619
+ emitSyncProfileEvent(profile, {
620
+ name,
621
+ component: "shared-log",
622
+ durationMs,
623
+ entries,
624
+ count,
625
+ messages: 1,
626
+ });
627
+ }
628
+ }
629
+ }
630
+
631
+ createNativeBackbonePreparedJoinCommit(
632
+ coordinateBatch?: NativeBackboneReceiveCoordinateBatch<R>,
633
+ onCoordinatesCommitted?: (
634
+ batch: NativeBackboneReceiveCoordinateBatch<R>,
635
+ ) => void,
636
+ verifyHashes?: string[],
637
+ verifyAllHashes = false,
638
+ profile?: SyncProfileFn,
639
+ onPreparedEntriesCommitted?: (hashes: string[]) => void,
640
+ ):
641
+ | ((input: {
642
+ entries: PreparedAppendJoinFacts[];
643
+ hashes: string[];
644
+ headFlags: boolean[];
645
+ headFlagsBytes: Uint8Array;
646
+ trustedMissing: boolean;
647
+ validatePlan?: boolean;
648
+ }) => boolean)
649
+ | undefined {
650
+ const backbone = this.deps.nativeBackbone();
651
+ if (
652
+ !backbone ||
653
+ this.deps.remoteBlocks()?.localStore !== backbone.blocks ||
654
+ (verifyHashes &&
655
+ verifyHashes.length > 0 &&
656
+ !backbone.graph.commitVerifiedPreparedRawReceiveJoinBatch)
657
+ ) {
658
+ return undefined;
659
+ }
660
+ return ({
661
+ entries,
662
+ hashes,
663
+ headFlags,
664
+ headFlagsBytes,
665
+ trustedMissing,
666
+ validatePlan,
667
+ }) => {
668
+ this.deps.throwIfReplicationOwnershipPoisoned();
669
+ if (!trustedMissing || entries.length === 0) {
670
+ return false;
671
+ }
672
+ const coordinateColumns =
673
+ coordinateBatch && coordinateBatch.rows.length > 0
674
+ ? this.nativeBackboneReceiveCoordinateRowsToColumns(
675
+ coordinateBatch.rows,
676
+ )
677
+ : undefined;
678
+ if (validatePlan) {
679
+ const verifiedCommitStartedAt = syncProfileStart(profile);
680
+ const profileNativeBackbone =
681
+ !!profile &&
682
+ !!backbone.resetAppendProfile &&
683
+ !!backbone.setAppendProfileEnabled &&
684
+ !!backbone.appendProfile;
685
+ if (profileNativeBackbone) {
686
+ backbone.resetAppendProfile();
687
+ backbone.setAppendProfileEnabled(true);
688
+ }
689
+ let committed: boolean | undefined;
690
+ try {
691
+ if (verifyHashes && verifyHashes.length > 0) {
692
+ if (verifyAllHashes) {
693
+ committed =
694
+ backbone.graph.commitVerifiedAllPreparedRawReceiveJoinBatch?.(
695
+ hashes,
696
+ headFlagsBytes,
697
+ coordinateColumns,
698
+ );
699
+ }
700
+ committed ??=
701
+ backbone.graph.commitVerifiedPreparedRawReceiveJoinBatch?.(
702
+ hashes,
703
+ headFlagsBytes,
704
+ verifyHashes,
705
+ coordinateColumns,
706
+ );
707
+ } else {
708
+ committed = backbone.graph.commitPreparedRawReceiveJoinBatch?.(
709
+ hashes,
710
+ headFlagsBytes,
711
+ coordinateColumns,
712
+ );
713
+ }
714
+ } finally {
715
+ if (profileNativeBackbone) {
716
+ backbone.setAppendProfileEnabled(false);
717
+ this.emitNativeBackboneRawCommitProfile(
718
+ profile,
719
+ backbone.appendProfile(),
720
+ entries.length,
721
+ verifyHashes?.length ?? 0,
722
+ );
723
+ }
724
+ }
725
+ if (verifyHashes && verifyHashes.length > 0 && profile) {
726
+ emitSyncProfileDuration(profile, verifiedCommitStartedAt, {
727
+ name: "sharedLog.receive.nativeVerifiedCommit",
728
+ component: "shared-log",
729
+ entries: entries.length,
730
+ count: verifyHashes.length,
731
+ messages: 1,
732
+ });
733
+ }
734
+ if (committed === true) {
735
+ onPreparedEntriesCommitted?.(hashes);
736
+ if (coordinateBatch) {
737
+ onCoordinatesCommitted?.(coordinateBatch);
738
+ }
739
+ return true;
740
+ }
741
+ if (committed === false) {
742
+ backbone.graph.clearPreparedRawReceiveEntries?.(hashes);
743
+ return false;
744
+ }
745
+ }
746
+ if (
747
+ backbone.graph.commitPreparedRawReceiveBatch(
748
+ hashes,
749
+ headFlagsBytes,
750
+ coordinateColumns,
751
+ )
752
+ ) {
753
+ onPreparedEntriesCommitted?.(hashes);
754
+ if (coordinateBatch) {
755
+ onCoordinatesCommitted?.(coordinateBatch);
756
+ }
757
+ return true;
758
+ }
759
+ const commitEntries = new Array<NativeBackboneLogCommitEntry>(
760
+ entries.length,
761
+ );
762
+ for (let i = 0; i < entries.length; i++) {
763
+ const entry = entries[i]!;
764
+ if (
765
+ !entry.bytes ||
766
+ !entry.nativeEntry ||
767
+ entry.meta.type !== EntryType.APPEND
768
+ ) {
769
+ return false;
770
+ }
771
+ commitEntries[i] = {
772
+ ...entry.nativeEntry,
773
+ head: headFlags[i] ?? true,
774
+ bytes: entry.bytes,
775
+ };
776
+ }
777
+ if (coordinateBatch && coordinateBatch.rows.length > 0) {
778
+ backbone.graph.commitBlocksGraphAndCoordinatesBatch(
779
+ commitEntries,
780
+ coordinateColumns!,
781
+ );
782
+ onCoordinatesCommitted?.(coordinateBatch);
783
+ } else {
784
+ backbone.graph.commitBlocksAndGraphBatch(commitEntries);
785
+ }
786
+ onPreparedEntriesCommitted?.(hashes);
787
+ return true;
788
+ };
789
+ }
790
+
791
+ createCoordinatePersistenceEntryFromLeaderPlan(properties: {
792
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
793
+ plan: EntryLeaderPlan<R>;
794
+ replicas: number;
795
+ }): PreparedCoordinatePersistence<R> | false {
796
+ const assignedToRangeBoundary =
797
+ properties.plan.assignedToRangeBoundary ??
798
+ shouldAssignToRangeBoundary(properties.plan.leaders, properties.replicas);
799
+ const hashNumber = this.deps.getEntryHashNumber(properties.entry);
800
+ const metaBytes = (properties.entry as EntryWithMetaBytes).getMetaBytes?.();
801
+ if (metaBytes) {
802
+ const rawTimestamp =
803
+ properties.entry instanceof Entry
804
+ ? getPreparedRawExchangeTimestamp(properties.entry)
805
+ : undefined;
806
+ const wallTime =
807
+ rawTimestamp?.wallTime ??
808
+ properties.entry.meta.clock.timestamp.wallTime;
809
+ return {
810
+ assignedToRangeBoundary,
811
+ fields: {
812
+ hash: properties.entry.hash,
813
+ hashNumber,
814
+ hashNumberString: hashNumber.toString(),
815
+ gid: this.deps.getEntryGid(properties.entry),
816
+ coordinates: properties.plan.coordinates,
817
+ coordinateStrings:
818
+ properties.plan.coordinateStrings ??
819
+ properties.plan.coordinates.map((coordinate) =>
820
+ coordinate.toString(),
821
+ ),
822
+ wallTime,
823
+ wallTimeString: wallTime.toString(),
824
+ assignedToRangeBoundary,
825
+ metaBytes,
826
+ },
827
+ };
828
+ }
829
+ return this.createCoordinatePersistenceEntry({
830
+ coordinates: properties.plan.coordinates,
831
+ entry: properties.entry,
832
+ leaders: properties.plan.leaders,
833
+ replicas: properties.replicas,
834
+ assignedToRangeBoundary,
835
+ hashNumber,
836
+ });
837
+ }
838
+
839
+ createCoordinatePersistenceEntry(properties: {
840
+ coordinates: NumberFromType<R>[];
841
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
842
+ leaders:
843
+ | Map<
844
+ string,
845
+ {
846
+ intersecting: boolean;
847
+ }
848
+ >
849
+ | false;
850
+ replicas: number;
851
+ prev?: EntryReplicated<R>;
852
+ assignedToRangeBoundary?: boolean;
853
+ hashNumber?: NumberFromType<R>;
854
+ }): PreparedCoordinatePersistence<R> | false {
855
+ const assignedToRangeBoundary =
856
+ properties.assignedToRangeBoundary ??
857
+ shouldAssignToRangeBoundary(properties.leaders, properties.replicas);
858
+
859
+ if (
860
+ properties.prev &&
861
+ properties.prev.assignedToRangeBoundary === assignedToRangeBoundary
862
+ ) {
863
+ return false;
864
+ }
865
+
866
+ const metaBytes = (properties.entry as EntryWithMetaBytes).getMetaBytes?.();
867
+ const coordinateEntry = new (this.deps.indexableDomain().constructorEntry)({
868
+ assignedToRangeBoundary,
869
+ coordinates: properties.coordinates,
870
+ meta: properties.entry.meta,
871
+ metaBytes,
872
+ hash: properties.entry.hash,
873
+ hashNumber:
874
+ properties.hashNumber ?? this.deps.getEntryHashNumber(properties.entry),
875
+ });
876
+ return {
877
+ coordinateEntry,
878
+ assignedToRangeBoundary,
879
+ fields: {
880
+ hash: coordinateEntry.hash,
881
+ hashNumber: coordinateEntry.hashNumber,
882
+ gid: coordinateEntry.gid,
883
+ coordinates: coordinateEntry.coordinates,
884
+ wallTime: coordinateEntry.wallTime,
885
+ assignedToRangeBoundary: coordinateEntry.assignedToRangeBoundary,
886
+ metaBytes: coordinateEntry.getMetaBytes(),
887
+ },
888
+ };
889
+ }
890
+
891
+ createCoordinatePersistenceEntryFromNativePlan(properties: {
892
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
893
+ plan: NativeAppendCoordinatePlan;
894
+ prev?: EntryReplicated<R>;
895
+ }): PreparedCoordinatePersistence<R> | false {
896
+ if (
897
+ properties.plan.hash !== properties.entry.hash ||
898
+ properties.plan.gid !== this.deps.getEntryGid(properties.entry)
899
+ ) {
900
+ return false;
901
+ }
902
+
903
+ const assignedToRangeBoundary = properties.plan.assignedToRangeBoundary;
904
+ if (
905
+ properties.prev &&
906
+ properties.prev.assignedToRangeBoundary === assignedToRangeBoundary
907
+ ) {
908
+ return false;
909
+ }
910
+
911
+ const coordinates = properties.plan.coordinates as NumberFromType<R>[];
912
+ const hashNumber = properties.plan.hashNumber as NumberFromType<R>;
913
+ const metaBytes = (properties.entry as EntryWithMetaBytes).getMetaBytes?.();
914
+ if (metaBytes) {
915
+ const rawTimestamp =
916
+ properties.entry instanceof Entry
917
+ ? getPreparedRawExchangeTimestamp(properties.entry)
918
+ : undefined;
919
+ const wallTime =
920
+ rawTimestamp?.wallTime ??
921
+ properties.entry.meta.clock.timestamp.wallTime;
922
+ return {
923
+ assignedToRangeBoundary,
924
+ fields: {
925
+ hash: properties.plan.hash,
926
+ hashNumber,
927
+ hashNumberString: properties.plan.hashNumberString,
928
+ gid: properties.plan.gid,
929
+ coordinates,
930
+ coordinateStrings: properties.plan.coordinateStrings,
931
+ wallTime,
932
+ wallTimeString: wallTime.toString(),
933
+ assignedToRangeBoundary,
934
+ metaBytes,
935
+ },
936
+ };
937
+ }
938
+ const entryMeta = properties.entry.meta;
939
+ const coordinateEntry = new (this.deps.indexableDomain().constructorEntry)({
940
+ assignedToRangeBoundary,
941
+ coordinates,
942
+ meta: entryMeta,
943
+ hash: properties.plan.hash,
944
+ hashNumber,
945
+ });
946
+ return {
947
+ coordinateEntry,
948
+ assignedToRangeBoundary,
949
+ fields: {
950
+ hash: properties.plan.hash,
951
+ hashNumber,
952
+ hashNumberString: properties.plan.hashNumberString,
953
+ gid: properties.plan.gid,
954
+ coordinates,
955
+ coordinateStrings: properties.plan.coordinateStrings,
956
+ wallTime: coordinateEntry.wallTime,
957
+ wallTimeString: coordinateEntry.wallTime.toString(),
958
+ assignedToRangeBoundary,
959
+ metaBytes: coordinateEntry.getMetaBytes(),
960
+ },
961
+ };
962
+ }
963
+
964
+ createCoordinateEntryFromNativeFields(
965
+ fields: SharedLogCoordinateNativeFields<R>,
966
+ ): EntryReplicated<R> {
967
+ return new (this.deps.indexableDomain().constructorEntry)({
968
+ assignedToRangeBoundary: fields.assignedToRangeBoundary,
969
+ coordinates: fields.coordinates,
970
+ metaBytes: fields.metaBytes,
971
+ gid: fields.gid,
972
+ wallTime: fields.wallTime,
973
+ hash: fields.hash,
974
+ hashNumber: fields.hashNumber,
975
+ });
976
+ }
977
+
978
+ materializePreparedCoordinateEntry(
979
+ prepared: PreparedCoordinatePersistence<R>,
980
+ ): EntryReplicated<R> {
981
+ return (prepared.coordinateEntry ??=
982
+ this.createCoordinateEntryFromNativeFields(prepared.fields));
983
+ }
984
+
985
+ materializeResidentCoordinateEntry(
986
+ entry: ResidentCoordinateEntry<R>,
987
+ ): EntryReplicated<R> {
988
+ return isEntryReplicated(entry)
989
+ ? entry
990
+ : this.createCoordinateEntryFromNativeFields(entry);
991
+ }
992
+
993
+ materializeRepairDispatchEntries(
994
+ entries: ReadonlyMap<string, RepairDispatchEntry<R>>,
995
+ ): Map<string, EntryReplicated<R>> {
996
+ const materialized = new Map<string, EntryReplicated<R>>();
997
+ for (const [hash, entry] of entries) {
998
+ materialized.set(hash, this.materializeResidentCoordinateEntry(entry));
999
+ }
1000
+ return materialized;
1001
+ }
1002
+
1003
+ snapshotResidentCoordinateEntries(
1004
+ hashes: Iterable<string>,
1005
+ ): NativeBackboneCoordinateRollback<R> | undefined {
1006
+ const uniqueHashes = new Set([...hashes].filter(Boolean));
1007
+ if (uniqueHashes.size === 0) {
1008
+ return undefined;
1009
+ }
1010
+ const entries = new Map<string, ResidentCoordinateEntry<R>>();
1011
+ const generations = new Map<string, number>();
1012
+ const mutationGenerations = (this._nativeCoordinateMutationGenerations ??=
1013
+ new Map());
1014
+ for (const hash of uniqueHashes) {
1015
+ const generation = (mutationGenerations.get(hash) ?? 0) + 1;
1016
+ mutationGenerations.set(hash, generation);
1017
+ generations.set(hash, generation);
1018
+ const entry = this._residentEntryCoordinatesByHash?.get(hash);
1019
+ if (entry) {
1020
+ entries.set(hash, entry);
1021
+ }
1022
+ }
1023
+ return { hashes: uniqueHashes, entries, generations };
1024
+ }
1025
+
1026
+ rollbackNativeBackboneCoordinateAppend(
1027
+ appendHash: string,
1028
+ rollback?: NativeBackboneCoordinateRollback<R>,
1029
+ ): void {
1030
+ const backbone = this.deps.nativeBackbone();
1031
+ if (!backbone) {
1032
+ return;
1033
+ }
1034
+ const hashes = rollback?.hashes ?? new Set([appendHash]);
1035
+ const mutationGenerations = (this._nativeCoordinateMutationGenerations ??=
1036
+ new Map());
1037
+ for (const hash of hashes) {
1038
+ const expectedGeneration = rollback?.generations.get(hash);
1039
+ if (
1040
+ expectedGeneration !== undefined &&
1041
+ mutationGenerations.get(hash) !== expectedGeneration
1042
+ ) {
1043
+ continue;
1044
+ }
1045
+ backbone.deleteEntryCoordinates(hash);
1046
+ this.deps.nativeSharedLogState()?.deleteEntryCoordinates(hash);
1047
+ this._residentEntryCoordinatesByHash?.delete(hash);
1048
+ const entry = rollback?.entries.get(hash);
1049
+ if (!entry) {
1050
+ continue;
1051
+ }
1052
+ const fields = isEntryReplicated(entry)
1053
+ ? {
1054
+ hash: entry.hash,
1055
+ gid: entry.gid,
1056
+ coordinates: entry.coordinates,
1057
+ assignedToRangeBoundary: entry.assignedToRangeBoundary,
1058
+ hashNumber: entry.hashNumber,
1059
+ }
1060
+ : entry;
1061
+ const requestedReplicas = isEntryReplicated(entry)
1062
+ ? decodeReplicas(entry).getValue(this.deps.host())
1063
+ : fields.coordinates.length;
1064
+ backbone.putEntryCoordinates(
1065
+ fields.hash,
1066
+ fields.gid,
1067
+ fields.coordinates,
1068
+ fields.assignedToRangeBoundary,
1069
+ requestedReplicas,
1070
+ fields.hashNumber,
1071
+ );
1072
+ this.deps.nativeSharedLogState()?.putEntryCoordinates(
1073
+ fields.hash,
1074
+ fields.gid,
1075
+ fields.coordinates,
1076
+ fields.assignedToRangeBoundary,
1077
+ requestedReplicas,
1078
+ fields.hashNumber,
1079
+ );
1080
+ this._residentEntryCoordinatesByHash?.set(hash, entry);
1081
+ }
1082
+ }
1083
+
1084
+ async rollbackNativeBackboneCoordinateAppendDurably(
1085
+ appendHash: string,
1086
+ rollback?: NativeBackboneCoordinateRollback<R>,
1087
+ ): Promise<void> {
1088
+ this.rollbackNativeBackboneCoordinateAppend(appendHash, rollback);
1089
+ const coordinateIndex = this.deps.entryCoordinatesIndex() as PutAndDeleteIndex<
1090
+ EntryReplicated<R>
1091
+ >;
1092
+ const hashes = rollback?.hashes ?? new Set([appendHash]);
1093
+ const mutationGenerations = (this._nativeCoordinateMutationGenerations ??=
1094
+ new Map());
1095
+ for (const hash of hashes) {
1096
+ const expectedGeneration = rollback?.generations.get(hash);
1097
+ if (
1098
+ expectedGeneration !== undefined &&
1099
+ mutationGenerations.get(hash) !== expectedGeneration
1100
+ ) {
1101
+ continue;
1102
+ }
1103
+ const previous = rollback?.entries.get(hash);
1104
+ if (previous) {
1105
+ await coordinateIndex.put(
1106
+ this.materializeResidentCoordinateEntry(previous),
1107
+ );
1108
+ } else if (coordinateIndex.delIds) {
1109
+ await coordinateIndex.delIds([hash]);
1110
+ } else if (coordinateIndex.delIdsNoReturn) {
1111
+ await coordinateIndex.delIdsNoReturn([hash]);
1112
+ } else {
1113
+ await coordinateIndex.del({ query: { hash } });
1114
+ }
1115
+ }
1116
+ const flushed = this.flushNativeBackboneCoordinateJournal();
1117
+ if (isPromiseLike(flushed)) {
1118
+ await flushed;
1119
+ }
1120
+ }
1121
+
1122
+ persistPreparedCoordinate(
1123
+ properties: {
1124
+ prepared: PreparedCoordinatePersistence<R>;
1125
+ hash: string;
1126
+ nextHashes: string[];
1127
+ coordinates: NumberFromType<R>[];
1128
+ replicas: number;
1129
+ commitNative?: boolean;
1130
+ commitNativeBackbone?: boolean;
1131
+ deleteHashes?: string[];
1132
+ },
1133
+ ownershipLifecycleController = this.deps.captureReplicationOwnershipLifecycle(),
1134
+ ): MaybePromise<boolean> {
1135
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1136
+ ownershipLifecycleController,
1137
+ );
1138
+ const { assignedToRangeBoundary, fields } = properties.prepared;
1139
+ const deleteHashes = combineCoordinateDeleteHashes(
1140
+ properties.nextHashes,
1141
+ properties.deleteHashes,
1142
+ );
1143
+ const coordinateIndex = this.deps.entryCoordinatesIndex() as PutAndDeleteIndex<
1144
+ EntryReplicated<R>
1145
+ >;
1146
+ let deleteNextOptions: DeleteOptions | undefined;
1147
+ let putResult: MaybePromise<unknown>;
1148
+ if (coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashesNoReturn) {
1149
+ putResult =
1150
+ coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashesNoReturn(
1151
+ fields,
1152
+ deleteHashes,
1153
+ );
1154
+ } else if (coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashes) {
1155
+ putResult = coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashes(
1156
+ fields,
1157
+ deleteHashes,
1158
+ );
1159
+ } else if (coordinateIndex.putSharedLogCoordinateFieldsAndDeleteIds) {
1160
+ putResult = coordinateIndex.putSharedLogCoordinateFieldsAndDeleteIds(
1161
+ fields,
1162
+ deleteHashes,
1163
+ toId(fields.hash),
1164
+ );
1165
+ } else if (coordinateIndex.putSharedLogCoordinateAndDeleteIds) {
1166
+ const coordinateEntry = this.materializePreparedCoordinateEntry(
1167
+ properties.prepared,
1168
+ );
1169
+ putResult = coordinateIndex.putSharedLogCoordinateAndDeleteIds(
1170
+ coordinateEntry,
1171
+ fields,
1172
+ deleteHashes,
1173
+ toId(fields.hash),
1174
+ );
1175
+ } else if (deleteHashes.length > 0 && coordinateIndex.putAndDeleteIds) {
1176
+ const coordinateEntry = this.materializePreparedCoordinateEntry(
1177
+ properties.prepared,
1178
+ );
1179
+ putResult = coordinateIndex.putAndDeleteIds(
1180
+ coordinateEntry,
1181
+ deleteHashes,
1182
+ );
1183
+ } else {
1184
+ const coordinateEntry = this.materializePreparedCoordinateEntry(
1185
+ properties.prepared,
1186
+ );
1187
+ deleteNextOptions =
1188
+ deleteHashes.length === 0
1189
+ ? undefined
1190
+ : deleteHashes.length === 1
1191
+ ? { query: { hash: deleteHashes[0] } }
1192
+ : {
1193
+ query: new Or(
1194
+ deleteHashes.map(
1195
+ (x) => new StringMatch({ key: "hash", value: x }),
1196
+ ),
1197
+ ),
1198
+ };
1199
+ if (deleteNextOptions && coordinateIndex.putAndDelete) {
1200
+ putResult = coordinateIndex.putAndDelete(
1201
+ coordinateEntry,
1202
+ deleteNextOptions,
1203
+ );
1204
+ } else {
1205
+ putResult = this.deps.entryCoordinatesIndex().put(coordinateEntry);
1206
+ }
1207
+ }
1208
+
1209
+ const finish = (): MaybePromise<boolean> => {
1210
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1211
+ ownershipLifecycleController,
1212
+ );
1213
+ const nativeDeleteHashes = combineCoordinateDeleteHashes(
1214
+ properties.nextHashes,
1215
+ properties.deleteHashes,
1216
+ );
1217
+ if (properties.commitNative !== false) {
1218
+ this.deps.nativeSharedLogState()?.commitEntryCoordinates(
1219
+ properties.hash,
1220
+ fields.gid,
1221
+ properties.coordinates,
1222
+ nativeDeleteHashes,
1223
+ assignedToRangeBoundary,
1224
+ properties.replicas,
1225
+ fields.hashNumber,
1226
+ );
1227
+ }
1228
+ if (properties.commitNativeBackbone !== false) {
1229
+ this.deps.nativeBackbone()?.commitEntryCoordinates(
1230
+ properties.hash,
1231
+ fields.gid,
1232
+ properties.coordinates,
1233
+ nativeDeleteHashes,
1234
+ assignedToRangeBoundary,
1235
+ properties.replicas,
1236
+ fields.hashNumber,
1237
+ );
1238
+ }
1239
+ if (this._residentEntryCoordinatesByHash) {
1240
+ this._residentEntryCoordinatesByHash.set(
1241
+ properties.hash,
1242
+ properties.prepared.coordinateEntry ?? fields,
1243
+ );
1244
+ for (const nextHash of nativeDeleteHashes) {
1245
+ this._residentEntryCoordinatesByHash.delete(nextHash);
1246
+ }
1247
+ }
1248
+
1249
+ for (const coordinate of properties.coordinates) {
1250
+ this.deps.coordinateToHash().add(coordinate, properties.hash);
1251
+ }
1252
+
1253
+ if (deleteNextOptions && !coordinateIndex.putAndDelete) {
1254
+ return mapMaybePromise(
1255
+ this.deps.entryCoordinatesIndex().del(deleteNextOptions),
1256
+ () => true,
1257
+ );
1258
+ }
1259
+ return true;
1260
+ };
1261
+ return mapMaybePromise(putResult, finish);
1262
+ }
1263
+
1264
+ persistPreparedCoordinateNativeTransaction(
1265
+ properties: {
1266
+ coordinateIndex: PutAndDeleteIndex<EntryReplicated<R>>;
1267
+ prepared: PreparedCoordinatePersistence<R>;
1268
+ hash: string;
1269
+ nextHashes: string[];
1270
+ coordinates: NumberFromType<R>[];
1271
+ deleteHashes?: string[];
1272
+ commitNative?: boolean;
1273
+ commitNativeBackbone?: boolean;
1274
+ },
1275
+ ownershipLifecycleController = this.deps.captureReplicationOwnershipLifecycle(),
1276
+ ): MaybePromise<boolean> {
1277
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1278
+ ownershipLifecycleController,
1279
+ );
1280
+ const { fields } = properties.prepared;
1281
+ const putNative =
1282
+ properties.coordinateIndex
1283
+ .putSharedLogCoordinateFieldsEncodedAndDeleteHashesNoReturn ??
1284
+ properties.coordinateIndex
1285
+ .putSharedLogCoordinateFieldsAndDeleteHashesNoReturn;
1286
+ if (!putNative) {
1287
+ return false;
1288
+ }
1289
+ const putResult = putNative.call(
1290
+ properties.coordinateIndex,
1291
+ fields,
1292
+ combineCoordinateDeleteHashes(
1293
+ properties.nextHashes,
1294
+ properties.deleteHashes,
1295
+ ),
1296
+ );
1297
+ const finish = () => {
1298
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1299
+ ownershipLifecycleController,
1300
+ );
1301
+ const nativeDeleteHashes = combineCoordinateDeleteHashes(
1302
+ properties.nextHashes,
1303
+ properties.deleteHashes,
1304
+ );
1305
+ if (properties.commitNative !== false) {
1306
+ this.deps.nativeSharedLogState()?.commitEntryCoordinates(
1307
+ properties.hash,
1308
+ fields.gid,
1309
+ properties.coordinates,
1310
+ nativeDeleteHashes,
1311
+ properties.prepared.assignedToRangeBoundary,
1312
+ properties.coordinates.length,
1313
+ fields.hashNumber,
1314
+ );
1315
+ }
1316
+ if (properties.commitNativeBackbone !== false) {
1317
+ this.deps.nativeBackbone()?.commitEntryCoordinates(
1318
+ properties.hash,
1319
+ fields.gid,
1320
+ properties.coordinates,
1321
+ nativeDeleteHashes,
1322
+ properties.prepared.assignedToRangeBoundary,
1323
+ properties.coordinates.length,
1324
+ fields.hashNumber,
1325
+ );
1326
+ }
1327
+ if (this._residentEntryCoordinatesByHash) {
1328
+ this._residentEntryCoordinatesByHash.set(
1329
+ properties.hash,
1330
+ properties.prepared.coordinateEntry ?? fields,
1331
+ );
1332
+ for (const nextHash of nativeDeleteHashes) {
1333
+ this._residentEntryCoordinatesByHash.delete(nextHash);
1334
+ }
1335
+ }
1336
+ for (const coordinate of properties.coordinates) {
1337
+ this.deps.coordinateToHash().add(coordinate, properties.hash);
1338
+ }
1339
+ return true;
1340
+ };
1341
+ return mapMaybePromise(putResult, finish);
1342
+ }
1343
+
1344
+ persistBackboneCoordinateFieldsNativeTransaction(
1345
+ properties: {
1346
+ coordinateIndex: PutAndDeleteIndex<EntryReplicated<R>>;
1347
+ fields: SharedLogCoordinateNativeFields<R>;
1348
+ hash: string;
1349
+ coordinates: NumberFromType<R>[];
1350
+ deleteHashes: string[];
1351
+ skipGenericTransientCoordinateIndex?: boolean;
1352
+ },
1353
+ ownershipLifecycleController = this.deps.captureReplicationOwnershipLifecycle(),
1354
+ ): MaybePromise<boolean> {
1355
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1356
+ ownershipLifecycleController,
1357
+ );
1358
+ const { fields } = properties;
1359
+ const useBackboneOnlyCoordinatePersistence =
1360
+ this.canUseBackboneOnlyCoordinatePersistence();
1361
+ const finish = (): MaybePromise<boolean> => {
1362
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1363
+ ownershipLifecycleController,
1364
+ );
1365
+ this.deps.nativeSharedLogState()?.commitEntryCoordinates(
1366
+ properties.hash,
1367
+ fields.gid,
1368
+ properties.coordinates,
1369
+ properties.deleteHashes,
1370
+ fields.assignedToRangeBoundary,
1371
+ properties.coordinates.length,
1372
+ fields.hashNumber,
1373
+ );
1374
+ if (this._residentEntryCoordinatesByHash) {
1375
+ this._residentEntryCoordinatesByHash.set(properties.hash, fields);
1376
+ for (const deletedHash of properties.deleteHashes) {
1377
+ this._residentEntryCoordinatesByHash.delete(deletedHash);
1378
+ }
1379
+ }
1380
+ for (const coordinate of properties.coordinates) {
1381
+ this.deps.coordinateToHash().add(coordinate, properties.hash);
1382
+ }
1383
+ if (this._nativeBackboneCoordinatePersistence) {
1384
+ const flushed = this.flushNativeBackboneCoordinateJournalOnAppend();
1385
+ if (isPromiseLike(flushed)) {
1386
+ return mapMaybePromise(flushed, () => true);
1387
+ }
1388
+ }
1389
+ return true;
1390
+ };
1391
+ if (
1392
+ (properties.skipGenericTransientCoordinateIndex &&
1393
+ this.canUseRuntimeOnlyNativeBackboneCoordinates(
1394
+ properties.coordinateIndex,
1395
+ )) ||
1396
+ useBackboneOnlyCoordinatePersistence
1397
+ ) {
1398
+ return finish();
1399
+ }
1400
+
1401
+ const putNative =
1402
+ properties.coordinateIndex
1403
+ .putSharedLogCoordinateFieldsEncodedAndDeleteHashesNoReturn ??
1404
+ properties.coordinateIndex
1405
+ .putSharedLogCoordinateFieldsAndDeleteHashesNoReturn;
1406
+ if (!putNative) {
1407
+ return false;
1408
+ }
1409
+ const putResult = putNative.call(
1410
+ properties.coordinateIndex,
1411
+ fields,
1412
+ properties.deleteHashes,
1413
+ );
1414
+ return mapMaybePromise(putResult, finish);
1415
+ }
1416
+
1417
+ flushNativeBackboneCoordinateJournal(): MaybePromise<void> {
1418
+ const backbone = this.deps.nativeBackbone();
1419
+ const persistence = this._nativeBackboneCoordinatePersistence;
1420
+ if (!backbone || !persistence || this.deps.isDropStarted()) {
1421
+ return undefined;
1422
+ }
1423
+ if (
1424
+ backbone.coordinatePendingJournalLength === 0 &&
1425
+ backbone.documentPendingJournalLength === 0 &&
1426
+ backbone.documentSignerPendingJournalLength === 0
1427
+ ) {
1428
+ return undefined;
1429
+ }
1430
+ return mapMaybePromise(persistence.flushJournal(backbone), () => {
1431
+ this._nativeBackboneCoordinateJournalLastFlushMs = Date.now();
1432
+ return undefined;
1433
+ });
1434
+ }
1435
+
1436
+ flushNativeBackboneCoordinateJournalOnAppend(): MaybePromise<void> {
1437
+ const backbone = this.deps.nativeBackbone();
1438
+ const persistence = this._nativeBackboneCoordinatePersistence;
1439
+ if (!backbone || !persistence || this.deps.isDropStarted()) {
1440
+ return undefined;
1441
+ }
1442
+ if (persistence.flushJournalOnAppend) {
1443
+ const flushed = persistence.flushJournalOnAppend(backbone);
1444
+ if (!isPromiseLike(flushed)) {
1445
+ return undefined;
1446
+ }
1447
+ return mapMaybePromise(flushed, () => {
1448
+ return undefined;
1449
+ });
1450
+ }
1451
+ if (!this.shouldFlushNativeBackboneCoordinateJournalOnAppend()) {
1452
+ return undefined;
1453
+ }
1454
+ return this.flushNativeBackboneCoordinateJournal();
1455
+ }
1456
+
1457
+ shouldFlushNativeBackboneCoordinateJournalOnAppend(): boolean {
1458
+ const persistence = this._nativeBackboneCoordinatePersistence;
1459
+ if (!persistence || persistence.flushOnAppend !== false) {
1460
+ return true;
1461
+ }
1462
+ const backbone = this.deps.nativeBackbone();
1463
+ if (!backbone || backbone.coordinatePendingJournalLength === 0) {
1464
+ return false;
1465
+ }
1466
+ if (
1467
+ persistence.flushMaxPendingBytes != null &&
1468
+ backbone.coordinatePendingJournalByteLength >=
1469
+ persistence.flushMaxPendingBytes
1470
+ ) {
1471
+ return true;
1472
+ }
1473
+ return (
1474
+ persistence.flushIntervalMs != null &&
1475
+ Date.now() - this._nativeBackboneCoordinateJournalLastFlushMs >=
1476
+ persistence.flushIntervalMs
1477
+ );
1478
+ }
1479
+
1480
+ async closeNativeBackboneCoordinatePersistence(): Promise<void> {
1481
+ const persistence = this._nativeBackboneCoordinatePersistence;
1482
+ if (!persistence) {
1483
+ return;
1484
+ }
1485
+ if (this.deps.isDropStarted()) {
1486
+ // `drop()` owns the durable namespace lifecycle. Never flush the live wasm
1487
+ // journals or invoke an ordinary custom close after its tombstone/erase has
1488
+ // started: a close implementation that rewrites cached state could resurrect
1489
+ // files after a successful terminal drop.
1490
+ return;
1491
+ }
1492
+ if (
1493
+ this.deps.getDurableCommitFailure() &&
1494
+ !this.deps.isDurableRecoveryReadyForReopen()
1495
+ ) {
1496
+ // The failed native transaction was never published by the lower log.
1497
+ // Its coordinate/document/signer records are still only in the wasm
1498
+ // pending journals. Closing without flushing discards that generation;
1499
+ // the next backbone hydrates the last acknowledged checkpoint.
1500
+ await persistence.close?.();
1501
+ this.deps.setDurableRecoveryReadyForReopen(true);
1502
+ return;
1503
+ }
1504
+ await this.flushNativeBackboneCoordinateJournal();
1505
+ await persistence.close?.();
1506
+ }
1507
+
1508
+ canUseBackboneOnlyCoordinatePersistence(): boolean {
1509
+ return (
1510
+ !!this._nativeBackboneCoordinatePersistence &&
1511
+ this.deps.canUseNativeBackboneResidentCoordinateState()
1512
+ );
1513
+ }
1514
+
1515
+ canUseNativeBackboneResidentCoordinateState(): boolean {
1516
+ return (
1517
+ !!this.deps.nativeBackbone() &&
1518
+ !!this._residentEntryCoordinatesByHash &&
1519
+ !this.deps.hasCustomFindLeaders()
1520
+ );
1521
+ }
1522
+
1523
+ canUseRuntimeOnlyNativeBackboneCoordinates(
1524
+ coordinateIndex: PutAndDeleteIndex<EntryReplicated<R>>,
1525
+ ): boolean {
1526
+ if (
1527
+ !this.deps.canUseNativeBackboneResidentCoordinateState() ||
1528
+ Object.prototype.hasOwnProperty.call(
1529
+ coordinateIndex,
1530
+ "putSharedLogCoordinateFieldsEncodedAndDeleteHashesNoReturn",
1531
+ ) ||
1532
+ Object.prototype.hasOwnProperty.call(
1533
+ coordinateIndex,
1534
+ "putSharedLogCoordinateFieldsAndDeleteHashesNoReturn",
1535
+ )
1536
+ ) {
1537
+ return false;
1538
+ }
1539
+ const persisted = (
1540
+ coordinateIndex as PutAndDeleteIndex<EntryReplicated<R>> & {
1541
+ persisted?: () => MaybePromise<boolean>;
1542
+ }
1543
+ ).persisted?.();
1544
+ return persisted === false;
1545
+ }
1546
+
1547
+ async persistCoordinate(
1548
+ properties: {
1549
+ coordinates: NumberFromType<R>[];
1550
+ entry: ShallowOrFullEntry<any> | EntryReplicated<R>;
1551
+ leaders:
1552
+ | Map<
1553
+ string,
1554
+ {
1555
+ intersecting: boolean;
1556
+ }
1557
+ >
1558
+ | false;
1559
+ replicas: number;
1560
+ prev?: EntryReplicated<R>;
1561
+ assignedToRangeBoundary?: boolean;
1562
+ commitNative?: boolean;
1563
+ commitNativeBackbone?: boolean;
1564
+ deleteHashes?: string[];
1565
+ hashNumber?: NumberFromType<R>;
1566
+ nextHashes?: string[];
1567
+ prepared?: PreparedCoordinatePersistence<R>;
1568
+ },
1569
+ ownershipLifecycleController = this.deps.captureReplicationOwnershipLifecycle(),
1570
+ ) {
1571
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1572
+ ownershipLifecycleController,
1573
+ );
1574
+ const prepared =
1575
+ properties.prepared ?? this.createCoordinatePersistenceEntry(properties);
1576
+ if (!prepared) {
1577
+ return false;
1578
+ }
1579
+ return this.persistPreparedCoordinate(
1580
+ {
1581
+ prepared,
1582
+ hash: properties.entry.hash,
1583
+ nextHashes: properties.nextHashes ?? properties.entry.meta.next,
1584
+ coordinates: properties.coordinates,
1585
+ replicas: properties.replicas,
1586
+ commitNative: properties.commitNative,
1587
+ commitNativeBackbone: properties.commitNativeBackbone,
1588
+ deleteHashes: properties.deleteHashes,
1589
+ },
1590
+ ownershipLifecycleController,
1591
+ );
1592
+ }
1593
+
1594
+ async persistCoordinatesBatch(
1595
+ items: CoordinatePersistBatchItem<R>[],
1596
+ ownershipLifecycleController = this.deps.captureReplicationOwnershipLifecycle(),
1597
+ ): Promise<boolean[]> {
1598
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1599
+ ownershipLifecycleController,
1600
+ );
1601
+ if (items.length === 0) {
1602
+ return [];
1603
+ }
1604
+
1605
+ const prepared = items.map((item) => ({
1606
+ item,
1607
+ prepared: item.prepared ?? this.createCoordinatePersistenceEntry(item),
1608
+ }));
1609
+ const changed = prepared.filter(
1610
+ (
1611
+ entry,
1612
+ ): entry is {
1613
+ item: (typeof items)[number];
1614
+ prepared: PreparedCoordinatePersistence<R>;
1615
+ } => entry.prepared !== false,
1616
+ );
1617
+ if (changed.length === 0) {
1618
+ return items.map(() => false);
1619
+ }
1620
+
1621
+ const coordinateIndex = this.deps.entryCoordinatesIndex() as PutAndDeleteIndex<
1622
+ EntryReplicated<R>
1623
+ >;
1624
+ const canUseGenericPutBatch =
1625
+ typeof coordinateIndex.putBatch === "function" &&
1626
+ changed.every(({ item }) => item.entry.meta.next.length === 0);
1627
+
1628
+ if (
1629
+ coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashesBatchNoReturn
1630
+ ) {
1631
+ await coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashesBatchNoReturn(
1632
+ changed.map(({ item, prepared }) => ({
1633
+ fields: prepared.fields,
1634
+ deleteHashes: item.entry.meta.next,
1635
+ })),
1636
+ );
1637
+ } else if (
1638
+ coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashesBatch
1639
+ ) {
1640
+ await coordinateIndex.putSharedLogCoordinateFieldsAndDeleteHashesBatch(
1641
+ changed.map(({ item, prepared }) => ({
1642
+ fields: prepared.fields,
1643
+ deleteHashes: item.entry.meta.next,
1644
+ })),
1645
+ );
1646
+ } else if (coordinateIndex.putSharedLogCoordinateFieldsAndDeleteIdsBatch) {
1647
+ await coordinateIndex.putSharedLogCoordinateFieldsAndDeleteIdsBatch(
1648
+ changed.map(({ item, prepared }) => ({
1649
+ fields: prepared.fields,
1650
+ deleteIds: item.entry.meta.next,
1651
+ id: toId(prepared.fields.hash),
1652
+ })),
1653
+ );
1654
+ } else if (coordinateIndex.putSharedLogCoordinatesAndDeleteIdsBatch) {
1655
+ await coordinateIndex.putSharedLogCoordinatesAndDeleteIdsBatch(
1656
+ changed.map(({ item, prepared }) => ({
1657
+ value: this.materializePreparedCoordinateEntry(prepared),
1658
+ fields: prepared.fields,
1659
+ deleteIds: item.entry.meta.next,
1660
+ id: toId(prepared.fields.hash),
1661
+ })),
1662
+ );
1663
+ } else if (canUseGenericPutBatch) {
1664
+ await coordinateIndex.putBatch!(
1665
+ changed.map(({ prepared }) =>
1666
+ this.materializePreparedCoordinateEntry(prepared),
1667
+ ),
1668
+ );
1669
+ } else {
1670
+ const results: boolean[] = [];
1671
+ for (const item of items) {
1672
+ results.push(
1673
+ await this.persistCoordinate(item, ownershipLifecycleController),
1674
+ );
1675
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1676
+ ownershipLifecycleController,
1677
+ );
1678
+ }
1679
+ return results;
1680
+ }
1681
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1682
+ ownershipLifecycleController,
1683
+ );
1684
+
1685
+ const nativeCoordinateCommits = changed.filter(
1686
+ ({ item }) => item.commitNative !== false,
1687
+ );
1688
+ const nativeSharedLogState = this.deps.nativeSharedLogState();
1689
+ if (nativeCoordinateCommits.length > 0 && nativeSharedLogState) {
1690
+ if (nativeSharedLogState.commitEntryCoordinatesBatch) {
1691
+ nativeSharedLogState.commitEntryCoordinatesBatch(
1692
+ nativeCoordinateCommits.map(({ item, prepared }) => ({
1693
+ hash: item.entry.hash,
1694
+ gid: prepared.fields.gid,
1695
+ coordinates: item.coordinates,
1696
+ nextHashes: item.entry.meta.next,
1697
+ assignedToRangeBoundary: prepared.assignedToRangeBoundary,
1698
+ requestedReplicas: item.replicas,
1699
+ hashNumber: prepared.fields.hashNumber,
1700
+ })),
1701
+ );
1702
+ } else {
1703
+ for (const { item, prepared } of nativeCoordinateCommits) {
1704
+ nativeSharedLogState.commitEntryCoordinates(
1705
+ item.entry.hash,
1706
+ prepared.fields.gid,
1707
+ item.coordinates,
1708
+ item.entry.meta.next,
1709
+ prepared.assignedToRangeBoundary,
1710
+ item.replicas,
1711
+ prepared.fields.hashNumber,
1712
+ );
1713
+ }
1714
+ }
1715
+ }
1716
+
1717
+ const nativeBackboneCoordinateCommits = changed.filter(
1718
+ ({ item }) => item.commitNativeBackbone !== false,
1719
+ );
1720
+ const nativeBackboneForBatch = this.deps.nativeBackbone();
1721
+ if (nativeBackboneCoordinateCommits.length > 0 && nativeBackboneForBatch) {
1722
+ if (nativeBackboneForBatch.commitEntryCoordinatesBatch) {
1723
+ nativeBackboneForBatch.commitEntryCoordinatesBatch(
1724
+ nativeBackboneCoordinateCommits.map(({ item, prepared }) => ({
1725
+ hash: item.entry.hash,
1726
+ gid: prepared.fields.gid,
1727
+ coordinates: item.coordinates,
1728
+ nextHashes: item.entry.meta.next,
1729
+ assignedToRangeBoundary: prepared.assignedToRangeBoundary,
1730
+ requestedReplicas: item.replicas,
1731
+ hashNumber: prepared.fields.hashNumber,
1732
+ })),
1733
+ );
1734
+ } else {
1735
+ for (const { item, prepared } of nativeBackboneCoordinateCommits) {
1736
+ nativeBackboneForBatch.commitEntryCoordinates(
1737
+ item.entry.hash,
1738
+ prepared.fields.gid,
1739
+ item.coordinates,
1740
+ item.entry.meta.next,
1741
+ prepared.assignedToRangeBoundary,
1742
+ item.replicas,
1743
+ prepared.fields.hashNumber,
1744
+ );
1745
+ }
1746
+ }
1747
+ }
1748
+
1749
+ for (const { item, prepared } of changed) {
1750
+ if (this._residentEntryCoordinatesByHash) {
1751
+ this._residentEntryCoordinatesByHash.set(
1752
+ item.entry.hash,
1753
+ prepared.coordinateEntry ?? prepared.fields,
1754
+ );
1755
+ for (const nextHash of item.entry.meta.next) {
1756
+ this._residentEntryCoordinatesByHash.delete(nextHash);
1757
+ }
1758
+ }
1759
+ for (const coordinate of item.coordinates) {
1760
+ this.deps.coordinateToHash().add(coordinate, item.entry.hash);
1761
+ }
1762
+ }
1763
+
1764
+ const changedHashes = new Set(
1765
+ changed.map(({ prepared }) => prepared.fields.hash),
1766
+ );
1767
+ return items.map((item) => changedHashes.has(item.entry.hash));
1768
+ }
1769
+
1770
+ async deleteCoordinates(
1771
+ properties: { hash: string },
1772
+ ownershipLifecycleController?: AbortController,
1773
+ ) {
1774
+ if (ownershipLifecycleController) {
1775
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1776
+ ownershipLifecycleController,
1777
+ );
1778
+ }
1779
+ this.deps.nativeSharedLogState()?.deleteEntryCoordinates(properties.hash);
1780
+ this.deps.nativeBackbone()?.deleteEntryCoordinates(properties.hash);
1781
+ this._residentEntryCoordinatesByHash?.delete(properties.hash);
1782
+ const coordinateIndex = this.deps.entryCoordinatesIndex() as PutAndDeleteIndex<
1783
+ EntryReplicated<R>
1784
+ >;
1785
+ if (coordinateIndex.delIds) {
1786
+ await coordinateIndex.delIds([properties.hash]);
1787
+ } else {
1788
+ await this.deps.entryCoordinatesIndex().del({ query: properties });
1789
+ }
1790
+ if (ownershipLifecycleController) {
1791
+ this.deps.throwIfReplicationOwnershipLifecycleInactive(
1792
+ ownershipLifecycleController,
1793
+ );
1794
+ }
1795
+ }
1796
+ }