@peerbit/log 6.1.1 → 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
@@ -3,14 +3,49 @@ import {} from "@peerbit/blocks-interface";
3
3
  import { Cache } from "@peerbit/cache";
4
4
  import { BoolQuery, Not, Or, Sort, SortDirection, StringMatch, StringMatchMethod, toId, } from "@peerbit/indexer-interface";
5
5
  import { FOREGROUND_READ_MESSAGE_PRIORITY } from "@peerbit/stream-interface";
6
+ import { LamportClock as Clock, Timestamp } from "./clock.js";
7
+ import { ShallowEntry, ShallowMeta } from "./entry-shallow.js";
6
8
  import { EntryType } from "./entry-type.js";
7
- import { Entry } from "./entry.js";
9
+ import { Entry, } from "./entry.js";
8
10
  import { logger as baseLogger } from "./logger.js";
9
11
  const log = baseLogger.newScope("entry-index");
10
12
  const LOG_ENTRY_REMOTE_READ_PRIORITY = FOREGROUND_READ_MESSAGE_PRIORITY;
11
13
  const ENTRY_CACHE_MAX_SIZE = 10; // TODO as param for log
12
14
  const DEFERRED_INDEX_FLUSH_IDLE_MS = 250;
13
15
  const NATIVE_GRAPH_REBUILD_BATCH_SIZE = 512;
16
+ const TIMESTAMP_WALL_TIME_KEY = ["meta", "clock", "timestamp", "wallTime"];
17
+ const TIMESTAMP_LOGICAL_KEY = ["meta", "clock", "timestamp", "logical"];
18
+ const entryIndexProfileNow = () => globalThis.performance?.now?.() ?? Date.now();
19
+ const entryIndexProfileStart = (sink) => sink ? entryIndexProfileNow() : 0;
20
+ const emitEntryIndexProfileDuration = (sink, startedAt, event) => {
21
+ if (!sink) {
22
+ return;
23
+ }
24
+ sink({
25
+ ...event,
26
+ durationMs: entryIndexProfileNow() - startedAt,
27
+ });
28
+ };
29
+ const hasRmMany = (store) => typeof store.rmMany === "function";
30
+ const hasExactDelete = (index) => typeof index.delIds === "function";
31
+ const hasExactDeleteCount = (index) => typeof index.delIdsCount === "function";
32
+ const putPreparedEntryBlock = async (store, prepared) => {
33
+ const storeWithKnown = store;
34
+ return typeof storeWithKnown.putKnown === "function"
35
+ ? await storeWithKnown.putKnown(prepared.cid, prepared.block.bytes)
36
+ : await store.put(prepared);
37
+ };
38
+ const isPromiseLike = (value) => !!value && typeof value.then === "function";
39
+ const mapMaybePromise = (value, fn) => (isPromiseLike(value) ? value.then(fn) : fn(value));
40
+ const materializePreparedAppendShallowEntry = (entry, isHead) => {
41
+ const shallowEntry = entry.shallowEntry ?? entry.getShallowEntry?.(isHead);
42
+ if (!shallowEntry) {
43
+ throw new Error("Missing prepared append shallow entry");
44
+ }
45
+ shallowEntry.head = isHead;
46
+ entry.shallowEntry = shallowEntry;
47
+ return shallowEntry;
48
+ };
14
49
  const withDefaultRemoteReadPriority = (options) => {
15
50
  if (!options?.remote) {
16
51
  return options;
@@ -32,6 +67,27 @@ const withDefaultRemoteReadPriority = (options) => {
32
67
  },
33
68
  };
34
69
  };
