@peerbit/log 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/benchmark/native-graph.js +447 -5
  2. package/dist/benchmark/native-graph.js.map +1 -1
  3. package/dist/src/clock.d.ts +1 -0
  4. package/dist/src/clock.d.ts.map +1 -1
  5. package/dist/src/clock.js +32 -0
  6. package/dist/src/clock.js.map +1 -1
  7. package/dist/src/entry-create.d.ts +1 -0
  8. package/dist/src/entry-create.d.ts.map +1 -1
  9. package/dist/src/entry-create.js.map +1 -1
  10. package/dist/src/entry-index.d.ts +288 -12
  11. package/dist/src/entry-index.d.ts.map +1 -1
  12. package/dist/src/entry-index.js +1478 -33
  13. package/dist/src/entry-index.js.map +1 -1
  14. package/dist/src/entry-v0.d.ts +184 -1
  15. package/dist/src/entry-v0.d.ts.map +1 -1
  16. package/dist/src/entry-v0.js +1021 -12
  17. package/dist/src/entry-v0.js.map +1 -1
  18. package/dist/src/entry.d.ts +73 -1
  19. package/dist/src/entry.d.ts.map +1 -1
  20. package/dist/src/entry.js +124 -1
  21. package/dist/src/entry.js.map +1 -1
  22. package/dist/src/index.d.ts +1 -1
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +1 -1
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/log.d.ts +62 -13
  27. package/dist/src/log.d.ts.map +1 -1
  28. package/dist/src/log.js +2282 -100
  29. package/dist/src/log.js.map +1 -1
  30. package/dist/src/runtime.d.ts +2 -0
  31. package/dist/src/runtime.d.ts.map +1 -0
  32. package/dist/src/runtime.js +10 -0
  33. package/dist/src/runtime.js.map +1 -0
  34. package/dist/src/trim.d.ts +14 -2
  35. package/dist/src/trim.d.ts.map +1 -1
  36. package/dist/src/trim.js +92 -4
  37. package/dist/src/trim.js.map +1 -1
  38. package/package.json +18 -15
  39. package/src/clock.ts +34 -0
  40. package/src/entry-create.ts +1 -0
  41. package/src/entry-index.ts +2232 -68
  42. package/src/entry-v0.ts +1488 -13
  43. package/src/entry.ts +235 -1
  44. package/src/index.ts +7 -1
  45. package/src/log.ts +3246 -206
  46. package/src/runtime.ts +16 -0
  47. package/src/trim.ts +133 -7
@@ -1,5 +1,5 @@
1
1
  import { deserialize, serialize } from "@dao-xyz/borsh";
2
- import { type Blocks } from "@peerbit/blocks-interface";
2
+ import { type Blocks, type GetOptions } from "@peerbit/blocks-interface";
3
3
  import { Cache } from "@peerbit/cache";
4
4
  import type { PublicSignKey } from "@peerbit/crypto";
5
5
  import {
@@ -17,10 +17,16 @@ import {
17
17
  toId,
18
18
  } from "@peerbit/indexer-interface";
19
19
  import { FOREGROUND_READ_MESSAGE_PRIORITY } from "@peerbit/stream-interface";
20
- import type { ShallowEntry } from "./entry-shallow.js";
20
+ import { LamportClock as Clock, Timestamp } from "./clock.js";
21
+ import { ShallowEntry, ShallowMeta } from "./entry-shallow.js";
21
22
  import { EntryType } from "./entry-type.js";
22
- import { Entry, type ShallowOrFullEntry } from "./entry.js";
23
- import type { SortFn } from "./log-sorting.js";
23
+ import {
24
+ Entry,
25
+ type PreparedEntryBlock,
26
+ type PreparedNativeLogEntry,
27
+ type ShallowOrFullEntry,
28
+ } from "./entry.js";
29
+ import type { SortFn, SortableEntry } from "./log-sorting.js";
24
30
  import { logger as baseLogger } from "./logger.js";
25
31
 
26
32
  const log = baseLogger.newScope("entry-index");
@@ -36,33 +42,360 @@ export type ResultsIterator<T> = {
36
42
  const ENTRY_CACHE_MAX_SIZE = 10; // TODO as param for log
37
43
  const DEFERRED_INDEX_FLUSH_IDLE_MS = 250;
38
44
  const NATIVE_GRAPH_REBUILD_BATCH_SIZE = 512;
45
+ const TIMESTAMP_WALL_TIME_KEY = ["meta", "clock", "timestamp", "wallTime"];
46
+ const TIMESTAMP_LOGICAL_KEY = ["meta", "clock", "timestamp", "logical"];
47
+
48
+ type EntryIndexProfileValue = string | number | boolean | undefined;
49
+ type EntryIndexProfileEvent = {
50
+ name: string;
51
+ component?: string;
52
+ durationMs?: number;
53
+ entries?: number;
54
+ messages?: number;
55
+ count?: number;
56
+ details?: Record<string, EntryIndexProfileValue>;
57
+ };
58
+ type EntryIndexProfileSink = (event: EntryIndexProfileEvent) => void;
59
+
60
+ const entryIndexProfileNow = () =>
61
+ globalThis.performance?.now?.() ?? Date.now();
62
+ const entryIndexProfileStart = (sink: EntryIndexProfileSink | undefined) =>
63
+ sink ? entryIndexProfileNow() : 0;
64
+ const emitEntryIndexProfileDuration = (
65
+ sink: EntryIndexProfileSink | undefined,
66
+ startedAt: number,
67
+ event: Omit<EntryIndexProfileEvent, "durationMs">,
68
+ ) => {
69
+ if (!sink) {
70
+ return;
71
+ }
72
+ sink({
73
+ ...event,
74
+ durationMs: entryIndexProfileNow() - startedAt,
75
+ });
76
+ };
77
+
78
+ type BlocksWithGetMany = Blocks & {
79
+ getMany?: (
80
+ cids: string[],
81
+ options?: GetOptions,
82
+ ) => Promise<Array<Uint8Array | undefined>> | Array<Uint8Array | undefined>;
83
+ };
84
+
85
+ type BlocksWithRmMany = Blocks & {
86
+ rmMany?: (cids: string[]) => Promise<number | void> | number | void;
87
+ };
88
+
89
+ type BlocksWithPutKnown = Blocks & {
90
+ putKnown?: (cid: string, bytes: Uint8Array) => Promise<string> | string;
91
+ };
92
+
93
+ const hasRmMany = (store: Blocks): store is BlocksWithRmMany =>
94
+ typeof (store as BlocksWithRmMany).rmMany === "function";
95
+
96
+ type IndexWithExactDelete = Index<any> & {
97
+ delIds: (
98
+ deleteIds: string[],
99
+ ) => Promise<ReturnType<typeof toId>[]> | ReturnType<typeof toId>[];
100
+ };
101
+
102
+ const hasExactDelete = (index: Index<any>): index is IndexWithExactDelete =>
103
+ typeof (index as IndexWithExactDelete).delIds === "function";
104
+
105
+ type IndexWithExactDeleteCount = Index<any> & {
106
+ delIdsCount: (deleteIds: string[]) => Promise<number> | number;
107
+ };
39
108
 
40
- type NativeLogEntry = {
109
+ const hasExactDeleteCount = (
110
+ index: Index<any>,
111
+ ): index is IndexWithExactDeleteCount =>
112
+ typeof (index as IndexWithExactDeleteCount).delIdsCount === "function";
113
+
114
+ const putPreparedEntryBlock = async (
115
+ store: Blocks,
116
+ prepared: PreparedEntryBlock,
117
+ ) => {
118
+ const storeWithKnown = store as BlocksWithPutKnown;
119
+ return typeof storeWithKnown.putKnown === "function"
120
+ ? await storeWithKnown.putKnown(prepared.cid, prepared.block.bytes)
121
+ : await store.put(prepared);
122
+ };
123
+
124
+ type NativeLogEntry = PreparedNativeLogEntry;
125
+
126
+ export type PreparedAppendIndexFacts = {
41
127
  hash: string;
128
+ meta: {
129
+ clock: Clock;
130
+ gid: string;
131
+ next: string[];
132
+ type: EntryType;
133
+ data?: Uint8Array;
134
+ };
135
+ size?: number;
136
+ shallowEntry?: ShallowEntry;
137
+ getShallowEntry?: (isHead?: boolean) => ShallowEntry;
138
+ nativeEntry?: NativeLogEntry;
139
+ };
140
+
141
+ type NativeJoinCutCheck = {
42
142
  gid: string;
143
+ wallTime: bigint | number | string;
144
+ logical?: number;
145
+ };
146
+
147
+ type NativeJoinPlanInput = {
148
+ hash: string;
43
149
  next: string[];
44
150
  type: number;
45
- head?: boolean;
46
- payloadSize?: number;
47
- clock: {
48
- timestamp: {
49
- wallTime: bigint | number | string;
50
- logical?: number;
51
- };
52
- };
151
+ cutCheck?: NativeJoinCutCheck;
152
+ };
153
+
154
+ type MaybePromise<T> = T | Promise<T>;
155
+ type PendingIndexWrite = ShallowEntry | (() => ShallowEntry);
156
+
157
+ const isPromiseLike = <T>(value: MaybePromise<T>): value is Promise<T> =>
158
+ !!value && typeof (value as { then?: unknown }).then === "function";
159
+
160
+ const mapMaybePromise = <T, R>(
161
+ value: MaybePromise<T>,
162
+ fn: (value: T) => MaybePromise<R>,
163
+ ): MaybePromise<R> => (isPromiseLike(value) ? value.then(fn) : fn(value));
164
+
165
+ const materializePreparedAppendShallowEntry = (
166
+ entry: PreparedAppendIndexFacts,
167
+ isHead: boolean,
168
+ ): ShallowEntry => {
169
+ const shallowEntry =
170
+ entry.shallowEntry ?? entry.getShallowEntry?.(isHead);
171
+ if (!shallowEntry) {
172
+ throw new Error("Missing prepared append shallow entry");
173
+ }
174
+ shallowEntry.head = isHead;
175
+ entry.shallowEntry = shallowEntry;
176
+ return shallowEntry;
53
177
  };
54
178
 
55
179
  export type NativeLogGraph = {
180
+ readonly length: number;
181
+ has: (hash: string) => boolean;
182
+ hasMany: (hashes: Iterable<string>) => Set<string>;
56
183
  put: (entry: NativeLogEntry) => void;
184
+ putBatch?: (entries: NativeLogEntry[]) => void;
185
+ putAppendChain?: (entries: NativeLogEntry[]) => void;
186
+ prepareEntryV0PlainChainAndPut?: (input: {
187
+ clockId: Uint8Array;
188
+ privateKey: Uint8Array;
189
+ publicKey: Uint8Array;
190
+ wallTimes: Array<bigint | number | string>;
191
+ logicals?: number[];
192
+ gid: string;
193
+ initialNext?: string[];
194
+ type?: number;
195
+ metaDatas?: Array<Uint8Array | undefined>;
196
+ payloadDatas: Uint8Array[];
197
+ }) => MaybePromise<
198
+ Array<{
199
+ bytes: Uint8Array;
200
+ cid: string;
201
+ byteLength: number;
202
+ signature: Uint8Array;
203
+ next: string[];
204
+ metaBytes: Uint8Array;
205
+ payloadBytes: Uint8Array;
206
+ signatureBytes: Uint8Array;
207
+ }>
208
+ >;
209
+ prepareEntryV0PlainEntryAndPut?: (input: {
210
+ clockId: Uint8Array;
211
+ privateKey: Uint8Array;
212
+ publicKey: Uint8Array;
213
+ wallTime: bigint | number | string;
214
+ logical?: number;
215
+ gid: string;
216
+ next?: string[];
217
+ type?: number;
218
+ metaData?: Uint8Array;
219
+ payloadData: Uint8Array;
220
+ includeMaterializationBytes?: boolean;
221
+ includeAppendFactsBytes?: boolean;
222
+ trimLengthTo?: number;
223
+ }) => MaybePromise<{
224
+ bytes: Uint8Array;
225
+ cid: string;
226
+ byteLength: number;
227
+ signature?: Uint8Array;
228
+ next: string[];
229
+ metaBytes?: Uint8Array;
230
+ payloadBytes?: Uint8Array;
231
+ signatureBytes?: Uint8Array;
232
+ hashDigestBytes?: Uint8Array;
233
+ trimmedEntries?: PreparedNativeLogEntry[];
234
+ }>;
235
+ prepareEntryV0PlainChainCommit?: (
236
+ input: {
237
+ clockId: Uint8Array;
238
+ privateKey: Uint8Array;
239
+ publicKey: Uint8Array;
240
+ wallTimes: Array<bigint | number | string>;
241
+ logicals?: number[];
242
+ gid: string;
243
+ initialNext?: string[];
244
+ type?: number;
245
+ metaDatas?: Array<Uint8Array | undefined>;
246
+ payloadDatas: Uint8Array[];
247
+ },
248
+ blockStore: unknown,
249
+ ) => MaybePromise<
250
+ | Array<{
251
+ bytes?: Uint8Array;
252
+ cid: string;
253
+ byteLength: number;
254
+ signature: Uint8Array;
255
+ next: string[];
256
+ metaBytes: Uint8Array;
257
+ payloadBytes: Uint8Array;
258
+ signatureBytes: Uint8Array;
259
+ }>
260
+ | undefined
261
+ >;
262
+ prepareEntryV0PlainEntryCommit?: (
263
+ input: {
264
+ clockId: Uint8Array;
265
+ privateKey: Uint8Array;
266
+ publicKey: Uint8Array;
267
+ wallTime: bigint | number | string;
268
+ logical?: number;
269
+ gid: string;
270
+ next?: string[];
271
+ type?: number;
272
+ metaData?: Uint8Array;
273
+ payloadData: Uint8Array;
274
+ resolveTrimmedEntries?: boolean;
275
+ },
276
+ blockStore: unknown,
277
+ ) => MaybePromise<
278
+ | {
279
+ bytes?: Uint8Array;
280
+ cid: string;
281
+ byteLength: number;
282
+ signature?: Uint8Array;
283
+ next: string[];
284
+ metaBytes?: Uint8Array;
285
+ payloadBytes?: Uint8Array;
286
+ signatureBytes?: Uint8Array;
287
+ hashDigestBytes?: Uint8Array;
288
+ trimmedEntries?: PreparedNativeLogEntry[];
289
+ trimmedEntryHashes?: string[];
290
+ }
291
+ | undefined
292
+ >;
293
+ prepareEntryV0PlainEntriesCommit?: (
294
+ input: {
295
+ clockId: Uint8Array;
296
+ privateKey: Uint8Array;
297
+ publicKey: Uint8Array;
298
+ wallTimes: Array<bigint | number | string>;
299
+ logicals?: number[];
300
+ gids: string[];
301
+ nexts: string[][];
302
+ type?: number;
303
+ metaDatas?: Array<Uint8Array | undefined>;
304
+ payloadDatas: Uint8Array[];
305
+ },
306
+ blockStore: unknown,
307
+ ) => MaybePromise<
308
+ | Array<{
309
+ bytes?: Uint8Array;
310
+ cid: string;
311
+ byteLength: number;
312
+ signature?: Uint8Array;
313
+ next: string[];
314
+ metaBytes?: Uint8Array;
315
+ payloadBytes?: Uint8Array;
316
+ signatureBytes?: Uint8Array;
317
+ hashDigestBytes?: Uint8Array;
318
+ }>
319
+ | undefined
320
+ >;
57
321
  delete: (hash: string) => boolean;
322
+ deleteMany?: (hashes: Iterable<string>) => number;
323
+ oldestEntries?: (limit: number) => NativeLogEntry[];
58
324
  clear: () => void;
59
325
  heads: (gid?: string) => string[];
326
+ hasHead: (gid?: string) => boolean;
327
+ hasAnyHead: (gids: Iterable<string>) => boolean;
328
+ hasAnyHeadBatch: (gidSets: Iterable<Iterable<string>>) => boolean[];
329
+ headDataEntries: (gid?: string) => NativeLogHeadDataEntry[];
330
+ maxHeadDataU32: (gid?: string) => number | undefined;
331
+ maxHeadDataU32Batch?: (gids: Iterable<string>) => Array<number | undefined>;
332
+ headEntries: (gid?: string) => SortableEntry[];
333
+ joinHeadEntries: (gid?: string) => NativeLogJoinEntry[];
334
+ childJoinEntries: (hash: string) => NativeLogJoinEntry[];
335
+ entryMetadataBatch?: (
336
+ hashes: Iterable<string>,
337
+ ) => Array<NativeLogEntryMetadata | undefined>;
338
+ entryMetadataHintsBatch?: (
339
+ hashes: Iterable<string>,
340
+ ) => Array<NativeLogEntryMetadata | undefined>;
341
+ entrySignaturePublicKeysBatch?: (
342
+ hashes: Iterable<string>,
343
+ ) => Array<Uint8Array | undefined>;
344
+ uniqueReferenceGids: (hash: string) => string[] | undefined;
345
+ uniqueReferenceGidRowsBatch?: (
346
+ hashes: Iterable<string>,
347
+ ) => Array<Array<[string, string]> | undefined>;
348
+ uniqueReferenceGidRowsFlatBatch?: (
349
+ hashes: Iterable<string>,
350
+ ) => Array<[number, string, string]> | undefined;
351
+ planDeleteRecursively: (
352
+ hashes: Iterable<string>,
353
+ skipFirst?: boolean,
354
+ ) => string[];
355
+ payloadSizeSum: () => number;
356
+ oldestHash?: () => string | undefined;
357
+ newestHash?: () => string | undefined;
60
358
  countHasNext: (next: string, excludeHash?: string) => number;
61
- shadowedGids: (
62
- gid: string,
359
+ shadowedGids: (gid: string, next: string[], excludeHash?: string) => string[];
360
+ planJoin: (
361
+ hash: string,
63
362
  next: string[],
64
- excludeHash?: string,
65
- ) => string[];
363
+ type: number,
364
+ reset?: boolean,
365
+ cutCheck?: NativeJoinCutCheck,
366
+ ) => JoinPlan;
367
+ planJoinBatch?: (
368
+ entries: NativeJoinPlanInput[],
369
+ reset?: boolean,
370
+ ) => JoinPlan[];
371
+ };
372
+
373
+ export type JoinPlan = {
374
+ skip: boolean;
375
+ missingParents: string[];
376
+ cutChecked: boolean;
377
+ coveredByCut: boolean;
378
+ };
379
+
380
+ export type NativeLogJoinEntry = SortableEntry & {
381
+ meta: SortableEntry["meta"] & {
382
+ type: EntryType;
383
+ next: string[];
384
+ };
385
+ };
386
+
387
+ export type NativeLogHeadDataEntry = {
388
+ hash: string;
389
+ meta: {
390
+ data?: Uint8Array;
391
+ };
392
+ };
393
+
394
+ export type NativeLogEntryMetadata = {
395
+ hash: string;
396
+ gid: string;
397
+ data?: Uint8Array;
398
+ replicas?: number;
66
399
  };
67
400
 
68
401
  type ResolveFullyOptions =
@@ -80,6 +413,7 @@ type ResolveFullyOptions =
80
413
  | boolean;
81
414
  ignoreMissing?: boolean;
82
415
  };
416
+
83
417
  type ResolveShapeOptions = { type: "shape"; shape: Shape };
84
418
  export type MaybeResolveOptions =
85
419
  | false
@@ -123,6 +457,36 @@ const withDefaultRemoteReadPriority = <
123
457
  },
124
458
  } as O;
