@peerbit/document 13.1.4 → 13.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/document",
3
- "version": "13.1.4",
3
+ "version": "13.1.6",
4
4
  "description": "Document store implementation",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -61,18 +61,18 @@
61
61
  "uint8arrays": "^5.1.0",
62
62
  "@peerbit/cache": "3.1.0",
63
63
  "@peerbit/crypto": "3.1.2",
64
- "@peerbit/document-interface": "3.2.46",
65
- "@peerbit/indexer-cache": "0.2.8",
66
- "@peerbit/indexer-interface": "3.0.5",
67
- "@peerbit/indexer-simple": "1.2.8",
68
- "@peerbit/log": "6.2.3",
69
- "@peerbit/indexer-sqlite3": "3.0.8",
70
- "@peerbit/program": "6.0.33",
64
+ "@peerbit/document-interface": "3.2.48",
65
+ "@peerbit/indexer-interface": "3.0.6",
66
+ "@peerbit/indexer-cache": "0.2.9",
67
+ "@peerbit/indexer-simple": "1.2.10",
68
+ "@peerbit/log": "6.2.5",
69
+ "@peerbit/indexer-sqlite3": "3.0.9",
70
+ "@peerbit/logger": "2.0.1",
71
71
  "@peerbit/pubsub": "5.3.0",
72
- "@peerbit/shared-log": "13.2.4",
73
- "@peerbit/rpc": "6.1.1",
74
- "@peerbit/stream-interface": "6.0.11",
75
- "@peerbit/logger": "2.0.1"
72
+ "@peerbit/rpc": "6.1.3",
73
+ "@peerbit/program": "6.0.35",
74
+ "@peerbit/shared-log": "13.2.6",
75
+ "@peerbit/stream-interface": "6.0.11"
76
76
  },
77
77
  "optionalDependencies": {
78
78
  "@peerbit/document-rust": "0.1.1"
@@ -83,11 +83,11 @@
83
83
  "@types/pidusage": "^2.0.5",
84
84
  "pidusage": "^4.0.1",
85
85
  "uuid": "^10.0.0",
86
- "@peerbit/native-backbone": "0.1.3",
87
- "@peerbit/test-utils": "3.1.4",
88
86
  "@peerbit/log-rust": "1.1.2",
89
- "peerbit": "5.3.4",
90
- "@peerbit/time": "3.0.0"
87
+ "@peerbit/native-backbone": "0.1.4",
88
+ "@peerbit/test-utils": "3.1.6",
89
+ "@peerbit/time": "3.0.0",
90
+ "peerbit": "5.3.6"
91
91
  },
92
92
  "repository": {
93
93
  "type": "git",
@@ -102,6 +102,7 @@
102
102
  "clean": "aegir clean",
103
103
  "build": "aegir build --no-bundle",
104
104
  "test": "aegir test --target node",
105
+ "test:document-rust-core": "npm run build && PEERBIT_SHARED_LOG_RUST_CORE=1 aegir test -t node --grep '^(?!.*(uses the validated local append path|uses the independent native prepared batch path|uses native shared-log planning for replicated target-none puts|uses commit-only local puts when coordinate persistence is deferred)).*(native conformance guard |operations basic |operations get |operations index |operations search |iterate sort |count approximate |query distribution |returnIndexed |caching |custom index |prefetch )' -- --forbid-pending",
105
106
  "lint": "aegir lint",
106
107
  "test:cov": "aegir test -t node --cov"
107
108
  }
package/src/program.ts CHANGED
@@ -112,6 +112,16 @@ export class NativeDocumentModeError extends Error {
112
112
 
113
113
  type MaybePromise<T> = Promise<T> | T;
114
114
 
115
+ /**
116
+ * True when an error signals that an entry's payload bytes were never
117
+ * materialized on the JS side (a hollow entry backed by a native block store).
118
+ * `DecryptedThing.getValue` throws `Error("Missing data")` in that case. Used to
119
+ * decide whether the auto-mode append/delete read-back should recover the
120
+ * operation from the storage-bytes / block-store path instead.
121
+ */
122
+ const isMissingPayloadDataError = (error: unknown): boolean =>
123
+ error instanceof Error && error.message === "Missing data";
124
+
115
125
  const isPromiseLike = <T>(value: MaybePromise<T>): value is Promise<T> =>
116
126
  !!value && typeof (value as Promise<T>).then === "function";
117
127
 
@@ -2837,17 +2847,44 @@ export class Documents<
2837
2847
  return;
2838
2848
  }