70
+ const canBatchResolveFromStore = (store, options) => {
71
+ if (typeof store.getMany !== "function") {
72
+ return false;
73
+ }
74
+ return typeof options !== "object" || !options.remote;
75
+ };
76
+ const sortKeyEquals = (sort, key) => Array.isArray(sort?.key) &&
77
+ sort.key.length === key.length &&
78
+ sort.key.every((part, index) => part === key[index]);
79
+ const timestampSortDirection = (sort) => {
80
+ if (sort.length < 2) {
81
+ return;
82
+ }
83
+ const [wallTime, logical] = sort;
84
+ if (!sortKeyEquals(wallTime, TIMESTAMP_WALL_TIME_KEY) ||
85
+ !sortKeyEquals(logical, TIMESTAMP_LOGICAL_KEY) ||
86
+ wallTime.direction !== logical.direction) {
87
+ return;
88
+ }
89
+ return wallTime.direction;
90
+ };
35
91
  export class EntryIndex {
36
92
  properties;
37
93
  cache;
@@ -41,6 +97,7 @@ export class EntryIndex {
41
97
  insertionPromises;
42
98
  pendingIndexWrites;
43
99
  pendingIndexFlushTimer;
100
+ pendingIndexFlushLastWriteMs = 0;
44
101
  constructor(properties) {
45
102
  this.properties = properties;
46
103
  this.sortReversed = properties.sort.sort.map((x) => deserialize(serialize(x), Sort));
@@ -53,15 +110,39 @@ export class EntryIndex {
53
110
  this.insertionPromises = new Map();
54
111
  this.pendingIndexWrites = new Map();
55
112
  }
113
+ materializePendingIndexWrite(hash, pending) {
114
+ if (typeof pending !== "function") {
115
+ return pending;
116
+ }
117
+ const shallowEntry = pending();
118
+ this.pendingIndexWrites.set(hash, shallowEntry);
119
+ return shallowEntry;
120
+ }
121
+ getPendingIndexWrite(hash) {
122
+ const pending = this.pendingIndexWrites.get(hash);
123
+ return pending
124
+ ? this.materializePendingIndexWrite(hash, pending)
125
+ : undefined;
126
+ }
56
127
  schedulePendingIndexWriteFlush() {
128
+ this.pendingIndexFlushLastWriteMs = Date.now();
57
129
  if (this.pendingIndexFlushTimer) {
58
- clearTimeout(this.pendingIndexFlushTimer);
130
+ return;
59
131
  }
60
- this.pendingIndexFlushTimer = setTimeout(() => {
132
+ const flushAfterIdle = () => {
133
+ const remainingMs = DEFERRED_INDEX_FLUSH_IDLE_MS -
134
+ (Date.now() - this.pendingIndexFlushLastWriteMs);
135
+ if (remainingMs > 0) {
136
+ this.pendingIndexFlushTimer = setTimeout(flushAfterIdle, remainingMs);
137
+ this.pendingIndexFlushTimer.unref?.();
138
+ return;
139
+ }
140
+ this.pendingIndexFlushTimer = undefined;
61
141
  void this.flushPendingWrites().catch((error) => {
62
142
  log.error("Failed to flush deferred entry-index writes", error);
63
143
  });
64
- }, DEFERRED_INDEX_FLUSH_IDLE_MS);
144
+ };
145
+ this.pendingIndexFlushTimer = setTimeout(flushAfterIdle, DEFERRED_INDEX_FLUSH_IDLE_MS);
65
146
  this.pendingIndexFlushTimer.unref?.();
66
147
  }
67
148
  clearPendingIndexFlushTimer() {
@@ -79,13 +160,27 @@ export class EntryIndex {
79
160
  return;
80
161
  }
81
162
  this.clearPendingIndexFlushTimer();
163
+ const writes = [];
82
164
  for (const hash of keys) {
83
165
  const pending = this.pendingIndexWrites.get(hash);
84
166
  if (!pending) {
85
167
  continue;
86
168
  }
87
- await this.properties.index.put(pending);
88
- this.pendingIndexWrites.delete(hash);
169
+ writes.push(this.materializePendingIndexWrite(hash, pending));
170
+ }
171
+ if (writes.length === 0) {
172
+ return;
173
+ }
174
+ if (writes.length > 1 && this.properties.index.putBatch) {
175
+ await this.properties.index.putBatch(writes);
176
+ }
177
+ else {
178
+ for (const write of writes) {
179
+ await this.properties.index.put(write);
180
+ }
181
+ }
182
+ for (const write of writes) {
183
+ this.pendingIndexWrites.delete(write.hash);
89
184
  }
90
185
  if (this.pendingIndexWrites.size > 0) {
91
186
  this.schedulePendingIndexWriteFlush();
@@ -93,6 +188,15 @@ export class EntryIndex {
93
188
  }
94
189
  getHeads(gid, resolve = false) {
95
190
  if (this.properties.nativeGraph?.useHeads) {
191
+ const shape = getResolveShape(resolve);
192
+ if (shape && isHeadHashOnlyShape(shape)) {
193
+ return this.iterateNativeProjected(() => this.properties
194
+ .nativeGraph.graph.heads(gid)
195
+ .map((hash) => ({ hash })), shape);
196
+ }
197
+ if (shape && isHeadDataShape(shape)) {
198
+ return this.iterateNativeProjected(() => this.properties.nativeGraph.graph.headDataEntries(gid), shape);
199
+ }
96
200
  return this.iterateNativeHashes(() => this.properties.nativeGraph.graph.heads(gid), resolve);
97
201
  }
98
202
  const query = [];
@@ -107,6 +211,110 @@ export class EntryIndex {
107
211
  }
108
212
  return this.iterate(query, undefined, resolve);
109
213
  }
214
+ getHeadsForAppend(gid) {
215
+ if (!this.properties.nativeGraph?.useHeads) {
216
+ return undefined;
217
+ }
218
+ return this.properties.nativeGraph.graph.headEntries(gid);
219
+ }
220
+ async hasHead(gid) {
221
+ if (!this.properties.nativeGraph?.useHeads) {
222
+ return undefined;
223
+ }
224
+ return this.properties.nativeGraph.graph.hasHead(gid);
225
+ }
226
+ async hasAnyHead(gids) {
227
+ if (!this.properties.nativeGraph?.useHeads) {
228
+ return undefined;
229
+ }
230
+ const uniqueGids = new Set([...gids].filter(Boolean));
231
+ if (uniqueGids.size === 0) {
232
+ return false;
233
+ }
234
+ return this.properties.nativeGraph.graph.hasAnyHead(uniqueGids);
235
+ }
236
+ async hasAnyHeadBatch(gidSets) {
237
+ if (!this.properties.nativeGraph?.useHeads) {
238
+ return undefined;
239
+ }
240
+ const normalized = [...gidSets].map((gids) => new Set([...gids].filter(Boolean)));
241
+ if (normalized.length === 0) {
242
+ return [];
243
+ }
244
+ return this.properties.nativeGraph.graph.hasAnyHeadBatch(normalized);
245
+ }
246
+ async getMaxHeadDataU32(gid) {
247
+ if (!this.properties.nativeGraph?.useHeads) {
248
+ return undefined;
249
+ }
250
+ return this.properties.nativeGraph.graph.maxHeadDataU32(gid);
251
+ }
252
+ async getMaxHeadDataU32Batch(gids) {
253
+ if (!this.properties.nativeGraph?.useHeads) {
254
+ return undefined;
255
+ }
256
+ const normalized = [...gids];
257
+ if (normalized.length === 0) {
258
+ return [];
259
+ }
260
+ return this.properties.nativeGraph.graph.maxHeadDataU32Batch?.(normalized);
261
+ }
262
+ getJoinHeads(gid) {
263
+ if (this.properties.nativeGraph?.useHeads) {
264
+ return Promise.resolve(this.properties.nativeGraph.graph.joinHeadEntries(gid));
265
+ }
266
+ return this.getHeads(gid, {
267
+ type: "shape",
268
+ shape: {
269
+ hash: true,
270
+ meta: { type: true, next: true, gid: true, clock: true },
271
+ },
272
+ }).all();
273
+ }
274
+ getJoinChildren(next) {
275
+ if (this.properties.nativeGraph) {
276
+ return Promise.resolve(this.properties.nativeGraph.graph.childJoinEntries(next));
277
+ }
278
+ return this.getHasNext(next, {
279
+ type: "shape",
280
+ shape: {
281
+ hash: true,
282
+ meta: { type: true, next: true, gid: true, clock: true },
283
+ },
284
+ }).all();
285
+ }
286
+ getUniqueReferenceGids(hash) {
287
+ if (!this.properties.nativeGraph) {
288
+ return undefined;
289
+ }
290
+ return this.properties.nativeGraph.graph.uniqueReferenceGids(hash);
291
+ }
292
+ getUniqueReferenceGidRowsBatch(hashes) {
293
+ if (!this.properties.nativeGraph) {
294
+ return undefined;
295
+ }
296
+ const normalized = [...hashes];
297
+ if (normalized.length === 0) {
298
+ return [];
299
+ }
300
+ return this.properties.nativeGraph.graph.uniqueReferenceGidRowsBatch?.(normalized);
301
+ }
302
+ getUniqueReferenceGidRowsFlatBatch(hashes) {
303
+ if (!this.properties.nativeGraph) {
304
+ return undefined;
305
+ }
306
+ const normalized = [...hashes];
307
+ if (normalized.length === 0) {
308
+ return [];
309
+ }
310
+ return this.properties.nativeGraph.graph.uniqueReferenceGidRowsFlatBatch?.(normalized);
311
+ }
312
+ planDeleteRecursively(from, skipFirst = false) {
313
+ if (!this.properties.nativeGraph) {
314
+ return undefined;
315
+ }
316
+ return this.properties.nativeGraph.graph.planDeleteRecursively([...from].map((entry) => entry.hash), skipFirst);
317
+ }
110
318
  getHasNext(next, resolve) {
111
319
  const query = [
112
320
  new StringMatch({
@@ -122,10 +330,10 @@ export class EntryIndex {
122
330
  return this._countHasNext(next, excludeHash);
123
331
  }
124
332
  async _countHasNext(next, excludeHash = undefined) {
125
- await this.flushPendingWrites();
126
333
  if (this.properties.nativeGraph) {
127
334
  return this.properties.nativeGraph.graph.countHasNext(next, excludeHash);
128
335
  }
336
+ await this.flushPendingWrites();
129
337
  const query = [
130
338
  new StringMatch({
131
339
  key: ["meta", "next"],
@@ -161,13 +369,13 @@ export class EntryIndex {
161
369
  let complete = false;
162
370
  const getHashes = async () => {
163
371
  if (!hashPromise) {
164
- hashPromise = this.flushPendingWrites().then(() => hashes());
372
+ hashPromise = Promise.resolve(hashes());
165
373
  }
166
374
  return hashPromise;
167
375
  };
168
376
  const coerce = async (hashes) => {
169
377
  if (resolveInFull) {
170
- const resolved = await Promise.all(hashes.map((hash) => this.resolve(hash, resolveInFullOptions)));
378
+ const resolved = await this.resolveMany(hashes, resolveInFullOptions);
171
379
  return resolved.filter((entry) => !!entry);
172
380
  }
173
381
  const shallow = await Promise.all(hashes.map((hash) => this.getShallow(hash)));
@@ -194,6 +402,36 @@ export class EntryIndex {
194
402
  },
195
403
  };
196
404
  }
405
+ iterateNativeProjected(values, shape) {
406
+ let valuesPromise;
407
+ let offset = 0;
408
+ let complete = false;
409
+ const getValues = async () => {
410
+ if (!valuesPromise) {
411
+ valuesPromise = Promise.resolve(values());
412
+ }
413
+ return valuesPromise;
414
+ };
415
+ const coerce = (values) => values.map((value) => projectShape(value, shape));
416
+ return {
417
+ close: () => undefined,
418
+ done: () => complete,
419
+ next: async (amount) => {
420
+ const all = await getValues();
421
+ const batch = all.slice(offset, offset + amount);
422
+ offset += batch.length;
423
+ complete = offset >= all.length;
424
+ return coerce(batch);
425
+ },
426
+ all: async () => {
427
+ const all = await getValues();
428
+ const remaining = all.slice(offset);
429
+ offset = all.length;
430
+ complete = true;
431
+ return coerce(remaining);
432
+ },
433
+ };
434
+ }
197
435
  iterate(query, sort = this.properties.sort.sort, options) {
198
436
  let resolveInFull = options
199
437
  ? options === true
@@ -226,7 +464,7 @@ export class EntryIndex {
226
464
  };
227
465
  const coerce = async (results) => {
228
466
  if (resolveInFull) {
229
- const maybeResolved = await Promise.all(results.map((x) => this.resolve(x.value.hash, resolveInFullOptions)));
467
+ const maybeResolved = await this.resolveMany(results.map((x) => x.value.hash), resolveInFullOptions);
230
468
  return maybeResolved.filter((x) => !!x);
231
469
  }
232
470
  else {
@@ -241,17 +479,87 @@ export class EntryIndex {
241
479
  };
242
480
  }
243
481
  async getOldest(resolve) {
482
+ const nativeHash = this.getNativeTimestampOrderedHash("oldest");
483
+ if (nativeHash) {
484
+ const nativeResult = resolve
485
+ ? await this.resolve(nativeHash)
486
+ : (await this.getShallow(nativeHash))?.value;
487
+ if (nativeResult) {
488
+ return nativeResult;
489
+ }
490
+ }
244
491
  const iterator = this.iterate([], this.properties.sort.sort, resolve);
245
492
  const results = await iterator.next(1);
246
493
  await iterator.close();
247
494
  return results[0];
248
495
  }
496
+ async getOldestMany(limit, resolve) {
497
+ return this.getOldestManyMaybe(limit, resolve);
498
+ }
499
+ getOldestManyMaybe(limit, resolve) {
500
+ if (limit <= 0) {
501
+ return [];
502
+ }
503
+ const nativeEntries = this.getNativeOldestEntries(limit, resolve);
504
+ if (nativeEntries) {
505
+ return nativeEntries;
506
+ }
507
+ return this.getOldestManyFromIterator(limit, resolve);
508
+ }
509
+ async getOldestManyFromIterator(limit, resolve) {
510
+ const iterator = this.iterate([], this.properties.sort.sort, resolve);
511
+ try {
512
+ return (await iterator.next(limit));
513
+ }
514
+ finally {
515
+ await iterator.close();
516
+ }
517
+ }
518
+ getNativeOldestEntries(limit, resolve) {
519
+ if (resolve || limit <= 0) {
520
+ return;
521
+ }
522
+ const graph = this.properties.nativeGraph?.graph;
523
+ if (!graph?.oldestEntries) {
524
+ return;
525
+ }
526
+ const direction = timestampSortDirection(this.properties.sort.sort);
527
+ if (direction !== SortDirection.ASC) {
528
+ return;
529
+ }
530
+ return graph
531
+ .oldestEntries(limit)
532
+ .map((entry) => this.nativeLogEntryToShallowEntry(entry));
533
+ }
249
534
  async getNewest(resolve) {
535
+ const nativeHash = this.getNativeTimestampOrderedHash("newest");
536
+ if (nativeHash) {
537
+ const nativeResult = resolve
538
+ ? await this.resolve(nativeHash)
539
+ : (await this.getShallow(nativeHash))?.value;
540
+ if (nativeResult) {
541
+ return nativeResult;
542
+ }
543
+ }
250
544
  const iterator = this.iterate([], this.sortReversed, resolve);
251
545
  const results = await iterator.next(1);
252
546
  await iterator.close();
253
547
  return results[0];
254
548
  }
549
+ getNativeTimestampOrderedHash(position) {
550
+ const graph = this.properties.nativeGraph?.graph;
551
+ if (!graph?.oldestHash || !graph.newestHash) {
552
+ return;
553
+ }
554
+ const direction = timestampSortDirection(this.properties.sort.sort);
555
+ if (direction == null) {
556
+ return;
557
+ }
558
+ const useNewest = direction === SortDirection.ASC
559
+ ? position === "newest"
560
+ : position === "oldest";
561
+ return useNewest ? graph.newestHash() : graph.oldestHash();
562
+ }
255
563
  async getBefore(before, resolve) {
256
564
  const iterator = this.iterate(this.properties.sort.before(before), this.sortReversed, resolve);
257
565
  const results = await iterator.next(1);
@@ -267,17 +575,130 @@ export class EntryIndex {
267
575
  async get(k, options) {
268
576
  return this.resolve(k, options);
269
577
  }
578
+ async getMany(k, options) {
579
+ return this.resolveMany(k, options);
580
+ }
581
+ async planJoin(entry, reset) {
582
+ if (this.properties.nativeGraph) {
583
+ const cutCheck = this.properties.nativeGraph.useHeads
584
+ ? {
585
+ gid: entry.meta.gid,
586
+ wallTime: entry.meta.clock.timestamp.wallTime,
587
+ logical: entry.meta.clock.timestamp.logical,
588
+ }
589
+ : undefined;
590
+ return this.properties.nativeGraph.graph.planJoin(entry.hash, entry.meta.next, entry.meta.type, reset === true, cutCheck);
591
+ }
592
+ if (!reset && (await this.getShallow(entry.hash)) != null) {
593
+ return {
594
+ skip: true,
595
+ missingParents: [],
596
+ cutChecked: false,
597
+ coveredByCut: false,
598
+ };
599
+ }
600
+ if (entry.meta.type === EntryType.CUT) {
601
+ return {
602
+ skip: false,
603
+ missingParents: [],
604
+ cutChecked: false,
605
+ coveredByCut: false,
606
+ };
607
+ }
608
+ const missingParents = [];
609
+ for (const next of entry.meta.next) {
610
+ if (reset || (await this.getShallow(next)) == null) {
611
+ missingParents.push(next);
612
+ }
613
+ }
614
+ return {
615
+ skip: false,
616
+ missingParents,
617
+ cutChecked: false,
618
+ coveredByCut: false,
619
+ };
620
+ }
621
+ async planJoinBatch(entries, reset, profile) {
622
+ if (entries.length === 0) {
623
+ return [];
624
+ }
625
+ if (this.properties.nativeGraph?.graph.planJoinBatch) {
626
+ const prepareStartedAt = entryIndexProfileStart(profile);
627
+ const nativeInputs = entries.map((entry) => ({
628
+ hash: entry.hash,
629
+ next: entry.meta.next,
630
+ type: entry.meta.type,
631
+ cutCheck: this.properties.nativeGraph.useHeads
632
+ ? {
633
+ gid: entry.meta.gid,
634
+ wallTime: entry.meta.clock.timestamp.wallTime,
635
+ logical: entry.meta.clock.timestamp.logical,
636
+ }
637
+ : undefined,
638
+ }));
639
+ emitEntryIndexProfileDuration(profile, prepareStartedAt, {
640
+ name: "log.entryIndex.planJoinBatch.prepareNative",
641
+ component: "log",
642
+ entries: entries.length,
643
+ messages: 1,
644
+ details: {
645
+ cutChecks: nativeInputs.reduce((sum, input) => sum + (input.cutCheck ? 1 : 0), 0),
646
+ },
647
+ });
648
+ const nativeStartedAt = entryIndexProfileStart(profile);
649
+ const plans = this.properties.nativeGraph.graph.planJoinBatch(nativeInputs, reset === true);
650
+ emitEntryIndexProfileDuration(profile, nativeStartedAt, {
651
+ name: "log.entryIndex.planJoinBatch.nativeGraph",
652
+ component: "log",
653
+ entries: entries.length,
654
+ messages: 1,
655
+ });
656
+ return plans;
657
+ }
658
+ const fallbackStartedAt = entryIndexProfileStart(profile);
659
+ const plans = [];
660
+ for (const entry of entries) {
661
+ plans.push(await this.planJoin(entry, reset));
662
+ }
663
+ emitEntryIndexProfileDuration(profile, fallbackStartedAt, {
664
+ name: "log.entryIndex.planJoinBatch.fallback",
665
+ component: "log",
666
+ entries: entries.length,
667
+ messages: 1,
668
+ });
669
+ return plans;
670
+ }
270
671
  async getShallow(k) {
271
- const pending = this.pendingIndexWrites.get(k);
672
+ const pending = this.getPendingIndexWrite(k);
272
673
  if (pending) {
273
674
  return { id: toId(k), value: pending };
274
675
  }
275
676
  return this.properties.index.get(toId(k));
276
677
  }
678
+ getNativeEntryMetadataBatch(hashes) {
679
+ if (!this.properties.nativeGraph) {
680
+ return undefined;
681
+ }
682
+ const normalized = [...hashes];
683
+ if (normalized.length === 0) {
684
+ return [];
685
+ }
686
+ return this.properties.nativeGraph.graph.entryMetadataBatch?.(normalized);
687
+ }
688
+ getNativeEntryMetadataHintsBatch(hashes) {
689
+ if (!this.properties.nativeGraph) {
690
+ return undefined;
691
+ }
692
+ const normalized = [...hashes];
693
+ if (normalized.length === 0) {
694
+ return [];
695
+ }
696
+ return (this.properties.nativeGraph.graph.entryMetadataHintsBatch?.(normalized) ??
697
+ this.properties.nativeGraph.graph.entryMetadataBatch?.(normalized));
698
+ }
277
699
  async has(k) {
278
- let mem = this.cache.get(k);
279
- if (mem) {
280
- return true;
700
+ if (this.properties.nativeGraph) {
701
+ return this.properties.nativeGraph.graph.has(k);
281
702
  }
282
703
  if (this.pendingIndexWrites.has(k)) {
283
704
  return true;
@@ -288,11 +709,14 @@ export class EntryIndex {
288
709
  return result != null;
289
710
  }
290
711
  async hasMany(hashes) {
712
+ if (this.properties.nativeGraph) {
713
+ return this.properties.nativeGraph.graph.hasMany(new Set([...hashes].filter(Boolean)));
714
+ }
291
715
  const batchSize = 64;
292
716
  const existing = new Set();
293
717
  const missing = [];
294
718
  for (const hash of new Set([...hashes].filter(Boolean))) {
295
- if (this.cache.get(hash) || this.pendingIndexWrites.has(hash)) {
719
+ if (this.pendingIndexWrites.has(hash)) {
296
720
  existing.add(hash);
297
721
  continue;
298
722
  }
@@ -331,9 +755,17 @@ export class EntryIndex {
331
755
  async put(entry, properties) {
332
756
  if (properties.toMultiHash) {
333
757
  const existingHash = entry.hash;
334
- entry.hash = undefined;
758
+ const preparedBlock = Entry.takePreparedBlock(entry);
335
759
  try {
336
- const hash = await Entry.toMultihash(this.properties.store, entry);
760
+ let hash;
761
+ if (preparedBlock) {
762
+ hash = await putPreparedEntryBlock(this.properties.store, preparedBlock);
763
+ entry.size = preparedBlock.block.bytes.length;
764
+ }
765
+ else {
766
+ entry.hash = undefined;
767
+ hash = await Entry.toMultihash(this.properties.store, entry);
768
+ }
337
769
  entry.hash = existingHash;
338
770
  if (entry.hash === undefined) {
339
771
  entry.hash = hash; // can happen if you sync entries that you load directly from ipfs
@@ -364,7 +796,10 @@ export class EntryIndex {
364
796
  }
365
797
  // add cache after .has check before .has uses the cache
366
798
  this.cache.add(entry.hash, entry);
367
- const shallowEntry = entry.toShallow(properties.isHead);
799
+ const shallowEntry = Entry.takePreparedShallowEntry(entry, properties.isHead) ??
800
+ entry.toShallow(properties.isHead);
801
+ const nativeEntry = Entry.takePreparedNativeLogEntry(entry, properties.isHead) ??
802
+ toNativeLogEntry(shallowEntry);
368
803
  const shouldDeferIndexWrite = properties.deferIndexWrite === true &&
369
804
  properties.isHead &&
370
805
  entry.meta.type !== EntryType.CUT &&
@@ -377,16 +812,9 @@ export class EntryIndex {
377
812
  await this.flushPendingWrites(entry.meta.next);
378
813
  await this.properties.index.put(shallowEntry);
379
814
  }
380
- this.properties.nativeGraph?.graph.put(toNativeLogEntry(shallowEntry));
815
+ this.properties.nativeGraph?.graph.put(nativeEntry);
381
816
  // check if gids has been shadowed, by query all nexts that have a different gid
382
- if (this.properties.onGidRemoved && entry.meta.next.length > 0) {
383
- const shadowedGids = this.properties.nativeGraph
384
- ? new Set(this.properties.nativeGraph.graph.shadowedGids(entry.meta.gid, entry.meta.next, entry.hash))
385
- : await this.findShadowedGids(entry);
386
- if (shadowedGids.size > 0) {
387
- this.properties.onGidRemoved?.([...shadowedGids]);
388
- }
389
- }
817
+ await this.notifyShadowedGids(entry);
390
818
  // mark all next entries as not heads
391
819
  await this.privateUpdateNextHeadProperty(entry, false);
392
820
  this.insertionPromises.delete(entry.hash);
@@ -396,12 +824,560 @@ export class EntryIndex {
396
824
  return promise;
397
825
  }
398
826
  }
827
+ async putAppendBatch(entries, properties) {
828
+ if (entries.length === 0) {
829
+ return;
830
+ }
831
+ if (entries.length === 1 &&
832
+ properties.prepared?.nativeGraphUpdated !== true) {
833
+ return this.put(entries[0], {
834
+ unique: properties.unique,
835
+ isHead: true,
836
+ toMultiHash: false,
837
+ deferIndexWrite: properties.deferIndexWrite,
838
+ });
839
+ }
840
+ for (const entry of entries) {
841
+ if (!entry.hash) {
842
+ throw new Error("Missing hash");
843
+ }
844
+ const existingPromise = this.insertionPromises.get(entry.hash);
845
+ if (existingPromise) {
846
+ await existingPromise;
847
+ }
848
+ }
849
+ const promise = (async () => {
850
+ const profile = properties.profile;
851
+ const prepareStartedAt = entryIndexProfileStart(profile);
852
+ const externalNexts = new Set(properties.externalNextHashes ?? []);
853
+ const shouldDiscoverExternalNexts = !properties.externalNextHashes;
854
+ const batchHashes = shouldDiscoverExternalNexts
855
+ ? new Set(entries.map((entry) => entry.hash))
856
+ : undefined;
857
+ const putBatch = !this.properties.onGidRemoved && this.properties.index.putBatch;
858
+ const shallowEntries = [];
859
+ const nativeGraphUpdated = properties.prepared?.nativeGraphUpdated === true;
860
+ const nativeCommitOwnsHotIndex = nativeGraphUpdated &&
861
+ properties.prepared?.nativeBlocksCommitted === true &&
862
+ !this.properties.onGidRemoved;
863
+ const nativeGraphPutAppendChain = !nativeGraphUpdated &&
864
+ !this.properties.onGidRemoved &&
865
+ properties.externalNextHashes &&
866
+ this.properties.nativeGraph?.graph.putAppendChain
867
+ ? this.properties.nativeGraph.graph.putAppendChain.bind(this.properties.nativeGraph.graph)
868
+ : undefined;
869
+ const nativeGraphPutBatch = !nativeGraphUpdated &&
870
+ !this.properties.onGidRemoved &&
871
+ this.properties.nativeGraph?.graph.putBatch
872
+ ? this.properties.nativeGraph.graph.putBatch.bind(this.properties.nativeGraph.graph)
873
+ : undefined;
874
+ const deferBatchIndexWrite = properties.deferIndexWrite === true &&
875
+ !!putBatch &&
876
+ !!this.properties.nativeGraph &&
877
+ !this.properties.onGidRemoved &&
878
+ entries.every((entry) => entry.meta.type !== EntryType.CUT);
879
+ const nativeEntries = [];
880
+ for (let i = 0; i < entries.length; i++) {
881
+ const entry = entries[i];
882
+ const isHead = properties.heads?.[i] ?? i === entries.length - 1;
883
+ if (properties.unique === true || !(await this.has(entry.hash))) {
884
+ this._length++;
885
+ }
886
+ this.cache.add(entry.hash, entry);
887
+ const preparedShallowEntry = properties.prepared?.shallowEntries[i];
888
+ if (preparedShallowEntry) {
889
+ preparedShallowEntry.head = isHead;
890
+ }
891
+ const shallowEntry = preparedShallowEntry ??
892
+ Entry.takePreparedShallowEntry(entry, isHead) ??
893
+ entry.toShallow(isHead);
894
+ if (nativeCommitOwnsHotIndex) {
895
+ this.pendingIndexWrites.set(entry.hash, shallowEntry);
896
+ if (batchHashes) {
897
+ for (const next of entry.meta.next) {
898
+ if (!batchHashes.has(next)) {
899
+ externalNexts.add(next);
900
+ }
901
+ }
902
+ }
903
+ continue;
904
+ }
905
+ const preparedNativeEntry = properties.prepared?.nativeEntries?.[i];
906
+ if (preparedNativeEntry) {
907
+ preparedNativeEntry.head = isHead;
908
+ }
909
+ const nativeEntry = !nativeGraphUpdated &&
910
+ this.properties.nativeGraph &&
911
+ (preparedNativeEntry ??
912
+ Entry.takePreparedNativeLogEntry(entry, isHead) ??
913
+ toNativeLogEntry(shallowEntry));
914
+ if (putBatch) {
915
+ shallowEntries.push(shallowEntry);
916
+ nativeEntry && nativeEntries.push(nativeEntry);
917
+ }
918
+ else {
919
+ await this.properties.index.put(shallowEntry);
920
+ if (nativeGraphPutAppendChain || nativeGraphPutBatch) {
921
+ nativeEntry && nativeEntries.push(nativeEntry);
922
+ }
923
+ else {
924
+ if (nativeEntry) {
925
+ this.properties.nativeGraph?.graph.put(nativeEntry);
926
+ await this.notifyShadowedGids(entry);
927
+ }
928
+ }
929
+ }
930
+ if (batchHashes) {
931
+ for (const next of entry.meta.next) {
932
+ if (!batchHashes.has(next)) {
933
+ externalNexts.add(next);
934
+ }
935
+ }
936
+ }
937
+ }
938
+ emitEntryIndexProfileDuration(profile, prepareStartedAt, {
939
+ name: "log.entryIndex.putAppendBatch.prepare",
940
+ component: "log",
941
+ entries: entries.length,
942
+ messages: 1,
943
+ details: {
944
+ unique: properties.unique,
945
+ usedPreparedShallowEntries: properties.prepared?.shallowEntries.length ?? 0,
946
+ usedPreparedNativeEntries: properties.prepared?.nativeEntries?.length ?? 0,
947
+ nativeEntries: nativeEntries.length,
948
+ discoverExternalNexts: shouldDiscoverExternalNexts,
949
+ },
950
+ });
951
+ const putNativeEntries = (allowLoopFallback) => {
952
+ if (nativeEntries.length === 0) {
953
+ return;
954
+ }
955
+ if (nativeGraphPutAppendChain) {
956
+ const nativePutStartedAt = entryIndexProfileStart(profile);
957
+ nativeGraphPutAppendChain(nativeEntries);
958
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
959
+ name: "log.entryIndex.putAppendBatch.nativeGraphPut",
960
+ component: "log",
961
+ entries: nativeEntries.length,
962
+ messages: 1,
963
+ details: { method: "putAppendChain" },
964
+ });
965
+ }
966
+ else if (nativeGraphPutBatch) {
967
+ const nativePutStartedAt = entryIndexProfileStart(profile);
968
+ nativeGraphPutBatch(nativeEntries);
969
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
970
+ name: "log.entryIndex.putAppendBatch.nativeGraphPut",
971
+ component: "log",
972
+ entries: nativeEntries.length,
973
+ messages: 1,
974
+ details: { method: "putBatch" },
975
+ });
976
+ }
977
+ else if (allowLoopFallback) {
978
+ const nativePutStartedAt = entryIndexProfileStart(profile);
979
+ for (const nativeEntry of nativeEntries) {
980
+ this.properties.nativeGraph?.graph.put(nativeEntry);
981
+ }
982
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
983
+ name: "log.entryIndex.putAppendBatch.nativeGraphPut",
984
+ component: "log",
985
+ entries: nativeEntries.length,
986
+ messages: 1,
987
+ details: { method: "putLoop" },
988
+ });
989
+ }
990
+ };
991
+ if (nativeCommitOwnsHotIndex) {
992
+ this.schedulePendingIndexWriteFlush();
993
+ }
994
+ else if (deferBatchIndexWrite) {
995
+ const indexPutStartedAt = entryIndexProfileStart(profile);
996
+ for (const shallowEntry of shallowEntries) {
997
+ this.pendingIndexWrites.set(shallowEntry.hash, shallowEntry);
998
+ }
999
+ this.schedulePendingIndexWriteFlush();
1000
+ emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
1001
+ name: "log.entryIndex.putAppendBatch.indexPut",
1002
+ component: "log",
1003
+ entries: shallowEntries.length,
1004
+ messages: 1,
1005
+ details: { deferred: true },
1006
+ });
1007
+ putNativeEntries(true);
1008
+ }
1009
+ else if (putBatch) {
1010
+ const indexPutStartedAt = entryIndexProfileStart(profile);
1011
+ await putBatch.call(this.properties.index, shallowEntries);
1012
+ emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
1013
+ name: "log.entryIndex.putAppendBatch.indexPut",
1014
+ component: "log",
1015
+ entries: shallowEntries.length,
1016
+ messages: 1,
1017
+ });
1018
+ putNativeEntries(true);
1019
+ }
1020
+ else if (nativeEntries.length > 0) {
1021
+ putNativeEntries(false);
1022
+ }
1023
+ if (externalNexts.size > 0) {
1024
+ const externalNextStartedAt = entryIndexProfileStart(profile);
1025
+ await this.privateUpdateNextHeadHashes([...externalNexts], false);
1026
+ emitEntryIndexProfileDuration(profile, externalNextStartedAt, {
1027
+ name: "log.entryIndex.putAppendBatch.externalNexts",
1028
+ component: "log",
1029
+ entries: externalNexts.size,
1030
+ messages: 1,
1031
+ });
1032
+ }
1033
+ })().finally(() => {
1034
+ for (const entry of entries) {
1035
+ this.insertionPromises.delete(entry.hash);
1036
+ }
1037
+ });
1038
+ for (const entry of entries) {
1039
+ this.insertionPromises.set(entry.hash, promise);
1040
+ }
1041
+ return promise;
1042
+ }
1043
+ // Internal trusted receive path for callers that can supply prepared append facts.
1044
+ async putAppendFactsBatch(entries, properties) {
1045
+ if (entries.length === 0) {
1046
+ return;
1047
+ }
1048
+ if (this.properties.onGidRemoved) {
1049
+ throw new Error("Prepared append facts batch requires no onGidRemoved hook");
1050
+ }
1051
+ for (const entry of entries) {
1052
+ if (!entry.hash) {
1053
+ throw new Error("Missing hash");
1054
+ }
1055
+ const existingPromise = this.insertionPromises.get(entry.hash);
1056
+ if (existingPromise) {
1057
+ await existingPromise;
1058
+ }
1059
+ }
1060
+ const promise = (async () => {
1061
+ const profile = properties.profile;
1062
+ const prepareStartedAt = entryIndexProfileStart(profile);
1063
+ const externalNexts = new Set(properties.externalNextHashes ?? []);
1064
+ const shouldDiscoverExternalNexts = !properties.externalNextHashes;
1065
+ const batchHashes = shouldDiscoverExternalNexts
1066
+ ? new Set(entries.map((entry) => entry.hash))
1067
+ : undefined;
1068
+ const putBatch = this.properties.index.putBatch;
1069
+ const shallowEntries = [];
1070
+ const nativeEntries = [];
1071
+ const nativeGraphUpdated = properties.nativeGraphUpdated === true;
1072
+ const nativeGraphPutAppendChain = !nativeGraphUpdated &&
1073
+ properties.externalNextHashes &&
1074
+ this.properties.nativeGraph?.graph.putAppendChain
1075
+ ? this.properties.nativeGraph.graph.putAppendChain.bind(this.properties.nativeGraph.graph)
1076
+ : undefined;
1077
+ const nativeGraphPutBatch = !nativeGraphUpdated && this.properties.nativeGraph?.graph.putBatch
1078
+ ? this.properties.nativeGraph.graph.putBatch.bind(this.properties.nativeGraph.graph)
1079
+ : undefined;
1080
+ const deferBatchIndexWrite = properties.deferIndexWrite === true &&
1081
+ !!this.properties.nativeGraph &&
1082
+ entries.every((entry) => entry.meta.type !== EntryType.CUT);
1083
+ const lazyNativeGraphUpdatedDeferredIndexWrite = nativeGraphUpdated && deferBatchIndexWrite;
1084
+ for (let i = 0; i < entries.length; i++) {
1085
+ const entry = entries[i];
1086
+ const isHead = properties.heads?.[i] ?? i === entries.length - 1;
1087
+ if (properties.unique === true || !(await this.has(entry.hash))) {
1088
+ this._length++;
1089
+ }
1090
+ let shallowEntry;
1091
+ if (lazyNativeGraphUpdatedDeferredIndexWrite) {
1092
+ this.pendingIndexWrites.set(entry.hash, () => materializePreparedAppendShallowEntry(entry, isHead));
1093
+ }
1094
+ else {
1095
+ shallowEntry = materializePreparedAppendShallowEntry(entry, isHead);
1096
+ }
1097
+ const nativeEntry = !nativeGraphUpdated &&
1098
+ this.properties.nativeGraph &&
1099
+ (entry.nativeEntry ??
1100
+ toNativeLogEntry(shallowEntry ??
1101
+ materializePreparedAppendShallowEntry(entry, isHead)));
1102
+ if (nativeEntry) {
1103
+ nativeEntry.head = isHead;
1104
+ }
1105
+ if (lazyNativeGraphUpdatedDeferredIndexWrite) {
1106
+ // The native commit already updated blocks/graph; keep the JS
1107
+ // compatibility index write lazy for later flushes.
1108
+ }
1109
+ else if (deferBatchIndexWrite || putBatch) {
1110
+ shallowEntries.push(shallowEntry);
1111
+ nativeEntry && nativeEntries.push(nativeEntry);
1112
+ }
1113
+ else {
1114
+ await this.properties.index.put(shallowEntry);
1115
+ nativeEntry && nativeEntries.push(nativeEntry);
1116
+ }
1117
+ if (batchHashes) {
1118
+ for (const next of entry.meta.next) {
1119
+ if (!batchHashes.has(next)) {
1120
+ externalNexts.add(next);
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1125
+ emitEntryIndexProfileDuration(profile, prepareStartedAt, {
1126
+ name: "log.entryIndex.putAppendFactsBatch.prepare",
1127
+ component: "log",
1128
+ entries: entries.length,
1129
+ messages: 1,
1130
+ details: {
1131
+ unique: properties.unique,
1132
+ nativeEntries: nativeEntries.length,
1133
+ discoverExternalNexts: shouldDiscoverExternalNexts,
1134
+ nativeGraphUpdated,
1135
+ },
1136
+ });
1137
+ const putNativeEntries = (allowLoopFallback) => {
1138
+ if (nativeEntries.length === 0) {
1139
+ return;
1140
+ }
1141
+ if (nativeGraphPutAppendChain) {
1142
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1143
+ nativeGraphPutAppendChain(nativeEntries);
1144
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1145
+ name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
1146
+ component: "log",
1147
+ entries: nativeEntries.length,
1148
+ messages: 1,
1149
+ details: { method: "putAppendChain" },
1150
+ });
1151
+ }
1152
+ else if (nativeGraphPutBatch) {
1153
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1154
+ nativeGraphPutBatch(nativeEntries);
1155
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1156
+ name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
1157
+ component: "log",
1158
+ entries: nativeEntries.length,
1159
+ messages: 1,
1160
+ details: { method: "putBatch" },
1161
+ });
1162
+ }
1163
+ else if (allowLoopFallback) {
1164
+ const nativePutStartedAt = entryIndexProfileStart(profile);
1165
+ for (const nativeEntry of nativeEntries) {
1166
+ this.properties.nativeGraph?.graph.put(nativeEntry);
1167
+ }
1168
+ emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
1169
+ name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
1170
+ component: "log",
1171
+ entries: nativeEntries.length,
1172
+ messages: 1,
1173
+ details: { method: "putLoop" },
1174
+ });
1175
+ }
1176
+ };
1177
+ if (deferBatchIndexWrite) {
1178
+ const indexPutStartedAt = entryIndexProfileStart(profile);
1179
+ if (!lazyNativeGraphUpdatedDeferredIndexWrite) {
1180
+ for (const shallowEntry of shallowEntries) {
1181
+ this.pendingIndexWrites.set(shallowEntry.hash, shallowEntry);
1182
+ }
1183
+ }
1184
+ this.schedulePendingIndexWriteFlush();
1185
+ emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
1186
+ name: "log.entryIndex.putAppendFactsBatch.indexPut",
1187
+ component: "log",
1188
+ entries: lazyNativeGraphUpdatedDeferredIndexWrite
1189
+ ? entries.length
1190
+ : shallowEntries.length,
1191
+ messages: 1,
1192
+ details: {
1193
+ deferred: true,
1194
+ lazy: lazyNativeGraphUpdatedDeferredIndexWrite,
1195
+ },
1196
+ });
1197
+ putNativeEntries(true);
1198
+ }
1199
+ else if (putBatch) {
1200
+ const indexPutStartedAt = entryIndexProfileStart(profile);
1201
+ await putBatch.call(this.properties.index, shallowEntries);
1202
+ emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
1203
+ name: "log.entryIndex.putAppendFactsBatch.indexPut",
1204
+ component: "log",
1205
+ entries: shallowEntries.length,
1206
+ messages: 1,
1207
+ });
1208
+ putNativeEntries(true);
1209
+ }
1210
+ else if (nativeEntries.length > 0) {
1211
+ putNativeEntries(true);
1212
+ }
1213
+ if (externalNexts.size > 0) {
1214
+ const externalNextStartedAt = entryIndexProfileStart(profile);
1215
+ await this.privateUpdateNextHeadHashes([...externalNexts], false);
1216
+ emitEntryIndexProfileDuration(profile, externalNextStartedAt, {
1217
+ name: "log.entryIndex.putAppendFactsBatch.externalNexts",
1218
+ component: "log",
1219
+ entries: externalNexts.size,
1220
+ messages: 1,
1221
+ });
1222
+ }
1223
+ })().finally(() => {
1224
+ for (const entry of entries) {
1225
+ this.insertionPromises.delete(entry.hash);
1226
+ }
1227
+ });
1228
+ for (const entry of entries) {
1229
+ this.insertionPromises.set(entry.hash, promise);
1230
+ }
1231
+ return promise;
1232
+ }
1233
+ /** @internal */
1234
+ async putNativeCommittedAppend(entry, properties) {
1235
+ if (!entry.hash) {
1236
+ throw new Error("Missing hash");
1237
+ }
1238
+ const existingPromise = this.insertionPromises.get(entry.hash);
1239
+ if (existingPromise) {
1240
+ await existingPromise;
1241
+ }
1242
+ const promise = (async () => {
1243
+ const isHead = properties.isHead ?? true;
1244
+ if (properties.unique === true || !(await this.has(entry.hash))) {
1245
+ this._length++;
1246
+ }
1247
+ this.cache.add(entry.hash, entry);
1248
+ const shallowEntry = properties.shallowEntry ??
1249
+ Entry.takePreparedShallowEntry(entry, isHead) ??
1250
+ entry.toShallow(isHead);
1251
+ shallowEntry.head = isHead;
1252
+ this.pendingIndexWrites.set(entry.hash, shallowEntry);
1253
+ this.schedulePendingIndexWriteFlush();
1254
+ if (properties.externalNextHashes.length > 0) {
1255
+ await this.privateUpdateNextHeadHashes(properties.externalNextHashes, false);
1256
+ }
1257
+ })().finally(() => {
1258
+ this.insertionPromises.delete(entry.hash);
1259
+ });
1260
+ this.insertionPromises.set(entry.hash, promise);
1261
+ return promise;
1262
+ }
1263
+ /** @internal */
1264
+ putNativeCommittedAppendFacts(properties) {
1265
+ if (!properties.hash) {
1266
+ throw new Error("Missing hash");
1267
+ }
1268
+ const existingPromise = this.insertionPromises.get(properties.hash);
1269
+ if (!existingPromise &&
1270
+ properties.unique === true &&
1271
+ properties.externalNextHashes.length === 0) {
1272
+ const isHead = properties.isHead ?? true;
1273
+ this._length++;
1274
+ if (!properties.shallowEntry && !properties.getShallowEntry) {
1275
+ throw new Error("Missing shallow entry");
1276
+ }
1277
+ const pending = properties.shallowEntry ??
1278
+ (() => {
1279
+ const shallowEntry = properties.getShallowEntry();
1280
+ shallowEntry.head = isHead;
1281
+ return shallowEntry;
1282
+ });
1283
+ if (properties.shallowEntry) {
1284
+ properties.shallowEntry.head = isHead;
1285
+ }
1286
+ this.pendingIndexWrites.set(properties.hash, pending);
1287
+ this.schedulePendingIndexWriteFlush();
1288
+ return;
1289
+ }
1290
+ return this.putNativeCommittedAppendFactsAsync(properties, existingPromise);
1291
+ }
1292
+ /** @internal */
1293
+ putNativeCommittedAppendFactsBatch(rows) {
1294
+ if (rows.length === 0) {
1295
+ return;
1296
+ }
1297
+ const asyncRows = [];
1298
+ const externalNextHashes = [];
1299
+ let scheduledPendingFlush = false;
1300
+ for (const row of rows) {
1301
+ if (!row.hash) {
1302
+ throw new Error("Missing hash");
1303
+ }
1304
+ const existingPromise = this.insertionPromises.get(row.hash);
1305
+ if (!existingPromise && row.unique === true) {
1306
+ const isHead = row.isHead ?? true;
1307
+ this._length++;
1308
+ if (!row.shallowEntry && !row.getShallowEntry) {
1309
+ throw new Error("Missing shallow entry");
1310
+ }
1311
+ const pending = row.shallowEntry ??
1312
+ (() => {
1313
+ const shallowEntry = row.getShallowEntry();
1314
+ shallowEntry.head = isHead;
1315
+ return shallowEntry;
1316
+ });
1317
+ if (row.shallowEntry) {
1318
+ row.shallowEntry.head = isHead;
1319
+ }
1320
+ this.pendingIndexWrites.set(row.hash, pending);
1321
+ if (row.externalNextHashes.length > 0) {
1322
+ externalNextHashes.push(...row.externalNextHashes);
1323
+ }
1324
+ scheduledPendingFlush = true;
1325
+ }
1326
+ else {
1327
+ asyncRows.push(row);
1328
+ }
1329
+ }
1330
+ if (scheduledPendingFlush) {
1331
+ this.schedulePendingIndexWriteFlush();
1332
+ }
1333
+ const batchedNextUpdate = externalNextHashes.length > 0
1334
+ ? this.privateUpdateNextHeadHashes(externalNextHashes, false)
1335
+ : undefined;
1336
+ if (asyncRows.length > 0 || batchedNextUpdate) {
1337
+ return Promise.all([
1338
+ batchedNextUpdate,
1339
+ ...asyncRows.map((row) => this.putNativeCommittedAppendFacts(row)),
1340
+ ]).then(() => undefined);
1341
+ }
1342
+ }
1343
+ async putNativeCommittedAppendFactsAsync(properties, existingPromise) {
1344
+ if (existingPromise) {
1345
+ await existingPromise;
1346
+ }
1347
+ const promise = (async () => {
1348
+ const isHead = properties.isHead ?? true;
1349
+ if (properties.unique === true || !(await this.has(properties.hash))) {
1350
+ this._length++;
1351
+ }
1352
+ if (!properties.shallowEntry && !properties.getShallowEntry) {
1353
+ throw new Error("Missing shallow entry");
1354
+ }
1355
+ const pending = properties.shallowEntry ??
1356
+ (() => {
1357
+ const shallowEntry = properties.getShallowEntry();
1358
+ shallowEntry.head = isHead;
1359
+ return shallowEntry;
1360
+ });
1361
+ if (properties.shallowEntry) {
1362
+ properties.shallowEntry.head = isHead;
1363
+ }
1364
+ this.pendingIndexWrites.set(properties.hash, pending);
1365
+ this.schedulePendingIndexWriteFlush();
1366
+ if (properties.externalNextHashes.length > 0) {
1367
+ await this.privateUpdateNextHeadHashes(properties.externalNextHashes, false);
1368
+ }
1369
+ })().finally(() => {
1370
+ this.insertionPromises.delete(properties.hash);
1371
+ });
1372
+ this.insertionPromises.set(properties.hash, promise);
1373
+ return promise;
1374
+ }
399
1375
  async delete(k, from) {
400
1376
  this.cache.del(k);
401
1377
  if (from && from.hash !== k) {
402
1378
  throw new Error("Shallow hash doesn't match the key");
403
1379
  }
404
- const pending = this.pendingIndexWrites.get(k);
1380
+ const pending = this.getPendingIndexWrite(k);
405
1381
  from = from || pending || (await this.getShallow(k))?.value;
406
1382
  if (!from) {
407
1383
  return; // already deleted
@@ -424,18 +1400,397 @@ export class EntryIndex {
424
1400
  return from;
425
1401
  }
426
1402
  }
1403
+ canDeleteMany() {
1404
+ return (!!this.properties.nativeGraph?.graph.deleteMany ||
1405
+ hasRmMany(this.properties.store));
1406
+ }
1407
+ async deleteMany(from, options) {
1408
+ return this.deleteManyMaybe(from, options);
1409
+ }
1410
+ deleteManyMaybe(from, options) {
1411
+ if (from.length === 0) {
1412
+ return [];
1413
+ }
1414
+ if (from.length === 1) {
1415
+ return this.deleteSingleMaybe(from[0], options);
1416
+ }
1417
+ const nodes = [];
1418
+ const seen = new Set();
1419
+ for (const node of from) {
1420
+ if (seen.has(node.hash)) {
1421
+ continue;
1422
+ }
1423
+ seen.add(node.hash);
1424
+ nodes.push(node);
1425
+ }
1426
+ const indexedByHash = new Map(nodes.map((node) => [node.hash, node]));
1427
+ const deletedByHash = new Map();
1428
+ const indexedHashes = [];
1429
+ const storeHashes = [];
1430
+ for (const node of nodes) {
1431
+ this.cache.del(node.hash);
1432
+ const pending = this.getPendingIndexWrite(node.hash);
1433
+ if (pending) {
1434
+ this.pendingIndexWrites.delete(node.hash);
1435
+ deletedByHash.set(node.hash, pending);
1436
+ storeHashes.push(node.hash);
1437
+ }
1438
+ else {
1439
+ indexedHashes.push(node.hash);
1440
+ }
1441
+ }
1442
+ const exactDeleteIndex = hasExactDelete(this.properties.index)
1443
+ ? this.properties.index
1444
+ : undefined;
1445
+ if (indexedHashes.length > 0 && exactDeleteIndex) {
1446
+ return mapMaybePromise(exactDeleteIndex.delIds(indexedHashes), (deleted) => {
1447
+ for (const id of deleted) {
1448
+ const hash = String(id.primitive);
1449
+ const node = indexedByHash.get(hash);
1450
+ if (!node || deletedByHash.has(hash)) {
1451
+ continue;
1452
+ }
1453
+ deletedByHash.set(hash, node);
1454
+ storeHashes.push(hash);
1455
+ }
1456
+ return this.finishDeleteMany(deletedByHash, nodes, storeHashes, options);
1457
+ });
1458
+ }
1459
+ if (indexedHashes.length > 0) {
1460
+ return this.deleteManyByQuery(indexedHashes, indexedByHash, deletedByHash, nodes, storeHashes, options);
1461
+ }
1462
+ return this.finishDeleteMany(deletedByHash, nodes, storeHashes, options);
1463
+ }
1464
+ consumeNativeTrimmedEntriesMaybe(from, options) {
1465
+ if (from.length === 0) {
1466
+ return [];
1467
+ }
1468
+ const nodes = [];
1469
+ const seen = new Set();
1470
+ for (const node of from) {
1471
+ if (seen.has(node.hash)) {
1472
+ continue;
1473
+ }
1474
+ seen.add(node.hash);
1475
+ nodes.push(node);
1476
+ }
1477
+ return this.consumeNativeTrimmedEntryNodesMaybe(nodes, options);
1478
+ }
1479
+ consumeNativeTrimmedEntryHashesMaybe(hashes, options) {
1480
+ if (hashes.length === 0) {
1481
+ return [];
1482
+ }
1483
+ const nodes = [];
1484
+ const seen = new Set();
1485
+ for (const hash of hashes) {
1486
+ if (seen.has(hash)) {
1487
+ continue;
1488
+ }
1489
+ seen.add(hash);
1490
+ nodes.push(this.nativeTrimmedHashToShallowEntry(hash));
1491
+ }
1492
+ return this.consumeNativeTrimmedEntryNodesMaybe(nodes, options);
1493
+ }
1494
+ consumeNativeTrimmedEntryHashesNoReturnMaybe(hashes, options) {
1495
+ if (!options?.skipNextHeadUpdates ||
1496
+ options.deleteBlocks !== false ||
1497
+ hashes.length === 0) {
1498
+ return undefined;
1499
+ }
1500
+ const indexedHashes = [];
1501
+ let pendingDeleted = 0;
1502
+ const seen = new Set();
1503
+ for (const hash of hashes) {
1504
+ if (seen.has(hash)) {
1505
+ continue;
1506
+ }
1507
+ seen.add(hash);
1508
+ this.cache.del(hash);
1509
+ if (this.pendingIndexWrites.delete(hash)) {
1510
+ pendingDeleted++;
1511
+ }
1512
+ else {
1513
+ indexedHashes.push(hash);
1514
+ }
1515
+ }
1516
+ const finish = (indexedDeleted) => {
1517
+ this._length -= pendingDeleted + indexedDeleted;
1518
+ return true;
1519
+ };
1520
+ if (indexedHashes.length === 0) {
1521
+ return finish(0);
1522
+ }
1523
+ const exactDeleteCountIndex = hasExactDeleteCount(this.properties.index)
1524
+ ? this.properties.index
1525
+ : undefined;
1526
+ if (exactDeleteCountIndex) {
1527
+ return mapMaybePromise(exactDeleteCountIndex.delIdsCount(indexedHashes), finish);
1528
+ }
1529
+ const exactDeleteIndex = hasExactDelete(this.properties.index)
1530
+ ? this.properties.index
1531
+ : undefined;
1532
+ if (exactDeleteIndex) {
1533
+ return mapMaybePromise(exactDeleteIndex.delIds(indexedHashes), (deleted) => finish(deleted.length));
1534
+ }
1535
+ return this.consumeNativeTrimmedEntryHashesNoReturnByQuery(indexedHashes, finish);
1536
+ }
1537
+ async consumeNativeTrimmedEntryHashesNoReturnByQuery(indexedHashes, finish) {
1538
+ const batchSize = 64;
1539
+ let deletedCount = 0;
1540
+ for (let i = 0; i < indexedHashes.length; i += batchSize) {
1541
+ const hashes = indexedHashes.slice(i, i + batchSize);
1542
+ const deleted = await this.properties.index.del({
1543
+ query: createHashMatchQuery(hashes),
1544
+ });
1545
+ deletedCount += deleted.length;
1546
+ }
1547
+ return finish(deletedCount);
1548
+ }
1549
+ consumeNativeTrimmedEntryNodesMaybe(nodes, options) {
1550
+ const indexedByHash = new Map(nodes.map((node) => [node.hash, node]));
1551
+ const deletedByHash = new Map();
1552
+ const indexedHashes = [];
1553
+ for (const node of nodes) {
1554
+ this.cache.del(node.hash);
1555
+ const pending = this.getPendingIndexWrite(node.hash);
1556
+ if (pending) {
1557
+ this.pendingIndexWrites.delete(node.hash);
1558
+ deletedByHash.set(node.hash, pending);
1559
+ }
1560
+ else {
1561
+ indexedHashes.push(node.hash);
1562
+ }
1563
+ }
1564
+ const finish = () => this.finishConsumeNativeTrimmedEntries(deletedByHash, nodes, options);
1565
+ const exactDeleteIndex = hasExactDelete(this.properties.index)
1566
+ ? this.properties.index
1567
+ : undefined;
1568
+ if (indexedHashes.length > 0 && exactDeleteIndex) {
1569
+ return mapMaybePromise(exactDeleteIndex.delIds(indexedHashes), (deleted) => {
1570
+ for (const id of deleted) {
1571
+ const hash = String(id.primitive);
1572
+ const node = indexedByHash.get(hash);
1573
+ if (!node || deletedByHash.has(hash)) {
1574
+ continue;
1575
+ }
1576
+ deletedByHash.set(hash, node);
1577
+ }
1578
+ return finish();
1579
+ });
1580
+ }
1581
+ if (indexedHashes.length > 0) {
1582
+ return this.consumeNativeTrimmedEntriesByQuery(indexedHashes, indexedByHash, deletedByHash, nodes, options);
1583
+ }
1584
+ return finish();
1585
+ }
1586
+ deleteSingleMaybe(node, options) {
1587
+ this.cache.del(node.hash);
1588
+ const pending = this.getPendingIndexWrite(node.hash);
1589
+ if (pending) {
1590
+ this.pendingIndexWrites.delete(node.hash);
1591
+ return this.finishDeleteSingle(pending, options);
1592
+ }
1593
+ const exactDeleteIndex = hasExactDelete(this.properties.index)
1594
+ ? this.properties.index
1595
+ : undefined;
1596
+ if (exactDeleteIndex) {
1597
+ return mapMaybePromise(exactDeleteIndex.delIds([node.hash]), (deleted) => deleted.some((id) => String(id.primitive) === node.hash)
1598
+ ? this.finishDeleteSingle(node, options)
1599
+ : []);
1600
+ }
1601
+ return mapMaybePromise(this.properties.index.del({ query: createHashMatchQuery([node.hash]) }), (deleted) => deleted.some((id) => String(id.primitive) === node.hash)
1602
+ ? this.finishDeleteSingle(node, options)
1603
+ : []);
1604
+ }
1605
+ finishDeleteSingle(node, options) {
1606
+ const afterStoreDelete = () => {
1607
+ this._length--;
1608
+ this.properties.nativeGraph?.graph.delete(node.hash);
1609
+ if (!options?.skipNextHeadUpdates && node.meta.type !== EntryType.CUT) {
1610
+ return mapMaybePromise(this.privateUpdateNextHeadHashes(node.meta.next, true), () => [node]);
1611
+ }
1612
+ return [node];
1613
+ };
1614
+ return mapMaybePromise(this.properties.store.rm(node.hash), afterStoreDelete);
1615
+ }
1616
+ async deleteManyByQuery(indexedHashes, indexedByHash, deletedByHash, nodes, storeHashes, options) {
1617
+ const batchSize = 64;
1618
+ for (let i = 0; i < indexedHashes.length; i += batchSize) {
1619
+ const hashes = indexedHashes.slice(i, i + batchSize);
1620
+ const deleted = await this.properties.index.del({
1621
+ query: createHashMatchQuery(hashes),
1622
+ });
1623
+ for (const id of deleted) {
1624
+ const hash = String(id.primitive);
1625
+ const node = indexedByHash.get(hash);
1626
+ if (!node || deletedByHash.has(hash)) {
1627
+ continue;
1628
+ }
1629
+ deletedByHash.set(hash, node);
1630
+ storeHashes.push(hash);
1631
+ }
1632
+ }
1633
+ return this.finishDeleteMany(deletedByHash, nodes, storeHashes, options);
1634
+ }
1635
+ async consumeNativeTrimmedEntriesByQuery(indexedHashes, indexedByHash, deletedByHash, nodes, options) {
1636
+ const batchSize = 64;
1637
+ for (let i = 0; i < indexedHashes.length; i += batchSize) {
1638
+ const hashes = indexedHashes.slice(i, i + batchSize);
1639
+ const deleted = await this.properties.index.del({
1640
+ query: createHashMatchQuery(hashes),
1641
+ });
1642
+ for (const id of deleted) {
1643
+ const hash = String(id.primitive);
1644
+ const node = indexedByHash.get(hash);
1645
+ if (!node || deletedByHash.has(hash)) {
1646
+ continue;
1647
+ }
1648
+ deletedByHash.set(hash, node);
1649
+ }
1650
+ }
1651
+ return this.finishConsumeNativeTrimmedEntries(deletedByHash, nodes, options);
1652
+ }
1653
+ finishConsumeNativeTrimmedEntries(deletedByHash, nodes, options) {
1654
+ const deleted = nodes
1655
+ .map((node) => deletedByHash.get(node.hash))
1656
+ .filter((node) => !!node);
1657
+ if (deleted.length === 0) {
1658
+ return [];
1659
+ }
1660
+ const afterStoreDelete = () => {
1661
+ this._length -= deleted.length;
1662
+ if (!options?.skipNextHeadUpdates) {
1663
+ const deletedNexts = [];
1664
+ for (const node of deleted) {
1665
+ if (node.meta.type !== EntryType.CUT) {
1666
+ deletedNexts.push(...node.meta.next);
1667
+ }
1668
+ }
1669
+ return mapMaybePromise(this.privateUpdateNextHeadHashes(deletedNexts, true), () => deleted);
1670
+ }
1671
+ return deleted;
1672
+ };
1673
+ if (options?.deleteBlocks) {
1674
+ const store = this.properties.store;
1675
+ const hashes = deleted.map((node) => node.hash);
1676
+ const deleteResult = hasRmMany(store) && store.rmMany
1677
+ ? store.rmMany(hashes)
1678
+ : Promise.all(hashes.map((hash) => store.rm(hash))).then(() => { });
1679
+ return mapMaybePromise(deleteResult, afterStoreDelete);
1680
+ }
1681
+ return afterStoreDelete();
1682
+ }
1683
+ finishDeleteMany(deletedByHash, nodes, storeHashes, options) {
1684
+ const deleted = nodes
1685
+ .map((node) => deletedByHash.get(node.hash))
1686
+ .filter((node) => !!node);
1687
+ if (deleted.length === 0) {
1688
+ return [];
1689
+ }
1690
+ const store = this.properties.store;
1691
+ const afterStoreDelete = () => {
1692
+ this._length -= deleted.length;
1693
+ const graph = this.properties.nativeGraph?.graph;
1694
+ if (graph?.deleteMany) {
1695
+ graph.deleteMany(storeHashes);
1696
+ }
1697
+ else {
1698
+ for (const hash of storeHashes) {
1699
+ graph?.delete(hash);
1700
+ }
1701
+ }
1702
+ if (!options?.skipNextHeadUpdates) {
1703
+ const deletedNexts = [];
1704
+ for (const node of deleted) {
1705
+ if (node.meta.type !== EntryType.CUT) {
1706
+ deletedNexts.push(...node.meta.next);
1707
+ }
1708
+ }
1709
+ return mapMaybePromise(this.privateUpdateNextHeadHashes(deletedNexts, true), () => deleted);
1710
+ }
1711
+ return deleted;
1712
+ };
1713
+ if (hasRmMany(store) && store.rmMany) {
1714
+ return mapMaybePromise(store.rmMany(storeHashes), afterStoreDelete);
1715
+ }
1716
+ return Promise.all(storeHashes.map((hash) => store.rm(hash))).then(afterStoreDelete);
1717
+ }
1718
+ nativeLogEntryToShallowEntry(entry) {
1719
+ return new ShallowEntry({
1720
+ hash: entry.hash,
1721
+ head: entry.head ?? false,
1722
+ payloadSize: entry.payloadSize ?? 0,
1723
+ meta: new ShallowMeta({
1724
+ gid: entry.gid,
1725
+ next: entry.next,
1726
+ type: entry.type,
1727
+ data: entry.data,
1728
+ clock: new Clock({
1729
+ id: this.properties.publicKey.bytes,
1730
+ timestamp: new Timestamp({
1731
+ wallTime: BigInt(entry.clock.timestamp.wallTime),
1732
+ logical: entry.clock.timestamp.logical ?? 0,
1733
+ }),
1734
+ }),
1735
+ }),
1736
+ });
1737
+ }
1738
+ nativeTrimmedHashToShallowEntry(hash) {
1739
+ return new ShallowEntry({
1740
+ hash,
1741
+ head: false,
1742
+ payloadSize: 0,
1743
+ meta: new ShallowMeta({
1744
+ gid: "",
1745
+ next: [],
1746
+ type: EntryType.APPEND,
1747
+ clock: new Clock({
1748
+ id: this.properties.publicKey.bytes,
1749
+ timestamp: new Timestamp({
1750
+ wallTime: 0n,
1751
+ logical: 0,
1752
+ }),
1753
+ }),
1754
+ }),
1755
+ });
1756
+ }
1757
+ nativeLogEntriesToShallowEntries(entries) {
1758
+ return entries.map((entry) => this.nativeLogEntryToShallowEntry(entry));
1759
+ }
427
1760
  async getMemoryUsage() {
1761
+ if (this.properties.nativeGraph) {
1762
+ return this.properties.nativeGraph.graph.payloadSizeSum();
1763
+ }
428
1764
  const indexed = (await this.properties.index.sum({ key: "payloadSize" })) || 0;
429
- const pending = [...this.pendingIndexWrites.values()].reduce((sum, entry) => sum + (entry.payloadSize || 0), 0);
430
- return typeof indexed === "bigint" ? indexed + BigInt(pending) : indexed + pending;
1765
+ let pending = 0;
1766
+ for (const [hash, write] of this.pendingIndexWrites) {
1767
+ pending +=
1768
+ this.materializePendingIndexWrite(hash, write).payloadSize || 0;
1769
+ }
1770
+ return typeof indexed === "bigint"
1771
+ ? indexed + BigInt(pending)
1772
+ : indexed + pending;
431
1773
  }
432
1774
  async privateUpdateNextHeadProperty(from, isHead) {
433
1775
  if (from.meta.type === EntryType.CUT) {
434
1776
  // if the next is a cut, we can't update it, since it's not in the index
435
1777
  return;
436
1778
  }
437
- for (const next of from.meta.next) {
438
- const pending = this.pendingIndexWrites.get(next);
1779
+ await this.privateUpdateNextHeadHashes(from.meta.next, isHead);
1780
+ }
1781
+ async privateUpdateNextHeadHashes(nexts, isHead) {
1782
+ const hashes = [...new Set(nexts.filter(Boolean))];
1783
+ if (hashes.length === 0) {
1784
+ return;
1785
+ }
1786
+ const existingNexts = isHead && this.properties.nativeGraph
1787
+ ? this.properties.nativeGraph.graph.hasMany(hashes)
1788
+ : undefined;
1789
+ for (const next of hashes) {
1790
+ const pending = this.getPendingIndexWrite(next);
1791
+ if (!pending && existingNexts && !existingNexts.has(next)) {
1792
+ continue;
1793
+ }
439
1794
  const indexedEntry = pending
440
1795
  ? { id: toId(next), value: pending }
441
1796
  : await this.properties.index.get(toId(next));
@@ -465,6 +1820,17 @@ export class EntryIndex {
465
1820
  }
466
1821
  }
467
1822
  }
1823
+ async notifyShadowedGids(entry) {
1824
+ if (!this.properties.onGidRemoved || entry.meta.next.length === 0) {
1825
+ return;
1826
+ }
1827
+ const shadowedGids = this.properties.nativeGraph
1828
+ ? new Set(this.properties.nativeGraph.graph.shadowedGids(entry.meta.gid, entry.meta.next, entry.hash))
1829
+ : await this.findShadowedGids(entry);
1830
+ if (shadowedGids.size > 0) {
1831
+ this.properties.onGidRemoved([...shadowedGids]);
1832
+ }
1833
+ }
468
1834
  async clear() {
469
1835
  this.clearPendingIndexFlushTimer();
470
1836
  const hashes = new Set(this.pendingIndexWrites.keys());
@@ -571,7 +1937,50 @@ export class EntryIndex {
571
1937
  }
572
1938
  return mem ? mem : undefined;
573
1939
  /* }
574
- return undefined; */
1940
+ return undefined; */
1941
+ }
1942
+ async resolveMany(hashes, options) {
1943
+ if (hashes.length === 0) {
1944
+ return [];
1945
+ }
1946
+ if (!canBatchResolveFromStore(this.properties.store, options)) {
1947
+ return Promise.all(hashes.map((hash) => this.resolve(hash, options)));
1948
+ }
1949
+ const coercedOptions = typeof options === "object" ? options : undefined;
1950
+ const resolved = new Array(hashes.length);
1951
+ const missingHashes = [];
1952
+ const missingPositions = [];
1953
+ for (let i = 0; i < hashes.length; i++) {
1954
+ const hash = hashes[i];
1955
+ const mem = this.cache.get(hash);
1956
+ if (mem !== undefined) {
1957
+ resolved[i] = mem ? mem : undefined;
1958
+ continue;
1959
+ }
1960
+ missingHashes.push(hash);
1961
+ missingPositions.push(i);
1962
+ }
1963
+ if (missingHashes.length === 0) {
1964
+ return resolved;
1965
+ }
1966
+ const values = await this.properties.store.getMany(missingHashes, withDefaultRemoteReadPriority(coercedOptions));
1967
+ for (let i = 0; i < values.length; i++) {
1968
+ const hash = missingHashes[i];
1969
+ const value = values[i];
1970
+ if (!value) {
1971
+ if (coercedOptions?.ignoreMissing !== true) {
1972
+ throw new Error("Failed to load entry from head with hash: " + hash);
1973
+ }
1974
+ continue;
1975
+ }
1976
+ const entry = deserialize(value, Entry);
1977
+ this.properties.init(entry);
1978
+ entry.hash = hash;
1979
+ entry.size = value.length;
1980
+ this.cache.add(hash, entry);
1981
+ resolved[missingPositions[i]] = entry;
1982
+ }
1983
+ return resolved;
575
1984
  }
576
1985
  async resolveFromStore(k, options) {
577
1986
  let coercedOptions = options;
@@ -614,6 +2023,19 @@ export class EntryIndex {
614
2023
  return null;
615
2024
  }
616
2025
  }
2026
+ const createHashMatchQuery = (hashes) => hashes.length === 1
2027
+ ? new StringMatch({
2028
+ key: "hash",
2029
+ value: hashes[0],
2030
+ caseInsensitive: false,
2031
+ method: StringMatchMethod.exact,
2032
+ })
2033
+ : new Or(hashes.map((hash) => new StringMatch({
2034
+ key: "hash",
2035
+ value: hash,
2036
+ caseInsensitive: false,
2037
+ method: StringMatchMethod.exact,
2038
+ })));
617
2039
  const toNativeLogEntry = (entry) => ({
618
2040
  hash: entry.hash,
619
2041
  gid: entry.meta.gid,
@@ -621,6 +2043,7 @@ const toNativeLogEntry = (entry) => ({
621
2043
  type: entry.meta.type,
622
2044
  head: entry.head,
623
2045
  payloadSize: entry.payloadSize,
2046
+ data: entry.meta.data,
624
2047
  clock: {
625
2048
  timestamp: {
626
2049
  wallTime: entry.meta.clock.timestamp.wallTime,
@@ -628,6 +2051,28 @@ const toNativeLogEntry = (entry) => ({
628
2051
  },
629
2052
  },
630
2053
  });
2054
+ const getResolveShape = (options) => options && options !== true && options.type === "shape"
2055
+ ? options.shape
2056
+ : undefined;
2057
+ const isHeadHashOnlyShape = (shape) => {
2058
+ const keys = Object.keys(shape);
2059
+ return keys.length === 1 && shape.hash === true;
2060
+ };
2061
+ const isHeadDataShape = (shape) => {
2062
+ const keys = Object.keys(shape);
2063
+ if (keys.some((key) => key !== "hash" && key !== "meta")) {
2064
+ return false;
2065
+ }
2066
+ if (shape.hash !== undefined && shape.hash !== true) {
2067
+ return false;
2068
+ }
2069
+ const meta = shape.meta;
2070
+ if (!meta || typeof meta !== "object") {
2071
+ return false;
2072
+ }
2073
+ const metaKeys = Object.keys(meta);
2074
+ return metaKeys.length === 1 && meta.data === true;
2075
+ };
631
2076
  const projectShape = (value, shape) => {
632
2077
  const out = {};
633
2078
  for (const [key, selector] of Object.entries(shape)) {