125
459
  };
460
+
461
+ const canBatchResolveFromStore = (
462
+ store: Blocks,
463
+ options?: ResolveFullyOptions,
464
+ ): store is BlocksWithGetMany => {
465
+ if (typeof (store as BlocksWithGetMany).getMany !== "function") {
466
+ return false;
467
+ }
468
+ return typeof options !== "object" || !options.remote;
469
+ };
470
+
471
+ const sortKeyEquals = (sort: Sort | undefined, key: string[]) =>
472
+ Array.isArray(sort?.key) &&
473
+ sort.key.length === key.length &&
474
+ sort.key.every((part, index) => part === key[index]);
475
+
476
+ const timestampSortDirection = (sort: Sort[]): SortDirection | undefined => {
477
+ if (sort.length < 2) {
478
+ return;
479
+ }
480
+ const [wallTime, logical] = sort;
481
+ if (
482
+ !sortKeyEquals(wallTime, TIMESTAMP_WALL_TIME_KEY) ||
483
+ !sortKeyEquals(logical, TIMESTAMP_LOGICAL_KEY) ||
484
+ wallTime.direction !== logical.direction
485
+ ) {
486
+ return;
487
+ }
488
+ return wallTime.direction;
489
+ };
126
490
  export type ReturnTypeFromResolveOptions<
127
491
  R extends MaybeResolveOptions,
128
492
  T,