2839
2849
  ensureInitialized?.();
2840
- return entry.getPayloadValue();
2850
+ try {
2851
+ return await entry.getPayloadValue();
2852
+ } catch (error) {
2853
+ // Auto (non-native document) mode with a native block store: the entry
2854
+ // materialized in the entry index can be a hollow shell whose in-memory
2855
+ // payload bytes were never loaded (the native store keeps the block
2856
+ // bytes at the storage layer, not on the JS entry). `getPayloadValue`
2857
+ // then throws "Missing data". The block itself is present, so recover
2858
+ // the operation via the storage-bytes / block-store read path. This is
2859
+ // a no-op for the pure-JS backend, where `getPayloadValue` succeeds.
2860
+ if (!isMissingPayloadDataError(error)) {
2861
+ throw error;
2862
+ }
2863
+ const operation = await this.getPlainEntryOperationFromStorage(entry);
2864
+ if (operation) {
2865
+ return operation;
2866
+ }
2867
+ throw error;
2868
+ }
2841
2869
  }
2842
2870
 
2843
2871
  private async getPlainEntryOperationFromStorage(
2844
2872
  entry: Entry<Operation>,
2845
2873
  ): Promise<Operation | undefined> {
2846
- let storageBytes: Uint8Array;
2874
+ let storageBytes: Uint8Array | undefined;
2847
2875
  try {
2848
2876
  storageBytes =
2849
2877
  Entry.getPreparedStorageBytes(entry) ?? entry.getStorageBytes();
2850
2878
  } catch {
2879
+ // The entry object is hollow (its payload bytes never materialized on
2880
+ // the JS side), so it cannot re-serialize itself. Fall through to the
2881
+ // block-store fallback below.
2882
+ storageBytes = undefined;
2883
+ }
2884
+ // Fall back to the raw block held by the block store, keyed by the entry
2885
+ // hash, whenever the entry could not (or did not) yield its own bytes.
2886
+ storageBytes ??= await this.getEntryStorageBytesFromBlocks(entry);
2887
+ if (!storageBytes) {
2851
2888
  return;
2852
2889
  }
2853
2890
  try {
@@ -2869,6 +2906,21 @@ export class Documents<
2869
2906
  }
2870
2907
  }
2871
2908
 
2909
+ private async getEntryStorageBytesFromBlocks(
2910
+ entry: Entry<Operation>,
2911
+ ): Promise<Uint8Array | undefined> {
2912
+ const hash = entry.hash;
2913
+ if (!hash) {
2914
+ return;
2915
+ }
2916
+ try {
2917
+ const bytes = await this.log.log.blocks.get(hash);
2918
+ return bytes ?? undefined;
2919
+ } catch {
2920
+ return;
2921
+ }
2922
+ }
2923
+
2872
2924
  private getNativeDocumentFieldExtractionPlan(
2873
2925
  path: string | readonly string[],
2874
2926
  ): SimpleDocumentFieldExtractionPlan | undefined {
@@ -11,6 +11,11 @@ import { logger as loggerFn } from "@peerbit/logger";
11
11
  const iteratorLogger = loggerFn("peerbit:document:index:iterate");
12
12
 
13
13
  export class ResumableIterators<T extends Record<string, any>> {
14
+ private readonly pendingMarks = new Map<
15
+ string,
16
+ Map<string, indexerTypes.IdKey>
17
+ >();
18
+
14
19
  constructor(
15
20
  readonly index: indexerTypes.Index<T>,
16
21
  readonly queues = new Cache<{
@@ -22,6 +27,36 @@ export class ResumableIterators<T extends Record<string, any>> {
22
27
  // TODO choose upper limit better
23
28
  }
24
29
 
30
+ private markKey(id: indexerTypes.IdKey) {
31
+ return `${id.key instanceof Uint8Array ? "bytes" : typeof id.key}:${String(id.primitive)}`;
32
+ }
33
+
34
+ private retainMarks(id: string, ids: Iterable<indexerTypes.IdKey>) {
35
+ let retained = this.pendingMarks.get(id);
36
+ for (const mark of ids) {
37
+ if (!retained) {
38
+ retained = new Map();
39
+ this.pendingMarks.set(id, retained);
40
+ }
41
+ retained.set(this.markKey(mark), mark);
42
+ }
43
+ }
44
+
45
+ private async applyPendingMarks(
46
+ id: string,
47
+ iterator: indexerTypes.IndexIterator<T, undefined>,
48
+ ) {
49
+ while (true) {
50
+ const retained = this.pendingMarks.get(id);
51
+ if (!retained?.size) {
52
+ this.pendingMarks.delete(id);
53
+ return;
54
+ }
55
+ this.pendingMarks.delete(id);
56
+ await iterator.markYielded?.(retained.values());
57
+ }
58
+ }
59
+
25
60
  async iterateAndFetch(
26
61
  request: SearchRequest | SearchRequestIndexed | IterationRequest,
27
62
  options?: { keepAlive?: boolean },
@@ -31,29 +66,32 @@ export class ResumableIterators<T extends Record<string, any>> {
31
66
  fetch: request.fetch,
32
67
  keepAlive: Boolean(options?.keepAlive),
33
68
  });
34
- const iterator = this.index.iterate(request);
35
- const firstResult = await iterator.next(request.fetch);
36
69
  const keepAlive = options?.keepAlive === true;
37
- if (keepAlive || iterator.done() !== true) {
38
- const cachedIterator = {
39
- iterator,
40
- request,
70
+ const iterator = this.index.iterate(request);
71
+ this.queues.add(request.idString, { iterator, request, keepAlive });
72
+ try {
73
+ await this.applyPendingMarks(request.idString, iterator);
74
+ const firstResult = await iterator.next(request.fetch);
75
+ if (!keepAlive && iterator.done() === true) {
76
+ this.queues.del(request.idString);
77
+ this.pendingMarks.delete(request.idString);
78
+ }
79
+ iteratorLogger("iterate:queued", {
80
+ id: request.idString,
41
81
  keepAlive,
42
- };
43
- this.queues.add(request.idString, cachedIterator);
82
+ done: iterator.done() === true,
83
+ batch: firstResult.length,
84
+ });
85
+ /* console.debug(
86
+ "[ResumableIterators] iterateAndFetch",
87
+ request.idString,
88
+ { keepAlive },
89
+ ); */
90
+ return firstResult;
91
+ } catch (error) {
92
+ this.clear(request.idString);
93
+ throw error;
44
94
  }
45
- iteratorLogger("iterate:queued", {
46
- id: request.idString,
47
- keepAlive,
48
- done: iterator.done() === true,
49
- batch: firstResult.length,
50
- });
51
- /* console.debug(
52
- "[ResumableIterators] iterateAndFetch",
53
- request.idString,
54
- { keepAlive },
55
- ); */
56
- return firstResult;
57
95
  }
58
96
 
59
97
  async next(
@@ -138,6 +176,7 @@ export class ResumableIterators<T extends Record<string, any>> {
138
176
  }
139
177
  }
140
178
  this.queues.del(id);
179
+ this.pendingMarks.delete(id);
141
180
  }
142
181
 
143
182
  async clearAll() {
@@ -155,6 +194,7 @@ export class ResumableIterators<T extends Record<string, any>> {
155
194
  ),
156
195
  );
157
196
  this.queues.clear();
197
+ this.pendingMarks.clear();
158
198
  }
159
199
 
160
200
  has(id: string) {
@@ -179,4 +219,12 @@ export class ResumableIterators<T extends Record<string, any>> {
179
219
  });
180
220
  return pending;
181
221
  }
222
+
223
+ async markYielded(id: string, ids: Iterable<indexerTypes.IdKey>) {
224
+ this.retainMarks(id, ids);
225
+ const iterator = this.queues.get(id);
226
+ if (iterator) {
227
+ await this.applyPendingMarks(id, iterator.iterator);
228
+ }
229
+ }
182
230
  }
package/src/search.ts CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  import { CachedIndex, type QueryCacheOptions } from "@peerbit/indexer-cache";
25
25
  import * as indexerTypes from "@peerbit/indexer-interface";
26
26
  import { HashmapIndex } from "@peerbit/indexer-simple";
27
- import { BORSH_ENCODING, type Encoding, Entry } from "@peerbit/log";
27
+ import { BORSH_ENCODING, type Encoding, type Entry } from "@peerbit/log";
28
28
  import { logger as loggerFn } from "@peerbit/logger";
29
29
  import { ClosedError, Program } from "@peerbit/program";
30
30
  import {
@@ -1179,13 +1179,98 @@ export class DocumentIndex<
1179
1179
  }
1180
1180
  }
1181
1181
 