@@ -138,8 +502,9 @@ export class EntryIndex<T> {
138
502
  private initialied = false;
139
503
  private _length: number;
140
504
  private insertionPromises: Map<string, Promise<void>>;
141
- private pendingIndexWrites: Map<string, ShallowEntry>;
505
+ private pendingIndexWrites: Map<string, PendingIndexWrite>;
142
506
  private pendingIndexFlushTimer?: ReturnType<typeof setTimeout>;
507
+ private pendingIndexFlushLastWriteMs = 0;
143
508
  constructor(
144
509
  readonly properties: {
145
510
  store: Blocks;
@@ -175,15 +540,48 @@ export class EntryIndex<T> {
175
540
  this.pendingIndexWrites = new Map();
176
541
  }
177
542
 
543
+ private materializePendingIndexWrite(
544
+ hash: string,
545
+ pending: PendingIndexWrite,
546
+ ): ShallowEntry {
547
+ if (typeof pending !== "function") {
548
+ return pending;
549
+ }
550
+ const shallowEntry = pending();
551
+ this.pendingIndexWrites.set(hash, shallowEntry);
552
+ return shallowEntry;
553
+ }
554
+
555
+ private getPendingIndexWrite(hash: string): ShallowEntry | undefined {
556
+ const pending = this.pendingIndexWrites.get(hash);
557
+ return pending
558
+ ? this.materializePendingIndexWrite(hash, pending)
559
+ : undefined;
560
+ }
561
+
178
562
  private schedulePendingIndexWriteFlush() {
563
+ this.pendingIndexFlushLastWriteMs = Date.now();
179
564
  if (this.pendingIndexFlushTimer) {
180
- clearTimeout(this.pendingIndexFlushTimer);
565
+ return;
181
566
  }
182
- this.pendingIndexFlushTimer = setTimeout(() => {
567
+ const flushAfterIdle = () => {
568
+ const remainingMs =
569
+ DEFERRED_INDEX_FLUSH_IDLE_MS -
570
+ (Date.now() - this.pendingIndexFlushLastWriteMs);
571
+ if (remainingMs > 0) {
572
+ this.pendingIndexFlushTimer = setTimeout(flushAfterIdle, remainingMs);
573
+ this.pendingIndexFlushTimer.unref?.();
574
+ return;
575
+ }
576
+ this.pendingIndexFlushTimer = undefined;
183
577
  void this.flushPendingWrites().catch((error) => {
184
578
  log.error("Failed to flush deferred entry-index writes", error);
185
579
  });
186
- }, DEFERRED_INDEX_FLUSH_IDLE_MS);
580
+ };
581
+ this.pendingIndexFlushTimer = setTimeout(
582
+ flushAfterIdle,
583
+ DEFERRED_INDEX_FLUSH_IDLE_MS,
584
+ );
187
585
  this.pendingIndexFlushTimer.unref?.();
188
586
  }
189
587
 
@@ -203,13 +601,26 @@ export class EntryIndex<T> {
203
601
  return;
204
602
  }
205
603
  this.clearPendingIndexFlushTimer();
604
+ const writes: ShallowEntry[] = [];
206
605
  for (const hash of keys) {
207
606
  const pending = this.pendingIndexWrites.get(hash);
208
607
  if (!pending) {
209
608
  continue;
210
609
  }
211
- await this.properties.index.put(pending);
212
- this.pendingIndexWrites.delete(hash);
610
+ writes.push(this.materializePendingIndexWrite(hash, pending));
611
+ }
612
+ if (writes.length === 0) {
613
+ return;
614
+ }
615
+ if (writes.length > 1 && this.properties.index.putBatch) {
616
+ await this.properties.index.putBatch(writes);
617
+ } else {
618
+ for (const write of writes) {
619
+ await this.properties.index.put(write);
620
+ }
621
+ }
622
+ for (const write of writes) {
623
+ this.pendingIndexWrites.delete(write.hash);
213
624
  }
214
625
  if (this.pendingIndexWrites.size > 0) {
215
626
  this.schedulePendingIndexWriteFlush();
@@ -221,6 +632,22 @@ export class EntryIndex<T> {
221
632
  resolve: R = false as R,
222
633
  ): ResultsIterator<ReturnTypeFromResolveOptions<R, T>> {
223
634
  if (this.properties.nativeGraph?.useHeads) {
635
+ const shape = getResolveShape(resolve);
636
+ if (shape && isHeadHashOnlyShape(shape)) {
637
+ return this.iterateNativeProjected(
638
+ () =>
639
+ this.properties
640
+ .nativeGraph!.graph.heads(gid)
641
+ .map((hash) => ({ hash })),
642
+ shape,
643
+ ) as ResultsIterator<ReturnTypeFromResolveOptions<R, T>>;
644
+ }
645
+ if (shape && isHeadDataShape(shape)) {
646
+ return this.iterateNativeProjected(
647
+ () => this.properties.nativeGraph!.graph.headDataEntries(gid),
648
+ shape,
649
+ ) as ResultsIterator<ReturnTypeFromResolveOptions<R, T>>;
650
+ }
224
651
  return this.iterateNativeHashes(
225
652
  () => this.properties.nativeGraph!.graph.heads(gid),
226
653
  resolve,
@@ -242,6 +669,146 @@ export class EntryIndex<T> {
242
669
  return this.iterate(query, undefined, resolve);
243
670
  }
244
671
 
672
+ getHeadsForAppend(gid?: string): SortableEntry[] | undefined {
673
+ if (!this.properties.nativeGraph?.useHeads) {
674
+ return undefined;
675
+ }
676
+ return this.properties.nativeGraph.graph.headEntries(gid);
677
+ }
678
+
679
+ async hasHead(gid?: string): Promise<boolean | undefined> {
680
+ if (!this.properties.nativeGraph?.useHeads) {
681
+ return undefined;
682
+ }
683
+ return this.properties.nativeGraph.graph.hasHead(gid);
684
+ }
685
+
686
+ async hasAnyHead(gids: Iterable<string>): Promise<boolean | undefined> {
687
+ if (!this.properties.nativeGraph?.useHeads) {
688
+ return undefined;
689
+ }
690
+ const uniqueGids = new Set([...gids].filter(Boolean));
691
+ if (uniqueGids.size === 0) {
692
+ return false;
693
+ }
694
+ return this.properties.nativeGraph.graph.hasAnyHead(uniqueGids);
695
+ }
696
+
697
+ async hasAnyHeadBatch(
698
+ gidSets: Iterable<Iterable<string>>,
699
+ ): Promise<boolean[] | undefined> {
700
+ if (!this.properties.nativeGraph?.useHeads) {
701
+ return undefined;
702
+ }
703
+ const normalized = [...gidSets].map(
704
+ (gids) => new Set([...gids].filter(Boolean)),
705
+ );
706
+ if (normalized.length === 0) {
707
+ return [];
708
+ }
709
+ return this.properties.nativeGraph.graph.hasAnyHeadBatch(normalized);
710
+ }
711
+
712
+ async getMaxHeadDataU32(gid?: string): Promise<number | undefined> {
713
+ if (!this.properties.nativeGraph?.useHeads) {
714
+ return undefined;
715
+ }
716
+ return this.properties.nativeGraph.graph.maxHeadDataU32(gid);
717
+ }
718
+
719
+ async getMaxHeadDataU32Batch(
720
+ gids: Iterable<string>,
721
+ ): Promise<Array<number | undefined> | undefined> {
722
+ if (!this.properties.nativeGraph?.useHeads) {
723
+ return undefined;
724
+ }
725
+ const normalized = [...gids];
726
+ if (normalized.length === 0) {
727
+ return [];
728
+ }
729
+ return this.properties.nativeGraph.graph.maxHeadDataU32Batch?.(normalized);
730
+ }
731
+
732
+ getJoinHeads(gid?: string): Promise<NativeLogJoinEntry[]> {
733
+ if (this.properties.nativeGraph?.useHeads) {
734
+ return Promise.resolve(
735
+ this.properties.nativeGraph.graph.joinHeadEntries(gid),
736
+ );
737
+ }
738
+ return this.getHeads(gid, {
739
+ type: "shape",
740
+ shape: {
741
+ hash: true,
742
+ meta: { type: true, next: true, gid: true, clock: true },
743
+ },
744
+ }).all() as Promise<NativeLogJoinEntry[]>;
745
+ }
746
+
747
+ getJoinChildren(next: string): Promise<NativeLogJoinEntry[]> {
748
+ if (this.properties.nativeGraph) {
749
+ return Promise.resolve(
750
+ this.properties.nativeGraph.graph.childJoinEntries(next),
751
+ );
752
+ }
753
+ return this.getHasNext(next, {
754
+ type: "shape",
755
+ shape: {
756
+ hash: true,
757
+ meta: { type: true, next: true, gid: true, clock: true },
758
+ },
759
+ }).all() as Promise<NativeLogJoinEntry[]>;
760
+ }
761
+
762
+ getUniqueReferenceGids(hash: string): string[] | undefined {
763
+ if (!this.properties.nativeGraph) {
764
+ return undefined;
765
+ }
766
+ return this.properties.nativeGraph.graph.uniqueReferenceGids(hash);
767
+ }
768
+
769
+ getUniqueReferenceGidRowsBatch(
770
+ hashes: Iterable<string>,
771
+ ): Array<Array<[string, string]> | undefined> | undefined {
772
+ if (!this.properties.nativeGraph) {
773
+ return undefined;
774
+ }
775
+ const normalized = [...hashes];
776
+ if (normalized.length === 0) {
777
+ return [];
778
+ }
779
+ return this.properties.nativeGraph.graph.uniqueReferenceGidRowsBatch?.(
780
+ normalized,
781
+ );
782
+ }
783
+
784
+ getUniqueReferenceGidRowsFlatBatch(
785
+ hashes: Iterable<string>,
786
+ ): Array<[number, string, string]> | undefined {
787
+ if (!this.properties.nativeGraph) {
788
+ return undefined;
789
+ }
790
+ const normalized = [...hashes];
791
+ if (normalized.length === 0) {
792
+ return [];
793
+ }
794
+ return this.properties.nativeGraph.graph.uniqueReferenceGidRowsFlatBatch?.(
795
+ normalized,
796
+ );
797
+ }
798
+
799
+ planDeleteRecursively(
800
+ from: Iterable<{ hash: string }>,
801
+ skipFirst = false,
802
+ ): string[] | undefined {
803
+ if (!this.properties.nativeGraph) {
804
+ return undefined;
805
+ }
806
+ return this.properties.nativeGraph.graph.planDeleteRecursively(
807
+ [...from].map((entry) => entry.hash),
808
+ skipFirst,
809
+ );
810
+ }
811
+
245
812
  getHasNext<R extends MaybeResolveOptions>(
246
813
  next: string,
247
814
  resolve?: R,
@@ -265,10 +832,10 @@ export class EntryIndex<T> {
265
832
  next: string,
266
833
  excludeHash: string | undefined = undefined,
267
834
  ) {
268
- await this.flushPendingWrites();
269
835
  if (this.properties.nativeGraph) {
270
836
  return this.properties.nativeGraph.graph.countHasNext(next, excludeHash);
271
837
  }
838
+ await this.flushPendingWrites();
272
839
  const query: Query[] = [
273
840
  new StringMatch({
274
841
  key: ["meta", "next"],
@@ -314,7 +881,7 @@ export class EntryIndex<T> {
314
881
 
315
882
  const getHashes = async () => {
316
883
  if (!hashPromise) {
317
- hashPromise = this.flushPendingWrites().then(() => hashes());
884
+ hashPromise = Promise.resolve(hashes());
318
885
  }
319
886
  return hashPromise;
320
887
  };
@@ -323,13 +890,10 @@ export class EntryIndex<T> {
323
890
  hashes: string[],
324
891
  ): Promise<ReturnTypeFromResolveOptions<R, T>[]> => {
325
892
  if (resolveInFull) {
326
- const resolved = await Promise.all(
327
- hashes.map((hash) => this.resolve(hash, resolveInFullOptions)),
328
- );
329
- return resolved.filter((entry) => !!entry) as ReturnTypeFromResolveOptions<
330
- R,
331
- T
332
- >[];
893
+ const resolved = await this.resolveMany(hashes, resolveInFullOptions);
894
+ return resolved.filter(
895
+ (entry) => !!entry,
896
+ ) as ReturnTypeFromResolveOptions<R, T>[];
333
897
  }
334
898
 
335
899
  const shallow = await Promise.all(
@@ -362,6 +926,44 @@ export class EntryIndex<T> {
362
926
  };
363
927
  }
364
928
 
929
+ private iterateNativeProjected<TValue>(
930
+ values: () => TValue[],
931
+ shape: Shape,
932
+ ): ResultsIterator<unknown> {
933
+ let valuesPromise: Promise<TValue[]> | undefined;
934
+ let offset = 0;
935
+ let complete = false;
936
+
937
+ const getValues = async () => {
938
+ if (!valuesPromise) {
939
+ valuesPromise = Promise.resolve(values());
940
+ }
941
+ return valuesPromise;
942
+ };
943
+
944
+ const coerce = (values: TValue[]) =>
945
+ values.map((value) => projectShape(value, shape));
946
+
947
+ return {
948
+ close: () => undefined,
949
+ done: () => complete,
950
+ next: async (amount: number) => {
951
+ const all = await getValues();
952
+ const batch = all.slice(offset, offset + amount);
953
+ offset += batch.length;
954
+ complete = offset >= all.length;
955
+ return coerce(batch);
956
+ },
957
+ all: async () => {
958
+ const all = await getValues();
959
+ const remaining = all.slice(offset);
960
+ offset = all.length;
961
+ complete = true;
962
+ return coerce(remaining);
963
+ },
964
+ };
965
+ }
966
+
365
967
  iterate<R extends MaybeResolveOptions>(
366
968
  query: Query[],
367
969
  sort = this.properties.sort.sort,
@@ -380,7 +982,9 @@ export class EntryIndex<T> {
380
982
  ? ({ hash: true } as const)
381
983
  : ((options as { shape: Shape })?.shape as Shape);
382
984
 
383
- let iteratorRef: ReturnType<typeof this.properties.index.iterate> | undefined;
985
+ let iteratorRef:
986
+ | ReturnType<typeof this.properties.index.iterate>
987
+ | undefined;
384
988
  let iteratorPromise:
385
989
  | Promise<ReturnType<typeof this.properties.index.iterate>>
386
990
  | undefined;
@@ -415,8 +1019,9 @@ export class EntryIndex<T> {
415
1019
  }>,
416
1020
  ): Promise<ReturnTypeFromResolveOptions<R, T>[]> => {
417
1021
  if (resolveInFull) {
418
- const maybeResolved = await Promise.all(
419
- results.map((x) => this.resolve(x.value.hash, resolveInFullOptions)),
1022
+ const maybeResolved = await this.resolveMany(
1023
+ results.map((x) => x.value.hash),
1024
+ resolveInFullOptions,
420
1025
  );
421
1026
  return maybeResolved.filter((x) => !!x) as ReturnTypeFromResolveOptions<
422
1027
  R,
@@ -442,22 +1047,111 @@ export class EntryIndex<T> {
442
1047
  T extends boolean,
443
1048
  R = T extends true ? Entry<any> : ShallowEntry,
444
1049
  >(resolve?: T): Promise<R | undefined> {
1050
+ const nativeHash = this.getNativeTimestampOrderedHash("oldest");
1051
+ if (nativeHash) {
1052
+ const nativeResult = resolve
1053
+ ? await this.resolve(nativeHash)
1054
+ : (await this.getShallow(nativeHash))?.value;
1055
+ if (nativeResult) {
1056
+ return nativeResult as R;
1057
+ }
1058
+ }
445
1059
  const iterator = this.iterate([], this.properties.sort.sort, resolve);
446
1060
  const results = await iterator.next(1);
447
1061
  await iterator.close();
448
1062
  return results[0] as R;
449
1063
  }
450
1064
 
1065
+ async getOldestMany<
1066
+ T extends boolean,
1067
+ R = T extends true ? Entry<any> : ShallowEntry,
1068
+ >(limit: number, resolve?: T): Promise<R[]> {
1069
+ return this.getOldestManyMaybe(limit, resolve);
1070
+ }
1071
+
1072
+ getOldestManyMaybe<
1073
+ T extends boolean,
1074
+ R = T extends true ? Entry<any> : ShallowEntry,
1075
+ >(limit: number, resolve?: T): MaybePromise<R[]> {
1076
+ if (limit <= 0) {
1077
+ return [];
1078
+ }
1079
+ const nativeEntries = this.getNativeOldestEntries(limit, resolve);
1080
+ if (nativeEntries) {
1081
+ return nativeEntries as R[];
1082
+ }
1083
+ return this.getOldestManyFromIterator(limit, resolve);
1084
+ }
1085
+
1086
+ private async getOldestManyFromIterator<
1087
+ T extends boolean,
1088
+ R = T extends true ? Entry<any> : ShallowEntry,
1089
+ >(limit: number, resolve?: T): Promise<R[]> {
1090
+ const iterator = this.iterate([], this.properties.sort.sort, resolve);
1091
+ try {
1092
+ return (await iterator.next(limit)) as R[];
1093
+ } finally {
1094
+ await iterator.close();
1095
+ }
1096
+ }
1097
+
1098
+ private getNativeOldestEntries<T extends boolean>(
1099
+ limit: number,
1100
+ resolve?: T,
1101
+ ): ShallowEntry[] | undefined {
1102
+ if (resolve || limit <= 0) {
1103
+ return;
1104
+ }
1105
+ const graph = this.properties.nativeGraph?.graph;
1106
+ if (!graph?.oldestEntries) {
1107
+ return;
1108
+ }
1109
+ const direction = timestampSortDirection(this.properties.sort.sort);
1110
+ if (direction !== SortDirection.ASC) {
1111
+ return;
1112
+ }
1113
+ return graph
1114
+ .oldestEntries(limit)
1115
+ .map((entry) => this.nativeLogEntryToShallowEntry(entry));
1116
+ }
1117
+
451
1118
  async getNewest<
452
1119
  T extends boolean,
453
1120
  R = T extends true ? Entry<any> : ShallowEntry,
454
1121
  >(resolve?: T): Promise<R | undefined> {
1122
+ const nativeHash = this.getNativeTimestampOrderedHash("newest");
1123
+ if (nativeHash) {
1124
+ const nativeResult = resolve
1125
+ ? await this.resolve(nativeHash)
1126
+ : (await this.getShallow(nativeHash))?.value;
1127
+ if (nativeResult) {
1128
+ return nativeResult as R;
1129
+ }
1130
+ }
455
1131
  const iterator = this.iterate([], this.sortReversed, resolve);
456
1132
  const results = await iterator.next(1);
457
1133
  await iterator.close();
458
1134
  return results[0] as R;
459
1135
  }
460
1136
 
1137
+ private getNativeTimestampOrderedHash(
1138
+ position: "oldest" | "newest",
1139
+ ): string | undefined {
1140
+ const graph = this.properties.nativeGraph?.graph;
1141
+ if (!graph?.oldestHash || !graph.newestHash) {
1142
+ return;
1143
+ }
1144
+ const direction = timestampSortDirection(this.properties.sort.sort);
1145
+ if (direction == null) {
1146
+ return;
1147
+ }
1148
+ const useNewest =
1149
+ direction === SortDirection.ASC
1150
+ ? position === "newest"
1151
+ : position === "oldest";
1152
+ return useNewest ? graph.newestHash() : graph.oldestHash();
1153
+ }
1154
+
461
1155
  async getBefore<
462
1156
  T extends boolean,
463
1157
  R = T extends true ? Entry<any> : ShallowEntry,
@@ -490,18 +1184,163 @@ export class EntryIndex<T> {
490
1184
  return this.resolve(k, options);
491
1185
  }
492
1186
 
1187
+ async getMany(k: string[], options?: ResolveFullyOptions) {
1188
+ return this.resolveMany(k, options);
1189
+ }
1190
+
1191
+ async planJoin(
1192
+ entry: Pick<Entry<T>, "hash" | "meta">,
1193
+ reset?: boolean,
1194
+ ): Promise<JoinPlan> {
1195
+ if (this.properties.nativeGraph) {
1196
+ const cutCheck = this.properties.nativeGraph.useHeads
1197
+ ? {
1198
+ gid: entry.meta.gid,
1199
+ wallTime: entry.meta.clock.timestamp.wallTime,
1200
+ logical: entry.meta.clock.timestamp.logical,
1201
+ }
1202
+ : undefined;
1203
+ return this.properties.nativeGraph.graph.planJoin(
1204
+ entry.hash,
1205
+ entry.meta.next,
1206
+ entry.meta.type,
1207
+ reset === true,
1208
+ cutCheck,
1209
+ );
1210
+ }
1211
+ if (!reset && (await this.getShallow(entry.hash)) != null) {
1212
+ return {
1213
+ skip: true,
1214
+ missingParents: [],
1215
+ cutChecked: false,
1216
+ coveredByCut: false,
1217
+ };
1218
+ }
1219
+ if (entry.meta.type === EntryType.CUT) {
1220
+ return {
1221
+ skip: false,
1222
+ missingParents: [],
1223
+ cutChecked: false,
1224
+ coveredByCut: false,
1225
+ };
1226
+ }
1227
+
1228
+ const missingParents: string[] = [];
1229
+ for (const next of entry.meta.next) {
1230
+ if (reset || (await this.getShallow(next)) == null) {
1231
+ missingParents.push(next);
1232
+ }
1233
+ }
1234
+ return {
1235
+ skip: false,
1236
+ missingParents,
1237
+ cutChecked: false,
1238
+ coveredByCut: false,
1239
+ };
1240
+ }
1241
+
1242
+ async planJoinBatch(
1243
+ entries: Array<Pick<Entry<T>, "hash" | "meta">>,
1244
+ reset?: boolean,
1245
+ profile?: EntryIndexProfileSink,
1246
+ ): Promise<JoinPlan[]> {
1247
+ if (entries.length === 0) {
1248
+ return [];
1249
+ }
1250
+ if (this.properties.nativeGraph?.graph.planJoinBatch) {
1251
+ const prepareStartedAt = entryIndexProfileStart(profile);
1252
+ const nativeInputs = entries.map((entry) => ({
1253
+ hash: entry.hash,
1254
+ next: entry.meta.next,
1255
+ type: entry.meta.type,
1256
+ cutCheck: this.properties.nativeGraph!.useHeads
1257
+ ? {
1258
+ gid: entry.meta.gid,
1259
+ wallTime: entry.meta.clock.timestamp.wallTime,
1260
+ logical: entry.meta.clock.timestamp.logical,
1261
+ }
1262
+ : undefined,
1263
+ }));
1264
+ emitEntryIndexProfileDuration(profile, prepareStartedAt, {
1265
+ name: "log.entryIndex.planJoinBatch.prepareNative",
1266
+ component: "log",
1267
+ entries: entries.length,
1268
+ messages: 1,
1269
+ details: {
1270
+ cutChecks: nativeInputs.reduce(
1271
+ (sum, input) => sum + (input.cutCheck ? 1 : 0),
1272
+ 0,
1273
+ ),
1274
+ },
1275
+ });
1276
+ const nativeStartedAt = entryIndexProfileStart(profile);
1277
+ const plans = this.properties.nativeGraph.graph.planJoinBatch(
1278
+ nativeInputs,
1279
+ reset === true,
1280
+ );
1281
+ emitEntryIndexProfileDuration(profile, nativeStartedAt, {
1282
+ name: "log.entryIndex.planJoinBatch.nativeGraph",
1283
+ component: "log",
1284
+ entries: entries.length,
1285
+ messages: 1,
1286
+ });
1287
+ return plans;
1288
+ }
1289
+
1290
+ const fallbackStartedAt = entryIndexProfileStart(profile);
1291
+ const plans: JoinPlan[] = [];
1292
+ for (const entry of entries) {
1293
+ plans.push(await this.planJoin(entry, reset));
1294
+ }
1295
+ emitEntryIndexProfileDuration(profile, fallbackStartedAt, {
1296
+ name: "log.entryIndex.planJoinBatch.fallback",
1297
+ component: "log",
1298
+ entries: entries.length,
1299
+ messages: 1,
1300
+ });
1301
+ return plans;
1302
+ }
1303
+
493
1304
  async getShallow(k: string) {
494
- const pending = this.pendingIndexWrites.get(k);
1305
+ const pending = this.getPendingIndexWrite(k);
495
1306
  if (pending) {
496
1307
  return { id: toId(k), value: pending };
497
1308
  }
498
1309
  return this.properties.index.get(toId(k));
499
1310
  }
500
1311
 
1312
+ getNativeEntryMetadataBatch(
1313
+ hashes: Iterable<string>,
1314
+ ): Array<NativeLogEntryMetadata | undefined> | undefined {
1315
+ if (!this.properties.nativeGraph) {
1316
+ return undefined;
1317
+ }
1318
+ const normalized = [...hashes];
1319
+ if (normalized.length === 0) {
1320
+ return [];
1321
+ }
1322
+ return this.properties.nativeGraph.graph.entryMetadataBatch?.(normalized);
1323
+ }
1324
+
1325
+ getNativeEntryMetadataHintsBatch(
1326
+ hashes: Iterable<string>,
1327
+ ): Array<NativeLogEntryMetadata | undefined> | undefined {
1328
+ if (!this.properties.nativeGraph) {
1329
+ return undefined;
1330
+ }
1331
+ const normalized = [...hashes];
1332
+ if (normalized.length === 0) {
1333
+ return [];
1334
+ }
1335
+ return (
1336
+ this.properties.nativeGraph.graph.entryMetadataHintsBatch?.(normalized) ??
1337
+ this.properties.nativeGraph.graph.entryMetadataBatch?.(normalized)
1338
+ );
1339
+ }
1340
+
501
1341
  async has(k: string) {
502
- let mem = this.cache.get(k);
503
- if (mem) {
504
- return true;
1342
+ if (this.properties.nativeGraph) {
1343
+ return this.properties.nativeGraph.graph.has(k);
505
1344
  }
506
1345
  if (this.pendingIndexWrites.has(k)) {
507
1346
  return true;
@@ -513,11 +1352,16 @@ export class EntryIndex<T> {
513
1352
  }
514
1353
 
515
1354
  async hasMany(hashes: Iterable<string>) {
1355
+ if (this.properties.nativeGraph) {
1356
+ return this.properties.nativeGraph.graph.hasMany(
1357
+ new Set([...hashes].filter(Boolean)),
1358
+ );
1359
+ }
516
1360
  const batchSize = 64;
517
1361
  const existing = new Set<string>();
518
1362
  const missing: string[] = [];
519
1363
  for (const hash of new Set([...hashes].filter(Boolean))) {
520
- if (this.cache.get(hash) || this.pendingIndexWrites.has(hash)) {
1364
+ if (this.pendingIndexWrites.has(hash)) {
521
1365
  existing.add(hash);
522
1366
  continue;
523
1367
  }
@@ -576,9 +1420,19 @@ export class EntryIndex<T> {
576
1420
  ) {
577
1421
  if (properties.toMultiHash) {
578
1422
  const existingHash = entry.hash;
579
- entry.hash = undefined as any;
1423
+ const preparedBlock = Entry.takePreparedBlock(entry);
580
1424
  try {
581
- const hash = await Entry.toMultihash(this.properties.store, entry);
1425
+ let hash: string;
1426
+ if (preparedBlock) {
1427
+ hash = await putPreparedEntryBlock(
1428
+ this.properties.store,
1429
+ preparedBlock,
1430
+ );
1431
+ entry.size = preparedBlock.block.bytes.length;
1432
+ } else {
1433
+ entry.hash = undefined as any;
1434
+ hash = await Entry.toMultihash(this.properties.store, entry);
1435
+ }
582
1436
  entry.hash = existingHash;
583
1437
  if (entry.hash === undefined) {
584
1438
  entry.hash = hash; // can happen if you sync entries that you load directly from ipfs
@@ -607,7 +1461,12 @@ export class EntryIndex<T> {
607
1461
 
608
1462
  // add cache after .has check before .has uses the cache
609
1463
  this.cache.add(entry.hash, entry);
610
- const shallowEntry = entry.toShallow(properties.isHead);
1464
+ const shallowEntry =
1465
+ Entry.takePreparedShallowEntry(entry, properties.isHead) ??
1466
+ entry.toShallow(properties.isHead);
1467
+ const nativeEntry =
1468
+ Entry.takePreparedNativeLogEntry(entry, properties.isHead) ??
1469
+ toNativeLogEntry(shallowEntry);
611
1470
  const shouldDeferIndexWrite =
612
1471
  properties.deferIndexWrite === true &&
613
1472
  properties.isHead &&
@@ -621,24 +1480,10 @@ export class EntryIndex<T> {
621
1480
  await this.flushPendingWrites(entry.meta.next);
622
1481
  await this.properties.index.put(shallowEntry);
623
1482
  }
624
- this.properties.nativeGraph?.graph.put(toNativeLogEntry(shallowEntry));
1483
+ this.properties.nativeGraph?.graph.put(nativeEntry);
625
1484
 
626
1485
  // check if gids has been shadowed, by query all nexts that have a different gid
627
- if (this.properties.onGidRemoved && entry.meta.next.length > 0) {
628
- const shadowedGids: Set<string> = this.properties.nativeGraph
629
- ? new Set<string>(
630
- this.properties.nativeGraph.graph.shadowedGids(
631
- entry.meta.gid,
632
- entry.meta.next,
633
- entry.hash,
634
- ),
635
- )
636
- : await this.findShadowedGids(entry);
637
-
638
- if (shadowedGids.size > 0) {
639
- this.properties.onGidRemoved?.([...shadowedGids]);
640
- }
641
- }
1486
+ await this.notifyShadowedGids(entry);
642
1487
 
643
1488
  // mark all next entries as not heads
644
1489
  await this.privateUpdateNextHeadProperty(entry, false);
@@ -651,6 +1496,679 @@ export class EntryIndex<T> {
651
1496
  }
652
1497
  }
653
1498
 
1499
+ async putAppendBatch(
1500
+ entries: Entry<any>[],
1501
+ properties: {
1502
+ unique: boolean;
1503
+ externalNextHashes?: string[];
1504
+ prepared?: {
1505
+ shallowEntries: ShallowEntry[];
1506
+ nativeEntries?: NativeLogEntry[];
1507
+ nativeGraphUpdated?: boolean;
1508
+ nativeBlocksCommitted?: boolean;
1509
+ };
1510
+ heads?: boolean[];
1511
+ deferIndexWrite?: boolean;
1512
+ profile?: EntryIndexProfileSink;
1513
+ },
1514
+ ) {
1515
+ if (entries.length === 0) {
1516
+ return;
1517
+ }
1518
+ if (
1519
+ entries.length === 1 &&
1520
+ properties.prepared?.nativeGraphUpdated !== true
1521
+ ) {
1522
+ return this.put(entries[0], {
1523
+ unique: properties.unique,
1524
+ isHead: true,
1525
+ toMultiHash: false,
1526
+ deferIndexWrite: properties.deferIndexWrite,
1527
+ });
1528
+ }
1529
+
1530
+ for (const entry of entries) {
1531
+ if (!entry.hash) {
1532
+ throw new Error("Missing hash");
1533
+ }
1534
+ const existingPromise = this.insertionPromises.get(entry.hash);
1535
+ if (existingPromise) {
1536
+ await existingPromise;
1537
+ }
1538
+ }
1539
+
1540
+ const promise = (async () => {
1541
+ const profile = properties.profile;
1542
+ const prepareStartedAt = entryIndexProfileStart(profile);
1543
+ const externalNexts = new Set<string>(
1544
+ properties.externalNextHashes ?? [],
1545
+ );
1546
+ const shouldDiscoverExternalNexts = !properties.externalNextHashes;
1547
+ const batchHashes = shouldDiscoverExternalNexts
1548
+ ? new Set(entries.map((entry) => entry.hash))
1549
+ : undefined;
1550
+ const putBatch =
1551
+ !this.properties.onGidRemoved && this.properties.index.putBatch;
1552
+ const shallowEntries: ShallowEntry[] = [];
1553
+ const nativeGraphUpdated =
1554
+ properties.prepared?.nativeGraphUpdated === true;
1555
+ const nativeCommitOwnsHotIndex =
1556
+ nativeGraphUpdated &&
1557
+ properties.prepared?.nativeBlocksCommitted === true &&
1558
+ !this.properties.onGidRemoved;
1559
+ const nativeGraphPutAppendChain =
1560
+ !nativeGraphUpdated &&
1561
+ !this.properties.onGidRemoved &&
1562
+ properties.externalNextHashes &&
1563
+ this.properties.nativeGraph?.graph.putAppendChain
1564
+ ? this.properties.nativeGraph.graph.putAppendChain.bind(
1565
+ this.properties.nativeGraph.graph,
1566
+ )
1567
+ : undefined;
1568
+ const nativeGraphPutBatch =
1569
+ !nativeGraphUpdated &&
1570
+ !this.properties.onGidRemoved &&
1571
+ this.properties.nativeGraph?.graph.putBatch
1572
+ ? this.properties.nativeGraph.graph.putBatch.bind(
1573
+ this.properties.nativeGraph.graph,
1574
+ )
1575
+ : undefined;
1576
+ const deferBatchIndexWrite =
1577
+ properties.deferIndexWrite === true &&
1578
+ !!putBatch &&
1579
+ !!this.properties.nativeGraph &&
1580
+ !this.properties.onGidRemoved &&
1581
+ entries.every((entry) => entry.meta.type !== EntryType.CUT);
1582
+ const nativeEntries: NativeLogEntry[] = [];
1583
+ for (let i = 0; i < entries.length; i++) {
1584
+ const entry = entries[i];
1585
+ const isHead = properties.heads?.[i] ?? i === entries.length - 1;
1586
+ if (properties.unique === true || !(await this.has(entry.hash))) {
1587
+ this._length++;
1588
+ }
1589
+
1590
+ this.cache.add(entry.hash, entry);
1591
+ const preparedShallowEntry = properties.prepared?.shallowEntries[i];
1592
+ if (preparedShallowEntry) {
1593
+ preparedShallowEntry.head = isHead;
1594
+ }
1595
+ const shallowEntry =
1596
+ preparedShallowEntry ??
1597
+ Entry.takePreparedShallowEntry(entry, isHead) ??
1598
+ entry.toShallow(isHead);
1599
+ if (nativeCommitOwnsHotIndex) {
1600
+ this.pendingIndexWrites.set(entry.hash, shallowEntry);
1601
+ if (batchHashes) {
1602
+ for (const next of entry.meta.next) {
1603
+ if (!batchHashes.has(next)) {
1604
+ externalNexts.add(next);
1605
+ }
1606
+ }
1607
+ }
1608
+ continue;
1609
+ }
1610
+ const preparedNativeEntry = properties.prepared?.nativeEntries?.[i];
1611
+ if (preparedNativeEntry) {
1612
+ preparedNativeEntry.head = isHead;
1613
+ }
1614
+ const nativeEntry =
1615
+ !nativeGraphUpdated &&
1616
+ this.properties.nativeGraph &&
1617
+ (preparedNativeEntry ??
1618
+ Entry.takePreparedNativeLogEntry(entry, isHead) ??
1619
+ toNativeLogEntry(shallowEntry));
1620
+ if (putBatch) {
1621
+ shallowEntries.push(shallowEntry);
1622
+ nativeEntry && nativeEntries.push(nativeEntry);
1623
+ } else {
1624
+ await this.properties.index.put(shallowEntry);
1625
+ if (nativeGraphPutAppendChain || nativeGraphPutBatch) {
1626
+ nativeEntry && nativeEntries.push(nativeEntry);
1627
+ } else {
1628
+ if (nativeEntry) {
1629
+ this.properties.nativeGraph?.graph.put(nativeEntry);
1630
+ await this.notifyShadowedGids(entry);
1631
+ }
1632
+ }
1633
+ }
1634
+
1635
+ if (batchHashes) {
1636
+ for (const next of entry.meta.next) {
1637
+ if (!batchHashes.has(next)) {
1638
+ externalNexts.add(next);
1639
+ }
1640
+ }
1641
+ }
1642
+ }
1643
+ emitEntryIndexProfileDuration(profile, prepareStartedAt, {
1644
+ name: "log.entryIndex.putAppendBatch.prepare",
1645
+ component: "log",
1646
+ entries: entries.length,
1647
+ messages: 1,
1648
+ details: {
1649
+ unique: properties.unique,
1650
+ usedPreparedShallowEntries:
1651
+ properties.prepared?.shallowEntries.length ?? 0,
1652
+ usedPreparedNativeEntries:
1653
+ properties.prepared?.nativeEntries?.length ?? 0,
1654
+ nativeEntries: nativeEntries.length,
1655
+ discoverExternalNexts: shouldDiscoverExternalNexts,
1656
+ },
1657
+ });
1658
+
1659
+ const putNativeEntries = (allowLoopFallback: boolean) => {
1660
+ if (nativeEntries.length === 0) {
1661
+ return;
1662
+ }
1663
+ if (nativeGraphPutAppendChain) {
1664
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1665
+ nativeGraphPutAppendChain(nativeEntries);
1666
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1667
+ name: "log.entryIndex.putAppendBatch.nativeGraphPut",
1668
+ component: "log",
1669
+ entries: nativeEntries.length,
1670
+ messages: 1,
1671
+ details: { method: "putAppendChain" },
1672
+ });
1673
+ } else if (nativeGraphPutBatch) {
1674
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1675
+ nativeGraphPutBatch(nativeEntries);
1676
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1677
+ name: "log.entryIndex.putAppendBatch.nativeGraphPut",
1678
+ component: "log",
1679
+ entries: nativeEntries.length,
1680
+ messages: 1,
1681
+ details: { method: "putBatch" },
1682
+ });
1683
+ } else if (allowLoopFallback) {
1684
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1685
+ for (const nativeEntry of nativeEntries) {
1686
+ this.properties.nativeGraph?.graph.put(nativeEntry);
1687
+ }
1688
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1689
+ name: "log.entryIndex.putAppendBatch.nativeGraphPut",
1690
+ component: "log",
1691
+ entries: nativeEntries.length,
1692
+ messages: 1,
1693
+ details: { method: "putLoop" },
1694
+ });
1695
+ }
1696
+ };
1697
+
1698
+ if (nativeCommitOwnsHotIndex) {
1699
+ this.schedulePendingIndexWriteFlush();
1700
+ } else if (deferBatchIndexWrite) {
1701
+ const indexPutStartedAt = entryIndexProfileStart(profile);
1702
+ for (const shallowEntry of shallowEntries) {
1703
+ this.pendingIndexWrites.set(shallowEntry.hash, shallowEntry);
1704
+ }
1705
+ this.schedulePendingIndexWriteFlush();
1706
+ emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
1707
+ name: "log.entryIndex.putAppendBatch.indexPut",
1708
+ component: "log",
1709
+ entries: shallowEntries.length,
1710
+ messages: 1,
1711
+ details: { deferred: true },
1712
+ });
1713
+ putNativeEntries(true);
1714
+ } else if (putBatch) {
1715
+ const indexPutStartedAt = entryIndexProfileStart(profile);
1716
+ await putBatch.call(this.properties.index, shallowEntries);
1717
+ emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
1718
+ name: "log.entryIndex.putAppendBatch.indexPut",
1719
+ component: "log",
1720
+ entries: shallowEntries.length,
1721
+ messages: 1,
1722
+ });
1723
+ putNativeEntries(true);
1724
+ } else if (nativeEntries.length > 0) {
1725
+ putNativeEntries(false);
1726
+ }
1727
+
1728
+ if (externalNexts.size > 0) {
1729
+ const externalNextStartedAt = entryIndexProfileStart(profile);
1730
+ await this.privateUpdateNextHeadHashes([...externalNexts], false);
1731
+ emitEntryIndexProfileDuration(profile, externalNextStartedAt, {
1732
+ name: "log.entryIndex.putAppendBatch.externalNexts",
1733
+ component: "log",
1734
+ entries: externalNexts.size,
1735
+ messages: 1,
1736
+ });
1737
+ }
1738
+ })().finally(() => {
1739
+ for (const entry of entries) {
1740
+ this.insertionPromises.delete(entry.hash);
1741
+ }
1742
+ });
1743
+
1744
+ for (const entry of entries) {
1745
+ this.insertionPromises.set(entry.hash, promise);
1746
+ }
1747
+
1748
+ return promise;
1749
+ }
1750
+
1751
+ // Internal trusted receive path for callers that can supply prepared append facts.
1752
+ async putAppendFactsBatch(
1753
+ entries: PreparedAppendIndexFacts[],
1754
+ properties: {
1755
+ unique: boolean;
1756
+ externalNextHashes?: string[];
1757
+ heads?: boolean[];
1758
+ deferIndexWrite?: boolean;
1759
+ nativeGraphUpdated?: boolean;
1760
+ profile?: EntryIndexProfileSink;
1761
+ },
1762
+ ) {
1763
+ if (entries.length === 0) {
1764
+ return;
1765
+ }
1766
+ if (this.properties.onGidRemoved) {
1767
+ throw new Error(
1768
+ "Prepared append facts batch requires no onGidRemoved hook",
1769
+ );
1770
+ }
1771
+
1772
+ for (const entry of entries) {
1773
+ if (!entry.hash) {
1774
+ throw new Error("Missing hash");
1775
+ }
1776
+ const existingPromise = this.insertionPromises.get(entry.hash);
1777
+ if (existingPromise) {
1778
+ await existingPromise;
1779
+ }
1780
+ }
1781
+
1782
+ const promise = (async () => {
1783
+ const profile = properties.profile;
1784
+ const prepareStartedAt = entryIndexProfileStart(profile);
1785
+ const externalNexts = new Set<string>(
1786
+ properties.externalNextHashes ?? [],
1787
+ );
1788
+ const shouldDiscoverExternalNexts = !properties.externalNextHashes;
1789
+ const batchHashes = shouldDiscoverExternalNexts
1790
+ ? new Set(entries.map((entry) => entry.hash))
1791
+ : undefined;
1792
+ const putBatch = this.properties.index.putBatch;
1793
+ const shallowEntries: ShallowEntry[] = [];
1794
+ const nativeEntries: NativeLogEntry[] = [];
1795
+ const nativeGraphUpdated = properties.nativeGraphUpdated === true;
1796
+ const nativeGraphPutAppendChain =
1797
+ !nativeGraphUpdated &&
1798
+ properties.externalNextHashes &&
1799
+ this.properties.nativeGraph?.graph.putAppendChain
1800
+ ? this.properties.nativeGraph.graph.putAppendChain.bind(
1801
+ this.properties.nativeGraph.graph,
1802
+ )
1803
+ : undefined;
1804
+ const nativeGraphPutBatch =
1805
+ !nativeGraphUpdated && this.properties.nativeGraph?.graph.putBatch
1806
+ ? this.properties.nativeGraph.graph.putBatch.bind(
1807
+ this.properties.nativeGraph.graph,
1808
+ )
1809
+ : undefined;
1810
+ const deferBatchIndexWrite =
1811
+ properties.deferIndexWrite === true &&
1812
+ !!this.properties.nativeGraph &&
1813
+ entries.every((entry) => entry.meta.type !== EntryType.CUT);
1814
+ const lazyNativeGraphUpdatedDeferredIndexWrite =
1815
+ nativeGraphUpdated && deferBatchIndexWrite;
1816
+
1817
+ for (let i = 0; i < entries.length; i++) {
1818
+ const entry = entries[i]!;
1819
+ const isHead = properties.heads?.[i] ?? i === entries.length - 1;
1820
+ if (properties.unique === true || !(await this.has(entry.hash))) {
1821
+ this._length++;
1822
+ }
1823
+
1824
+ let shallowEntry: ShallowEntry | undefined;
1825
+ if (lazyNativeGraphUpdatedDeferredIndexWrite) {
1826
+ this.pendingIndexWrites.set(entry.hash, () =>
1827
+ materializePreparedAppendShallowEntry(entry, isHead),
1828
+ );
1829
+ } else {
1830
+ shallowEntry = materializePreparedAppendShallowEntry(entry, isHead);
1831
+ }
1832
+ const nativeEntry =
1833
+ !nativeGraphUpdated &&
1834
+ this.properties.nativeGraph &&
1835
+ (entry.nativeEntry ??
1836
+ toNativeLogEntry(
1837
+ shallowEntry ??
1838
+ materializePreparedAppendShallowEntry(entry, isHead),
1839
+ ));
1840
+ if (nativeEntry) {
1841
+ nativeEntry.head = isHead;
1842
+ }
1843
+ if (lazyNativeGraphUpdatedDeferredIndexWrite) {
1844
+ // The native commit already updated blocks/graph; keep the JS
1845
+ // compatibility index write lazy for later flushes.
1846
+ } else if (deferBatchIndexWrite || putBatch) {
1847
+ shallowEntries.push(shallowEntry!);
1848
+ nativeEntry && nativeEntries.push(nativeEntry);
1849
+ } else {
1850
+ await this.properties.index.put(shallowEntry!);
1851
+ nativeEntry && nativeEntries.push(nativeEntry);
1852
+ }
1853
+
1854
+ if (batchHashes) {
1855
+ for (const next of entry.meta.next) {
1856
+ if (!batchHashes.has(next)) {
1857
+ externalNexts.add(next);
1858
+ }
1859
+ }
1860
+ }
1861
+ }
1862
+ emitEntryIndexProfileDuration(profile, prepareStartedAt, {
1863
+ name: "log.entryIndex.putAppendFactsBatch.prepare",
1864
+ component: "log",
1865
+ entries: entries.length,
1866
+ messages: 1,
1867
+ details: {
1868
+ unique: properties.unique,
1869
+ nativeEntries: nativeEntries.length,
1870
+ discoverExternalNexts: shouldDiscoverExternalNexts,
1871
+ nativeGraphUpdated,
1872
+ },
1873
+ });
1874
+
1875
+ const putNativeEntries = (allowLoopFallback: boolean) => {
1876
+ if (nativeEntries.length === 0) {
1877
+ return;
1878
+ }
1879
+ if (nativeGraphPutAppendChain) {
1880
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1881
+ nativeGraphPutAppendChain(nativeEntries);
1882
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1883
+ name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
1884
+ component: "log",
1885
+ entries: nativeEntries.length,
1886
+ messages: 1,
1887
+ details: { method: "putAppendChain" },
1888
+ });
1889
+ } else if (nativeGraphPutBatch) {
1890
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1891
+ nativeGraphPutBatch(nativeEntries);
1892
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1893
+ name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
1894
+ component: "log",
1895
+ entries: nativeEntries.length,
1896
+ messages: 1,
1897
+ details: { method: "putBatch" },
1898
+ });
1899
+ } else if (allowLoopFallback) {
1900
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1901
+ for (const nativeEntry of nativeEntries) {
1902
+ this.properties.nativeGraph?.graph.put(nativeEntry);
1903
+ }
1904
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1905
+ name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
1906
+ component: "log",
1907
+ entries: nativeEntries.length,
1908
+ messages: 1,
1909
+ details: { method: "putLoop" },
1910
+ });
1911
+ }
1912
+ };
1913
+
1914
+ if (deferBatchIndexWrite) {
1915
+ const indexPutStartedAt = entryIndexProfileStart(profile);
1916
+ if (!lazyNativeGraphUpdatedDeferredIndexWrite) {
1917
+ for (const shallowEntry of shallowEntries) {
1918
+ this.pendingIndexWrites.set(shallowEntry.hash, shallowEntry);
1919
+ }
1920
+ }
1921
+ this.schedulePendingIndexWriteFlush();
1922
+ emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
1923
+ name: "log.entryIndex.putAppendFactsBatch.indexPut",
1924
+ component: "log",
1925
+ entries: lazyNativeGraphUpdatedDeferredIndexWrite
1926
+ ? entries.length
1927
+ : shallowEntries.length,
1928
+ messages: 1,
1929
+ details: {
1930
+ deferred: true,
1931
+ lazy: lazyNativeGraphUpdatedDeferredIndexWrite,
1932
+ },
1933
+ });
1934
+ putNativeEntries(true);
1935
+ } else if (putBatch) {
1936
+ const indexPutStartedAt = entryIndexProfileStart(profile);
1937
+ await putBatch.call(this.properties.index, shallowEntries);
1938
+ emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
1939
+ name: "log.entryIndex.putAppendFactsBatch.indexPut",
1940
+ component: "log",
1941
+ entries: shallowEntries.length,
1942
+ messages: 1,
1943
+ });
1944
+ putNativeEntries(true);
1945
+ } else if (nativeEntries.length > 0) {
1946
+ putNativeEntries(true);
1947
+ }
1948
+
1949
+ if (externalNexts.size > 0) {
1950
+ const externalNextStartedAt = entryIndexProfileStart(profile);
1951
+ await this.privateUpdateNextHeadHashes([...externalNexts], false);
1952
+ emitEntryIndexProfileDuration(profile, externalNextStartedAt, {
1953
+ name: "log.entryIndex.putAppendFactsBatch.externalNexts",
1954
+ component: "log",
1955
+ entries: externalNexts.size,
1956
+ messages: 1,
1957
+ });
1958
+ }
1959
+ })().finally(() => {
1960
+ for (const entry of entries) {
1961
+ this.insertionPromises.delete(entry.hash);
1962
+ }
1963
+ });
1964
+
1965
+ for (const entry of entries) {
1966
+ this.insertionPromises.set(entry.hash, promise);
1967
+ }
1968
+
1969
+ return promise;
1970
+ }
1971
+
1972
+ /** @internal */
1973
+ async putNativeCommittedAppend(
1974
+ entry: Entry<any>,
1975
+ properties: {
1976
+ unique: boolean;
1977
+ externalNextHashes: string[];
1978
+ shallowEntry?: ShallowEntry;
1979
+ isHead?: boolean;
1980
+ },
1981
+ ) {
1982
+ if (!entry.hash) {
1983
+ throw new Error("Missing hash");
1984
+ }
1985
+ const existingPromise = this.insertionPromises.get(entry.hash);
1986
+ if (existingPromise) {
1987
+ await existingPromise;
1988
+ }
1989
+
1990
+ const promise = (async () => {
1991
+ const isHead = properties.isHead ?? true;
1992
+ if (properties.unique === true || !(await this.has(entry.hash))) {
1993
+ this._length++;
1994
+ }
1995
+
1996
+ this.cache.add(entry.hash, entry);
1997
+ const shallowEntry =
1998
+ properties.shallowEntry ??
1999
+ Entry.takePreparedShallowEntry(entry, isHead) ??
2000
+ entry.toShallow(isHead);
2001
+ shallowEntry.head = isHead;
2002
+ this.pendingIndexWrites.set(entry.hash, shallowEntry);
2003
+ this.schedulePendingIndexWriteFlush();
2004
+
2005
+ if (properties.externalNextHashes.length > 0) {
2006
+ await this.privateUpdateNextHeadHashes(
2007
+ properties.externalNextHashes,
2008
+ false,
2009
+ );
2010
+ }
2011
+ })().finally(() => {
2012
+ this.insertionPromises.delete(entry.hash);
2013
+ });
2014
+
2015
+ this.insertionPromises.set(entry.hash, promise);
2016
+ return promise;
2017
+ }
2018
+
2019
+ /** @internal */
2020
+ putNativeCommittedAppendFacts(properties: {
2021
+ hash: string;
2022
+ unique: boolean;
2023
+ externalNextHashes: string[];
2024
+ shallowEntry?: ShallowEntry;
2025
+ getShallowEntry?: () => ShallowEntry;
2026
+ isHead?: boolean;
2027
+ }): Promise<void> | void {
2028
+ if (!properties.hash) {
2029
+ throw new Error("Missing hash");
2030
+ }
2031
+ const existingPromise = this.insertionPromises.get(properties.hash);
2032
+ if (
2033
+ !existingPromise &&
2034
+ properties.unique === true &&
2035
+ properties.externalNextHashes.length === 0
2036
+ ) {
2037
+ const isHead = properties.isHead ?? true;
2038
+ this._length++;
2039
+ if (!properties.shallowEntry && !properties.getShallowEntry) {
2040
+ throw new Error("Missing shallow entry");
2041
+ }
2042
+ const pending =
2043
+ properties.shallowEntry ??
2044
+ (() => {
2045
+ const shallowEntry = properties.getShallowEntry!();
2046
+ shallowEntry.head = isHead;
2047
+ return shallowEntry;
2048
+ });
2049
+ if (properties.shallowEntry) {
2050
+ properties.shallowEntry.head = isHead;
2051
+ }
2052
+ this.pendingIndexWrites.set(properties.hash, pending);
2053
+ this.schedulePendingIndexWriteFlush();
2054
+ return;
2055
+ }
2056
+
2057
+ return this.putNativeCommittedAppendFactsAsync(properties, existingPromise);
2058
+ }
2059
+
2060
+ /** @internal */
2061
+ putNativeCommittedAppendFactsBatch(
2062
+ rows: Array<{
2063
+ hash: string;
2064
+ unique: boolean;
2065
+ externalNextHashes: string[];
2066
+ shallowEntry?: ShallowEntry;
2067
+ getShallowEntry?: () => ShallowEntry;
2068
+ isHead?: boolean;
2069
+ }>,
2070
+ ): Promise<void> | void {
2071
+ if (rows.length === 0) {
2072
+ return;
2073
+ }
2074
+ const asyncRows: typeof rows = [];
2075
+ const externalNextHashes: string[] = [];
2076
+ let scheduledPendingFlush = false;
2077
+ for (const row of rows) {
2078
+ if (!row.hash) {
2079
+ throw new Error("Missing hash");
2080
+ }
2081
+ const existingPromise = this.insertionPromises.get(row.hash);
2082
+ if (!existingPromise && row.unique === true) {
2083
+ const isHead = row.isHead ?? true;
2084
+ this._length++;
2085
+ if (!row.shallowEntry && !row.getShallowEntry) {
2086
+ throw new Error("Missing shallow entry");
2087
+ }
2088
+ const pending =
2089
+ row.shallowEntry ??
2090
+ (() => {
2091
+ const shallowEntry = row.getShallowEntry!();
2092
+ shallowEntry.head = isHead;
2093
+ return shallowEntry;
2094
+ });
2095
+ if (row.shallowEntry) {
2096
+ row.shallowEntry.head = isHead;
2097
+ }
2098
+ this.pendingIndexWrites.set(row.hash, pending);
2099
+ if (row.externalNextHashes.length > 0) {
2100
+ externalNextHashes.push(...row.externalNextHashes);
2101
+ }
2102
+ scheduledPendingFlush = true;
2103
+ } else {
2104
+ asyncRows.push(row);
2105
+ }
2106
+ }
2107
+ if (scheduledPendingFlush) {
2108
+ this.schedulePendingIndexWriteFlush();
2109
+ }
2110
+ const batchedNextUpdate =
2111
+ externalNextHashes.length > 0
2112
+ ? this.privateUpdateNextHeadHashes(externalNextHashes, false)
2113
+ : undefined;
2114
+ if (asyncRows.length > 0 || batchedNextUpdate) {
2115
+ return Promise.all([
2116
+ batchedNextUpdate,
2117
+ ...asyncRows.map((row) => this.putNativeCommittedAppendFacts(row)),
2118
+ ]).then(() => undefined);
2119
+ }
2120
+ }
2121
+
2122
+ private async putNativeCommittedAppendFactsAsync(
2123
+ properties: {
2124
+ hash: string;
2125
+ unique: boolean;
2126
+ externalNextHashes: string[];
2127
+ shallowEntry?: ShallowEntry;
2128
+ getShallowEntry?: () => ShallowEntry;
2129
+ isHead?: boolean;
2130
+ },
2131
+ existingPromise?: Promise<void>,
2132
+ ) {
2133
+ if (existingPromise) {
2134
+ await existingPromise;
2135
+ }
2136
+ const promise = (async () => {
2137
+ const isHead = properties.isHead ?? true;
2138
+ if (properties.unique === true || !(await this.has(properties.hash))) {
2139
+ this._length++;
2140
+ }
2141
+
2142
+ if (!properties.shallowEntry && !properties.getShallowEntry) {
2143
+ throw new Error("Missing shallow entry");
2144
+ }
2145
+ const pending =
2146
+ properties.shallowEntry ??
2147
+ (() => {
2148
+ const shallowEntry = properties.getShallowEntry!();
2149
+ shallowEntry.head = isHead;
2150
+ return shallowEntry;
2151
+ });
2152
+ if (properties.shallowEntry) {
2153
+ properties.shallowEntry.head = isHead;
2154
+ }
2155
+ this.pendingIndexWrites.set(properties.hash, pending);
2156
+ this.schedulePendingIndexWriteFlush();
2157
+
2158
+ if (properties.externalNextHashes.length > 0) {
2159
+ await this.privateUpdateNextHeadHashes(
2160
+ properties.externalNextHashes,
2161
+ false,
2162
+ );
2163
+ }
2164
+ })().finally(() => {
2165
+ this.insertionPromises.delete(properties.hash);
2166
+ });
2167
+
2168
+ this.insertionPromises.set(properties.hash, promise);
2169
+ return promise;
2170
+ }
2171
+
654
2172
  async delete(k: string, from?: Entry<any> | ShallowEntry) {
655
2173
  this.cache.del(k);
656
2174
 
@@ -658,7 +2176,7 @@ export class EntryIndex<T> {
658
2176
  throw new Error("Shallow hash doesn't match the key");
659
2177
  }
660
2178
 
661
- const pending = this.pendingIndexWrites.get(k);
2179
+ const pending = this.getPendingIndexWrite(k);
662
2180
  from = from || pending || (await this.getShallow(k))?.value;
663
2181
  if (!from) {
664
2182
  return; // already deleted
@@ -685,14 +2203,523 @@ export class EntryIndex<T> {
685
2203
  }
686
2204
  }
687
2205
 
2206
+ canDeleteMany(): boolean {
2207
+ return (
2208
+ !!this.properties.nativeGraph?.graph.deleteMany ||
2209
+ hasRmMany(this.properties.store)
2210
+ );
2211
+ }
2212
+
2213
+ async deleteMany(
2214
+ from: ShallowEntry[],
2215
+ options?: { skipNextHeadUpdates?: boolean },
2216
+ ): Promise<ShallowEntry[]> {
2217
+ return this.deleteManyMaybe(from, options);
2218
+ }
2219
+
2220
+ deleteManyMaybe(
2221
+ from: ShallowEntry[],
2222
+ options?: { skipNextHeadUpdates?: boolean },
2223
+ ): MaybePromise<ShallowEntry[]> {
2224
+ if (from.length === 0) {
2225
+ return [];
2226
+ }
2227
+ if (from.length === 1) {
2228
+ return this.deleteSingleMaybe(from[0]!, options);
2229
+ }
2230
+ const nodes: ShallowEntry[] = [];
2231
+ const seen = new Set<string>();
2232
+ for (const node of from) {
2233
+ if (seen.has(node.hash)) {
2234
+ continue;
2235
+ }
2236
+ seen.add(node.hash);
2237
+ nodes.push(node);
2238
+ }
2239
+
2240
+ const indexedByHash = new Map(nodes.map((node) => [node.hash, node]));
2241
+ const deletedByHash = new Map<string, ShallowEntry>();
2242
+ const indexedHashes: string[] = [];
2243
+ const storeHashes: string[] = [];
2244
+
2245
+ for (const node of nodes) {
2246
+ this.cache.del(node.hash);
2247
+ const pending = this.getPendingIndexWrite(node.hash);
2248
+ if (pending) {
2249
+ this.pendingIndexWrites.delete(node.hash);
2250
+ deletedByHash.set(node.hash, pending);
2251
+ storeHashes.push(node.hash);
2252
+ } else {
2253
+ indexedHashes.push(node.hash);
2254
+ }
2255
+ }
2256
+
2257
+ const exactDeleteIndex: IndexWithExactDelete | undefined = hasExactDelete(
2258
+ this.properties.index,
2259
+ )
2260
+ ? (this.properties.index as IndexWithExactDelete)
2261
+ : undefined;
2262
+ if (indexedHashes.length > 0 && exactDeleteIndex) {
2263
+ return mapMaybePromise(
2264
+ exactDeleteIndex.delIds(indexedHashes),
2265
+ (deleted) => {
2266
+ for (const id of deleted) {
2267
+ const hash = String(id.primitive);
2268
+ const node = indexedByHash.get(hash);
2269
+ if (!node || deletedByHash.has(hash)) {
2270
+ continue;
2271
+ }
2272
+ deletedByHash.set(hash, node);
2273
+ storeHashes.push(hash);
2274
+ }
2275
+ return this.finishDeleteMany(
2276
+ deletedByHash,
2277
+ nodes,
2278
+ storeHashes,
2279
+ options,
2280
+ );
2281
+ },
2282
+ );
2283
+ }
2284
+ if (indexedHashes.length > 0) {
2285
+ return this.deleteManyByQuery(
2286
+ indexedHashes,
2287
+ indexedByHash,
2288
+ deletedByHash,
2289
+ nodes,
2290
+ storeHashes,
2291
+ options,
2292
+ );
2293
+ }
2294
+ return this.finishDeleteMany(deletedByHash, nodes, storeHashes, options);
2295
+ }
2296
+
2297
+ consumeNativeTrimmedEntriesMaybe(
2298
+ from: ShallowEntry[],
2299
+ options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
2300
+ ): MaybePromise<ShallowEntry[]> {
2301
+ if (from.length === 0) {
2302
+ return [];
2303
+ }
2304
+
2305
+ const nodes: ShallowEntry[] = [];
2306
+ const seen = new Set<string>();
2307
+ for (const node of from) {
2308
+ if (seen.has(node.hash)) {
2309
+ continue;
2310
+ }
2311
+ seen.add(node.hash);
2312
+ nodes.push(node);
2313
+ }
2314
+
2315
+ return this.consumeNativeTrimmedEntryNodesMaybe(nodes, options);
2316
+ }
2317
+
2318
+ consumeNativeTrimmedEntryHashesMaybe(
2319
+ hashes: string[],
2320
+ options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
2321
+ ): MaybePromise<ShallowEntry[]> {
2322
+ if (hashes.length === 0) {
2323
+ return [];
2324
+ }
2325
+
2326
+ const nodes: ShallowEntry[] = [];
2327
+ const seen = new Set<string>();
2328
+ for (const hash of hashes) {
2329
+ if (seen.has(hash)) {
2330
+ continue;
2331
+ }
2332
+ seen.add(hash);
2333
+ nodes.push(this.nativeTrimmedHashToShallowEntry(hash));
2334
+ }
2335
+
2336
+ return this.consumeNativeTrimmedEntryNodesMaybe(nodes, options);
2337
+ }
2338
+
2339
+ consumeNativeTrimmedEntryHashesNoReturnMaybe(
2340
+ hashes: string[],
2341
+ options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
2342
+ ): MaybePromise<boolean> | undefined {
2343
+ if (
2344
+ !options?.skipNextHeadUpdates ||
2345
+ options.deleteBlocks !== false ||
2346
+ hashes.length === 0
2347
+ ) {
2348
+ return undefined;
2349
+ }
2350
+
2351
+ const indexedHashes: string[] = [];
2352
+ let pendingDeleted = 0;
2353
+ const seen = new Set<string>();
2354
+ for (const hash of hashes) {
2355
+ if (seen.has(hash)) {
2356
+ continue;
2357
+ }
2358
+ seen.add(hash);
2359
+ this.cache.del(hash);
2360
+ if (this.pendingIndexWrites.delete(hash)) {
2361
+ pendingDeleted++;
2362
+ } else {
2363
+ indexedHashes.push(hash);
2364
+ }
2365
+ }
2366
+
2367
+ const finish = (indexedDeleted: number) => {
2368
+ this._length -= pendingDeleted + indexedDeleted;
2369
+ return true;
2370
+ };
2371
+ if (indexedHashes.length === 0) {
2372
+ return finish(0);
2373
+ }
2374
+ const exactDeleteCountIndex = hasExactDeleteCount(this.properties.index)
2375
+ ? this.properties.index
2376
+ : undefined;
2377
+ if (exactDeleteCountIndex) {
2378
+ return mapMaybePromise(
2379
+ exactDeleteCountIndex.delIdsCount(indexedHashes),
2380
+ finish,
2381
+ );
2382
+ }
2383
+ const exactDeleteIndex = hasExactDelete(this.properties.index)
2384
+ ? this.properties.index
2385
+ : undefined;
2386
+ if (exactDeleteIndex) {
2387
+ return mapMaybePromise(
2388
+ exactDeleteIndex.delIds(indexedHashes),
2389
+ (deleted) => finish(deleted.length),
2390
+ );
2391
+ }
2392
+ return this.consumeNativeTrimmedEntryHashesNoReturnByQuery(
2393
+ indexedHashes,
2394
+ finish,
2395
+ );
2396
+ }
2397
+
2398
+ private async consumeNativeTrimmedEntryHashesNoReturnByQuery(
2399
+ indexedHashes: string[],
2400
+ finish: (indexedDeleted: number) => boolean,
2401
+ ): Promise<boolean> {
2402
+ const batchSize = 64;
2403
+ let deletedCount = 0;
2404
+ for (let i = 0; i < indexedHashes.length; i += batchSize) {
2405
+ const hashes = indexedHashes.slice(i, i + batchSize);
2406
+ const deleted = await this.properties.index.del({
2407
+ query: createHashMatchQuery(hashes),
2408
+ });
2409
+ deletedCount += deleted.length;
2410
+ }
2411
+ return finish(deletedCount);
2412
+ }
2413
+
2414
+ private consumeNativeTrimmedEntryNodesMaybe(
2415
+ nodes: ShallowEntry[],
2416
+ options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
2417
+ ): MaybePromise<ShallowEntry[]> {
2418
+ const indexedByHash = new Map(nodes.map((node) => [node.hash, node]));
2419
+ const deletedByHash = new Map<string, ShallowEntry>();
2420
+ const indexedHashes: string[] = [];
2421
+ for (const node of nodes) {
2422
+ this.cache.del(node.hash);
2423
+ const pending = this.getPendingIndexWrite(node.hash);
2424
+ if (pending) {
2425
+ this.pendingIndexWrites.delete(node.hash);
2426
+ deletedByHash.set(node.hash, pending);
2427
+ } else {
2428
+ indexedHashes.push(node.hash);
2429
+ }
2430
+ }
2431
+
2432
+ const finish = (): MaybePromise<ShallowEntry[]> =>
2433
+ this.finishConsumeNativeTrimmedEntries(deletedByHash, nodes, options);
2434
+
2435
+ const exactDeleteIndex: IndexWithExactDelete | undefined = hasExactDelete(
2436
+ this.properties.index,
2437
+ )
2438
+ ? (this.properties.index as IndexWithExactDelete)
2439
+ : undefined;
2440
+ if (indexedHashes.length > 0 && exactDeleteIndex) {
2441
+ return mapMaybePromise(
2442
+ exactDeleteIndex.delIds(indexedHashes),
2443
+ (deleted) => {
2444
+ for (const id of deleted) {
2445
+ const hash = String(id.primitive);
2446
+ const node = indexedByHash.get(hash);
2447
+ if (!node || deletedByHash.has(hash)) {
2448
+ continue;
2449
+ }
2450
+ deletedByHash.set(hash, node);
2451
+ }
2452
+ return finish();
2453
+ },
2454
+ );
2455
+ }
2456
+ if (indexedHashes.length > 0) {
2457
+ return this.consumeNativeTrimmedEntriesByQuery(
2458
+ indexedHashes,
2459
+ indexedByHash,
2460
+ deletedByHash,
2461
+ nodes,
2462
+ options,
2463
+ );
2464
+ }
2465
+ return finish();
2466
+ }
2467
+
2468
+ private deleteSingleMaybe(
2469
+ node: ShallowEntry,
2470
+ options?: { skipNextHeadUpdates?: boolean },
2471
+ ): MaybePromise<ShallowEntry[]> {
2472
+ this.cache.del(node.hash);
2473
+ const pending = this.getPendingIndexWrite(node.hash);
2474
+ if (pending) {
2475
+ this.pendingIndexWrites.delete(node.hash);
2476
+ return this.finishDeleteSingle(pending, options);
2477
+ }
2478
+
2479
+ const exactDeleteIndex: IndexWithExactDelete | undefined = hasExactDelete(
2480
+ this.properties.index,
2481
+ )
2482
+ ? (this.properties.index as IndexWithExactDelete)
2483
+ : undefined;
2484
+ if (exactDeleteIndex) {
2485
+ return mapMaybePromise(exactDeleteIndex.delIds([node.hash]), (deleted) =>
2486
+ deleted.some((id) => String(id.primitive) === node.hash)
2487
+ ? this.finishDeleteSingle(node, options)
2488
+ : [],
2489
+ );
2490
+ }
2491
+ return mapMaybePromise(
2492
+ this.properties.index.del({ query: createHashMatchQuery([node.hash]) }),
2493
+ (deleted) =>
2494
+ deleted.some((id) => String(id.primitive) === node.hash)
2495
+ ? this.finishDeleteSingle(node, options)
2496
+ : [],
2497
+ );
2498
+ }
2499
+
2500
+ private finishDeleteSingle(
2501
+ node: ShallowEntry,
2502
+ options?: { skipNextHeadUpdates?: boolean },
2503
+ ): MaybePromise<ShallowEntry[]> {
2504
+ const afterStoreDelete = (): MaybePromise<ShallowEntry[]> => {
2505
+ this._length--;
2506
+ this.properties.nativeGraph?.graph.delete(node.hash);
2507
+ if (!options?.skipNextHeadUpdates && node.meta.type !== EntryType.CUT) {
2508
+ return mapMaybePromise(
2509
+ this.privateUpdateNextHeadHashes(node.meta.next, true),
2510
+ () => [node],
2511
+ );
2512
+ }
2513
+ return [node];
2514
+ };
2515
+ return mapMaybePromise(
2516
+ this.properties.store.rm(node.hash),
2517
+ afterStoreDelete,
2518
+ );
2519
+ }
2520
+
2521
+ private async deleteManyByQuery(
2522
+ indexedHashes: string[],
2523
+ indexedByHash: Map<string, ShallowEntry>,
2524
+ deletedByHash: Map<string, ShallowEntry>,
2525
+ nodes: ShallowEntry[],
2526
+ storeHashes: string[],
2527
+ options?: { skipNextHeadUpdates?: boolean },
2528
+ ): Promise<ShallowEntry[]> {
2529
+ const batchSize = 64;
2530
+ for (let i = 0; i < indexedHashes.length; i += batchSize) {
2531
+ const hashes = indexedHashes.slice(i, i + batchSize);
2532
+ const deleted = await this.properties.index.del({
2533
+ query: createHashMatchQuery(hashes),
2534
+ });
2535
+ for (const id of deleted) {
2536
+ const hash = String(id.primitive);
2537
+ const node = indexedByHash.get(hash);
2538
+ if (!node || deletedByHash.has(hash)) {
2539
+ continue;
2540
+ }
2541
+ deletedByHash.set(hash, node);
2542
+ storeHashes.push(hash);
2543
+ }
2544
+ }
2545
+ return this.finishDeleteMany(deletedByHash, nodes, storeHashes, options);
2546
+ }
2547
+
2548
+ private async consumeNativeTrimmedEntriesByQuery(
2549
+ indexedHashes: string[],
2550
+ indexedByHash: Map<string, ShallowEntry>,
2551
+ deletedByHash: Map<string, ShallowEntry>,
2552
+ nodes: ShallowEntry[],
2553
+ options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
2554
+ ): Promise<ShallowEntry[]> {
2555
+ const batchSize = 64;
2556
+ for (let i = 0; i < indexedHashes.length; i += batchSize) {
2557
+ const hashes = indexedHashes.slice(i, i + batchSize);
2558
+ const deleted = await this.properties.index.del({
2559
+ query: createHashMatchQuery(hashes),
2560
+ });
2561
+ for (const id of deleted) {
2562
+ const hash = String(id.primitive);
2563
+ const node = indexedByHash.get(hash);
2564
+ if (!node || deletedByHash.has(hash)) {
2565
+ continue;
2566
+ }
2567
+ deletedByHash.set(hash, node);
2568
+ }
2569
+ }
2570
+ return this.finishConsumeNativeTrimmedEntries(
2571
+ deletedByHash,
2572
+ nodes,
2573
+ options,
2574
+ );
2575
+ }
2576
+
2577
+ private finishConsumeNativeTrimmedEntries(
2578
+ deletedByHash: Map<string, ShallowEntry>,
2579
+ nodes: ShallowEntry[],
2580
+ options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
2581
+ ): MaybePromise<ShallowEntry[]> {
2582
+ const deleted = nodes
2583
+ .map((node) => deletedByHash.get(node.hash))
2584
+ .filter((node): node is ShallowEntry => !!node);
2585
+ if (deleted.length === 0) {
2586
+ return [];
2587
+ }
2588
+
2589
+ const afterStoreDelete = () => {
2590
+ this._length -= deleted.length;
2591
+ if (!options?.skipNextHeadUpdates) {
2592
+ const deletedNexts: string[] = [];
2593
+ for (const node of deleted) {
2594
+ if (node.meta.type !== EntryType.CUT) {
2595
+ deletedNexts.push(...node.meta.next);
2596
+ }
2597
+ }
2598
+ return mapMaybePromise(
2599
+ this.privateUpdateNextHeadHashes(deletedNexts, true),
2600
+ () => deleted,
2601
+ );
2602
+ }
2603
+ return deleted;
2604
+ };
2605
+
2606
+ if (options?.deleteBlocks) {
2607
+ const store = this.properties.store;
2608
+ const hashes = deleted.map((node) => node.hash);
2609
+ const deleteResult =
2610
+ hasRmMany(store) && store.rmMany
2611
+ ? store.rmMany(hashes)
2612
+ : Promise.all(hashes.map((hash) => store.rm(hash))).then(() => {});
2613
+ return mapMaybePromise(deleteResult, afterStoreDelete);
2614
+ }
2615
+ return afterStoreDelete();
2616
+ }
2617
+
2618
+ private finishDeleteMany(
2619
+ deletedByHash: Map<string, ShallowEntry>,
2620
+ nodes: ShallowEntry[],
2621
+ storeHashes: string[],
2622
+ options?: { skipNextHeadUpdates?: boolean },
2623
+ ): MaybePromise<ShallowEntry[]> {
2624
+ const deleted = nodes
2625
+ .map((node) => deletedByHash.get(node.hash))
2626
+ .filter((node): node is ShallowEntry => !!node);
2627
+ if (deleted.length === 0) {
2628
+ return [];
2629
+ }
2630
+
2631
+ const store = this.properties.store;
2632
+ const afterStoreDelete = () => {
2633
+ this._length -= deleted.length;
2634
+ const graph = this.properties.nativeGraph?.graph;
2635
+ if (graph?.deleteMany) {
2636
+ graph.deleteMany(storeHashes);
2637
+ } else {
2638
+ for (const hash of storeHashes) {
2639
+ graph?.delete(hash);
2640
+ }
2641
+ }
2642
+ if (!options?.skipNextHeadUpdates) {
2643
+ const deletedNexts: string[] = [];
2644
+ for (const node of deleted) {
2645
+ if (node.meta.type !== EntryType.CUT) {
2646
+ deletedNexts.push(...node.meta.next);
2647
+ }
2648
+ }
2649
+ return mapMaybePromise(
2650
+ this.privateUpdateNextHeadHashes(deletedNexts, true),
2651
+ () => deleted,
2652
+ );
2653
+ }
2654
+ return deleted;
2655
+ };
2656
+ if (hasRmMany(store) && store.rmMany) {
2657
+ return mapMaybePromise(store.rmMany(storeHashes), afterStoreDelete);
2658
+ }
2659
+ return Promise.all(storeHashes.map((hash) => store.rm(hash))).then(
2660
+ afterStoreDelete,
2661
+ );
2662
+ }
2663
+
2664
+ private nativeLogEntryToShallowEntry(entry: NativeLogEntry): ShallowEntry {
2665
+ return new ShallowEntry({
2666
+ hash: entry.hash,
2667
+ head: entry.head ?? false,
2668
+ payloadSize: entry.payloadSize ?? 0,
2669
+ meta: new ShallowMeta({
2670
+ gid: entry.gid,
2671
+ next: entry.next,
2672
+ type: entry.type,
2673
+ data: entry.data,
2674
+ clock: new Clock({
2675
+ id: this.properties.publicKey.bytes,
2676
+ timestamp: new Timestamp({
2677
+ wallTime: BigInt(entry.clock.timestamp.wallTime),
2678
+ logical: entry.clock.timestamp.logical ?? 0,
2679
+ }),
2680
+ }),
2681
+ }),
2682
+ });
2683
+ }
2684
+
2685
+ private nativeTrimmedHashToShallowEntry(hash: string): ShallowEntry {
2686
+ return new ShallowEntry({
2687
+ hash,
2688
+ head: false,
2689
+ payloadSize: 0,
2690
+ meta: new ShallowMeta({
2691
+ gid: "",
2692
+ next: [],
2693
+ type: EntryType.APPEND,
2694
+ clock: new Clock({
2695
+ id: this.properties.publicKey.bytes,
2696
+ timestamp: new Timestamp({
2697
+ wallTime: 0n,
2698
+ logical: 0,
2699
+ }),
2700
+ }),
2701
+ }),
2702
+ });
2703
+ }
2704
+
2705
+ nativeLogEntriesToShallowEntries(entries: NativeLogEntry[]): ShallowEntry[] {
2706
+ return entries.map((entry) => this.nativeLogEntryToShallowEntry(entry));
2707
+ }
2708
+
688
2709
  async getMemoryUsage() {
2710
+ if (this.properties.nativeGraph) {
2711
+ return this.properties.nativeGraph.graph.payloadSizeSum();
2712
+ }
689
2713
  const indexed =
690
2714
  (await this.properties.index.sum({ key: "payloadSize" })) || 0;
691
- const pending = [...this.pendingIndexWrites.values()].reduce(
692
- (sum, entry) => sum + (entry.payloadSize || 0),
693
- 0,
694
- );
695
- return typeof indexed === "bigint" ? indexed + BigInt(pending) : indexed + pending;
2715
+ let pending = 0;
2716
+ for (const [hash, write] of this.pendingIndexWrites) {
2717
+ pending +=
2718
+ this.materializePendingIndexWrite(hash, write).payloadSize || 0;
2719
+ }
2720
+ return typeof indexed === "bigint"
2721
+ ? indexed + BigInt(pending)
2722
+ : indexed + pending;
696
2723
  }
697
2724
 
698
2725
  private async privateUpdateNextHeadProperty(
@@ -704,8 +2731,23 @@ export class EntryIndex<T> {
704
2731
  return;
705
2732
  }
706
2733
 
707
- for (const next of from.meta.next) {
708
- const pending = this.pendingIndexWrites.get(next);
2734
+ await this.privateUpdateNextHeadHashes(from.meta.next, isHead);
2735
+ }
2736
+
2737
+ private async privateUpdateNextHeadHashes(nexts: string[], isHead: boolean) {
2738
+ const hashes = [...new Set(nexts.filter(Boolean))];
2739
+ if (hashes.length === 0) {
2740
+ return;
2741
+ }
2742
+ const existingNexts =
2743
+ isHead && this.properties.nativeGraph
2744
+ ? this.properties.nativeGraph.graph.hasMany(hashes)
2745
+ : undefined;
2746
+ for (const next of hashes) {
2747
+ const pending = this.getPendingIndexWrite(next);
2748
+ if (!pending && existingNexts && !existingNexts.has(next)) {
2749
+ continue;
2750
+ }
709
2751
  const indexedEntry = pending
710
2752
  ? { id: toId(next), value: pending }
711
2753
  : await this.properties.index.get(toId(next));
@@ -735,6 +2777,25 @@ export class EntryIndex<T> {
735
2777
  }
736
2778
  }
737
2779
 
2780
+ private async notifyShadowedGids(entry: Entry<any>) {
2781
+ if (!this.properties.onGidRemoved || entry.meta.next.length === 0) {
2782
+ return;
2783
+ }
2784
+ const shadowedGids: Set<string> = this.properties.nativeGraph
2785
+ ? new Set<string>(
2786
+ this.properties.nativeGraph.graph.shadowedGids(
2787
+ entry.meta.gid,
2788
+ entry.meta.next,
2789
+ entry.hash,
2790
+ ),
2791
+ )
2792
+ : await this.findShadowedGids(entry);
2793
+
2794
+ if (shadowedGids.size > 0) {
2795
+ this.properties.onGidRemoved([...shadowedGids]);
2796
+ }
2797
+ }
2798
+
738
2799
  async clear() {
739
2800
  this.clearPendingIndexFlushTimer();
740
2801
  const hashes = new Set<string>(this.pendingIndexWrites.keys());
@@ -862,7 +2923,63 @@ export class EntryIndex<T> {
862
2923
  }
863
2924
  return mem ? mem : undefined;
864
2925
  /* }
865
- return undefined; */
2926
+ return undefined; */
2927
+ }
2928
+
2929
+ private async resolveMany(
2930
+ hashes: string[],
2931
+ options?: ResolveFullyOptions,
2932
+ ): Promise<Array<Entry<T> | undefined>> {
2933
+ if (hashes.length === 0) {
2934
+ return [];
2935
+ }
2936
+ if (!canBatchResolveFromStore(this.properties.store, options)) {
2937
+ return Promise.all(hashes.map((hash) => this.resolve(hash, options)));
2938
+ }
2939
+
2940
+ const coercedOptions = typeof options === "object" ? options : undefined;
2941
+ const resolved: Array<Entry<T> | undefined> = new Array(hashes.length);
2942
+ const missingHashes: string[] = [];
2943
+ const missingPositions: number[] = [];
2944
+
2945
+ for (let i = 0; i < hashes.length; i++) {
2946
+ const hash = hashes[i]!;
2947
+ const mem = this.cache.get(hash);
2948
+ if (mem !== undefined) {
2949
+ resolved[i] = mem ? mem : undefined;
2950
+ continue;
2951
+ }
2952
+ missingHashes.push(hash);
2953
+ missingPositions.push(i);
2954
+ }
2955
+
2956
+ if (missingHashes.length === 0) {
2957
+ return resolved;
2958
+ }
2959
+
2960
+ const values = await this.properties.store.getMany!(
2961
+ missingHashes,
2962
+ withDefaultRemoteReadPriority(coercedOptions),
2963
+ );
2964
+ for (let i = 0; i < values.length; i++) {
2965
+ const hash = missingHashes[i]!;
2966
+ const value = values[i];
2967
+ if (!value) {
2968
+ if (coercedOptions?.ignoreMissing !== true) {
2969
+ throw new Error("Failed to load entry from head with hash: " + hash);
2970
+ }
2971
+ continue;
2972
+ }
2973
+
2974
+ const entry = deserialize(value, Entry) as Entry<T>;
2975
+ this.properties.init(entry);
2976
+ entry.hash = hash;
2977
+ entry.size = value.length;
2978
+ this.cache.add(hash, entry);
2979
+ resolved[missingPositions[i]!] = entry;
2980
+ }
2981
+
2982
+ return resolved;
866
2983
  }
867
2984
 
868
2985
  private async resolveFromStore(
@@ -922,6 +3039,26 @@ export class EntryIndex<T> {
922
3039
  }
923
3040
  }
924
3041
 
3042
+ const createHashMatchQuery = (hashes: string[]): Query =>
3043
+ hashes.length === 1
3044
+ ? new StringMatch({
3045
+ key: "hash",
3046
+ value: hashes[0]!,
3047
+ caseInsensitive: false,
3048
+ method: StringMatchMethod.exact,
3049
+ })
3050
+ : new Or(
3051
+ hashes.map(
3052
+ (hash) =>
3053
+ new StringMatch({
3054
+ key: "hash",
3055
+ value: hash,
3056
+ caseInsensitive: false,
3057
+ method: StringMatchMethod.exact,
3058
+ }),
3059
+ ),
3060
+ );
3061
+
925
3062
  const toNativeLogEntry = (entry: ShallowEntry): NativeLogEntry => ({
926
3063
  hash: entry.hash,
927
3064
  gid: entry.meta.gid,
@@ -929,6 +3066,7 @@ const toNativeLogEntry = (entry: ShallowEntry): NativeLogEntry => ({
929
3066
  type: entry.meta.type,
930
3067
  head: entry.head,
931
3068
  payloadSize: entry.payloadSize,
3069
+ data: entry.meta.data,
932
3070
  clock: {
933
3071
  timestamp: {
934
3072
  wallTime: entry.meta.clock.timestamp.wallTime,
@@ -937,6 +3075,32 @@ const toNativeLogEntry = (entry: ShallowEntry): NativeLogEntry => ({
937
3075
  },
938
3076
  });
939
3077
 
3078
+ const getResolveShape = (options: MaybeResolveOptions | undefined) =>
3079
+ options && options !== true && options.type === "shape"
3080
+ ? options.shape
3081
+ : undefined;
3082
+
3083
+ const isHeadHashOnlyShape = (shape: Shape) => {
3084
+ const keys = Object.keys(shape);
3085
+ return keys.length === 1 && shape.hash === true;
3086
+ };
3087
+
3088
+ const isHeadDataShape = (shape: Shape) => {
3089
+ const keys = Object.keys(shape);
3090
+ if (keys.some((key) => key !== "hash" && key !== "meta")) {
3091
+ return false;
3092
+ }
3093
+ if (shape.hash !== undefined && shape.hash !== true) {
3094
+ return false;
3095
+ }
3096
+ const meta = shape.meta as Shape | undefined;
3097
+ if (!meta || typeof meta !== "object") {
3098
+ return false;
3099
+ }
3100
+ const metaKeys = Object.keys(meta);
3101
+ return metaKeys.length === 1 && meta.data === true;
3102
+ };
3103
+
940
3104
  const projectShape = (value: any, shape: Shape): any => {
941
3105
  const out: any = {};
942
3106
  for (const [key, selector] of Object.entries(shape)) {