1182
+ /**
1183
+ * Resolve documents without bypassing the resolver/program caches. Cache and
1184
+ * identity-index hits are collected first; only the remaining heads cross the
1185
+ * log read boundary, and those reads stay aligned in one batch.
1186
+ */
1187
+ private async resolveDocumentsWithBatchedHeads(
1188
+ values: Array<{
1189
+ id?: indexerTypes.IdPrimitive;
1190
+ indexed: I;
1191
+ head: string;
1192
+ }>,
1193
+ ): Promise<{
1194
+ resolved: Array<{ value: T } | undefined>;
1195
+ heads: Array<Entry<Operation> | undefined>;
1196
+ }> {
1197
+ const resolved: Array<{ value: T } | undefined> = new Array(values.length);
1198
+ const heads: Array<Entry<Operation> | undefined> = new Array(values.length);
1199
+ const unresolvedPositions: number[] = [];
1200
+
1201
+ for (let i = 0; i < values.length; i++) {
1202
+ const value = values[i]!;
1203
+ const cached = await this.resolveDocument({
1204
+ ...value,
1205
+ headEntry: null,
1206
+ });
1207
+ if (cached) {
1208
+ resolved[i] = cached;
1209
+ } else {
1210
+ unresolvedPositions.push(i);
1211
+ }
1212
+ }
1213
+
1214
+ if (unresolvedPositions.length === 0) {
1215
+ return { resolved, heads };
1216
+ }
1217
+
1218
+ const unresolvedHeads = await this._log.log.getMany(
1219
+ unresolvedPositions.map((position) => values[position]!.head),
1220
+ );
1221
+ for (let i = 0; i < unresolvedPositions.length; i++) {
1222
+ const position = unresolvedPositions[i]!;
1223
+ const head = unresolvedHeads[i];
1224
+ heads[position] = head;
1225
+ resolved[position] = await this.resolveDocument({
1226
+ ...values[position]!,
1227
+ headEntry: head ?? null,
1228
+ });
1229
+ }
1230
+
1231
+ return { resolved, heads };
1232
+ }
1233
+
1182
1234
  private async wrapPushResults(
1183
1235
  matches: Array<WithContext<T> | WithContext<I>>,
1184
1236
  resolve: boolean,
1185
1237
  ): Promise<types.Result[]> {
1186
1238
  if (!matches.length) return [];
1239
+ const headsByMatch: Array<Entry<Operation> | undefined> = [];
1240
+ const resolvedByMatch: Array<{ value: T } | undefined> = [];
1241
+ if (!resolve) {
1242
+ headsByMatch.push(
1243
+ ...(await this._log.log.getMany(
1244
+ matches.map((match) => match.__context.head),
1245
+ )),
1246
+ );
1247
+ } else {
1248
+ const indexedPositions: number[] = [];
1249
+ for (let i = 0; i < matches.length; i++) {
1250
+ if (!(matches[i] instanceof this.documentType)) {
1251
+ indexedPositions.push(i);
1252
+ }
1253
+ }
1254
+ const indexedBatch = indexedPositions.length
1255
+ ? await this.resolveDocumentsWithBatchedHeads(
1256
+ indexedPositions.map((position) => {
1257
+ const indexed = matches[position] as WithContext<I>;
1258
+ return {
1259
+ indexed,
1260
+ head: indexed.__context.head,
1261
+ };
1262
+ }),
1263
+ )
1264
+ : { resolved: [], heads: [] };
1265
+ for (let i = 0; i < indexedPositions.length; i++) {
1266
+ const position = indexedPositions[i]!;
1267
+ resolvedByMatch[position] = indexedBatch.resolved[i];
1268
+ headsByMatch[position] = indexedBatch.heads[i];
1269
+ }
1270
+ }
1187
1271
  const results: types.Result[] = [];
1188
- for (const match of matches) {
1272
+ for (let i = 0; i < matches.length; i++) {
1273
+ const match = matches[i]!;
1189
1274
  if (resolve) {
1190
1275
  if (match instanceof this.documentType) {
1191
1276
  const doc = match as WithContext<T>;
@@ -1203,10 +1288,7 @@ export class DocumentIndex<
1203
1288
  }
1204
1289
 
1205
1290
  const indexed = match as WithContext<I>;
1206
- const resolved = await this.resolveDocument({
1207
- indexed,
1208
- head: indexed.__context.head,
1209
- });
1291
+ const resolved = resolvedByMatch[i];
1210
1292
 
1211
1293
  if (resolved) {
1212
1294
  results.push(
@@ -1220,7 +1302,7 @@ export class DocumentIndex<
1220
1302
  continue;
1221
1303
  }
1222
1304
 
1223
- const head = await this._log.log.get(indexed.__context.head);
1305
+ const head = headsByMatch[i];
1224
1306
  results.push(
1225
1307
  new types.ResultIndexedValue({
1226
1308
  context: indexed.__context,
@@ -1231,7 +1313,7 @@ export class DocumentIndex<
1231
1313
  );
1232
1314
  } else {
1233
1315
  const indexed = match as WithContext<I>;
1234
- const head = await this._log.log.get(indexed.__context.head);
1316
+ const head = headsByMatch[i];
1235
1317
  results.push(
1236
1318
  new types.ResultIndexedValue({
1237
1319
  context: indexed.__context,
@@ -1253,17 +1335,28 @@ export class DocumentIndex<
1253
1335
  return [];
1254
1336
  }
1255
1337
  const drained = queueEntries.splice(0);
1338
+ const resolvedBatch = resolve
1339
+ ? await this.resolveDocumentsWithBatchedHeads(
1340
+ drained.map((entry) => ({
1341
+ indexed: entry.value,
1342
+ head: entry.value.__context.head,
1343
+ })),
1344
+ )
1345
+ : undefined;
1346
+ const heads = resolvedBatch
1347
+ ? resolvedBatch.heads
1348
+ : await this._log.log.getMany(
1349
+ drained.map((entry) => entry.value.__context.head),
1350
+ );
1256
1351
  const results: types.Result[] = [];
1257
- for (const entry of drained) {
1352
+ for (let i = 0; i < drained.length; i++) {
1353
+ const entry = drained[i]!;
1258
1354
  const indexedUnwrapped = Object.assign(
1259
1355
  Object.create(this.indexedType.prototype),
1260
1356
  entry.value,
1261
1357
  );
1262
1358
  if (resolve) {
1263
- const value = await this.resolveDocument({
1264
- indexed: entry.value,
1265
- head: entry.value.__context.head,
1266
- });
1359
+ const value = resolvedBatch!.resolved[i];
1267
1360
  if (value) {
1268
1361
  results.push(
1269
1362
  new types.ResultValue({
@@ -1276,7 +1369,7 @@ export class DocumentIndex<
1276
1369
  continue;
1277
1370
  }
1278
1371
 
1279
- const head = await this._log.log.get(entry.value.__context.head);
1372
+ const head = heads[i];
1280
1373
  results.push(
1281
1374
  new types.ResultIndexedValue({
1282
1375
  context: entry.value.__context,
@@ -1286,7 +1379,7 @@ export class DocumentIndex<
1286
1379
  }),
1287
1380
  );
1288
1381
  } else {
1289
- const head = await this._log.log.get(entry.value.__context.head);
1382
+ const head = heads[i];
1290
1383
  results.push(
1291
1384
  new types.ResultIndexedValue({
1292
1385
  context: entry.value.__context,
@@ -1336,6 +1429,7 @@ export class DocumentIndex<
1336
1429
  let pendingAdded = added;
1337
1430
  do {
1338
1431
  const batches: types.Result[] = [];
1432
+ let claimedIds: indexerTypes.IdKey[] = [];
1339
1433
  const queued = await this.drainQueuedResults(
1340
1434
  queue.queue,
1341
1435
  resolveFlag,
@@ -1357,6 +1451,21 @@ export class DocumentIndex<
1357
1451
  const wrapped = await this.wrapPushResults(matches, resolveFlag);
1358
1452
  if (wrapped.length) {
1359
1453
  batches.push(...wrapped);
1454
+ claimedIds = wrapped
1455
+ .map((result) =>
1456
+ result instanceof types.ResultValue && result.indexed
1457
+ ? indexerTypes.toId(
1458
+ this.indexByResolver(result.indexed),
1459
+ )
1460
+ : result instanceof types.ResultIndexedValue
1461
+ ? indexerTypes.toId(
1462
+ this.indexByResolver(result.value),
1463
+ )
1464
+ : undefined,
1465
+ )
1466
+ .filter(
1467
+ (id): id is indexerTypes.IdKey => id !== undefined,
1468
+ );
1360
1469
  }
1361
1470
  }
1362
1471
  if (batches.length) {
@@ -1374,6 +1483,12 @@ export class DocumentIndex<
1374
1483
  redundancy: 1,
1375
1484
  }),
1376
1485
  });
1486
+ if (claimedIds.length) {
1487
+ await this._resumableIterators.markYielded(
1488
+ _iteratorId,
1489
+ claimedIds,
1490
+ );
1491
+ }
1377
1492
  }
1378
1493
  pendingAdded = queue.pendingAdded ?? [];
1379
1494
  queue.pendingAdded = undefined;
@@ -3452,6 +3567,7 @@ export class DocumentIndex<
3452
3567
  id?: indexerTypes.IdPrimitive;
3453
3568
  indexed: I;
3454
3569
  head: string;
3570
+ headEntry?: Entry<Operation> | null;
3455
3571
  }): Promise<{ value: T } | undefined> {
3456
3572
  const id =
3457
3573
  value.id ??
@@ -3473,7 +3589,10 @@ export class DocumentIndex<
3473
3589
  return { value: obj as T };
3474
3590
  }
3475
3591
 
3476
- const head = await this._log.log.get(value.head);
3592
+ const head =
3593
+ value.headEntry === undefined
3594
+ ? await this._log.log.get(value.head)
3595
+ : (value.headEntry ?? undefined);
3477
3596
  if (!head) {
3478
3597
  return undefined; // we could end up here if we recently pruned the document and other peers never persisted the entry
3479
3598
  // TODO update changes in index before removing entries from log entry storage
@@ -3611,7 +3730,10 @@ export class DocumentIndex<
3611
3730
  this._resultQueue.set(query.idString, prevQueued);
3612
3731
  }
3613
3732
 
3614
- const filteredResults: types.Result[] = [];
3733
+ const candidates: Array<{
3734
+ result: indexerTypes.IndexedResult<WithContext<I>>;
3735
+ indexed: I;
3736
+ }> = [];
3615
3737
  const resolveDocumentsFlag = resolvesDocuments(fromQuery);
3616
3738
  const replicateIndexFlag = replicatesIndex(fromQuery);
3617
3739
  for (const result of toIterate) {
@@ -3633,11 +3755,29 @@ export class DocumentIndex<
3633
3755
  ) {
3634
3756
  continue;
3635
3757
  }
3758
+ candidates.push({ result, indexed: indexedUnwrapped });
3759
+ }
3760
+
3761
+ const resolvedBatch = resolveDocumentsFlag
3762
+ ? await this.resolveDocumentsWithBatchedHeads(
3763
+ candidates.map(({ result }) => ({
3764
+ indexed: result.value,
3765
+ head: result.value.__context.head,
3766
+ })),
3767
+ )
3768
+ : undefined;
3769
+ const heads = resolvedBatch
3770
+ ? resolvedBatch.heads
3771
+ : candidates.length > 0
3772
+ ? await this._log.log.getMany(
3773
+ candidates.map(({ result }) => result.value.__context.head),
3774
+ )
3775
+ : [];
3776
+ const filteredResults: types.Result[] = [];
3777
+ for (let i = 0; i < candidates.length; i++) {
3778
+ const { result, indexed: indexedUnwrapped } = candidates[i]!;
3636
3779
  if (resolveDocumentsFlag) {
3637
- const value = await this.resolveDocument({
3638
- indexed: result.value,
3639
- head: result.value.__context.head,
3640
- });
3780
+ const value = resolvedBatch!.resolved[i];
3641
3781
 
3642
3782
  if (!value) {
3643
3783
  continue;
@@ -3653,7 +3793,7 @@ export class DocumentIndex<
3653
3793
  );
3654
3794
  } else {
3655
3795
  const context = result.value.__context;
3656
- const head = await this._log.log.get(context.head);
3796
+ const head = heads[i];
3657
3797
  if (replicateIndexFlag) {
3658
3798
  if (!head) {
3659
3799
  continue;
@@ -6122,9 +6262,10 @@ export class DocumentIndex<
6122
6262
  continue;
6123
6263
  }
6124
6264
  }
6125
- const id = indexerTypes.toId(
6265
+ const indexId = indexerTypes.toId(
6126
6266
  this.indexByResolver(indexedCandidate),
6127
- ).primitive;
6267
+ );
6268
+ const id = indexId.primitive;
6128
6269
  const existingIndexed = indexedPlaceholders?.get(id);
6129
6270
  if (existingIndexed) {
6130
6271
  if (resolve) {
@@ -6165,6 +6306,10 @@ export class DocumentIndex<
6165
6306
  if (!resolve) {
6166
6307
  ensureIndexedPlaceholders().set(id, placeholder);
6167
6308
  }
6309
+ await this._resumableIterators.markYielded(
6310
+ queryRequestCoerced.idString,
6311
+ [indexId],
6312
+ );
6168
6313
  hasRelevantChange = true;
6169
6314
  }
6170
6315
  }