@peerbit/blocks 4.1.9 → 4.2.1

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.
@@ -2,6 +2,7 @@ import { createStore } from "@peerbit/any-store";
2
2
  import { type AnyStore } from "@peerbit/any-store-interface";
3
3
  import {
4
4
  type Blocks,
5
+ type GetOptions,
5
6
  calculateRawCid,
6
7
  cidifyString,
7
8
  codecCodes,
@@ -16,6 +17,27 @@ import { waitFor } from "@peerbit/time";
16
17
  import { type Block, decode } from "multiformats/block";
17
18
  import * as raw from "multiformats/codecs/raw";
18
19
 
20
+ const isPromiseLike = <T>(value: Promise<T> | T): value is Promise<T> =>
21
+ typeof (value as { then?: unknown })?.then === "function";
22
+
23
+ type ImmutableBlockStore = AnyStore & {
24
+ putImmutable?: (key: string, value: Uint8Array) => Promise<void> | void;
25
+ putManyImmutable?: (
26
+ entries: Iterable<readonly [string, Uint8Array]>,
27
+ ) => Promise<void> | void;
28
+ };
29
+
30
+ type NativeLogBlockStoreCarrier = {
31
+ getNativeLogBlockStoreHandle?: () => unknown;
32
+ getBlockResponsePayload?: (cid: string) => Uint8Array | undefined;
33
+ };
34
+
35
+ type BlockGetOptions = GetOptions & {
36
+ raw?: boolean;
37
+ links?: string[];
38
+ hasher?: any;
39
+ };
40
+
19
41
  export class AnyBlockStore implements Blocks {
20
42
  private _store: AnyStore;
21
43
  private _opening: Promise<any>;
@@ -25,42 +47,84 @@ export class AnyBlockStore implements Blocks {
25
47
  this._store = store;
26
48
  }
27
49
 
50
+ getNativeLogBlockStoreHandle(): unknown {
51
+ return (this._store as NativeLogBlockStoreCarrier)
52
+ .getNativeLogBlockStoreHandle?.();
53
+ }
54
+
55
+ getBlockResponsePayload(cid: string): Uint8Array | undefined {
56
+ return (this._store as NativeLogBlockStoreCarrier).getBlockResponsePayload?.(
57
+ cid,
58
+ );
59
+ }
60
+
61
+ private async decodeStoredBytes(
62
+ cid: string,
63
+ bytes: Uint8Array,
64
+ options?: BlockGetOptions,
65
+ ): Promise<Uint8Array> {
66
+ const cidObject = cidifyString(cid);
67
+ if (
68
+ cidObject.code === raw.code &&
69
+ (options?.hasher == null || options.hasher === defaultHasher)
70
+ ) {
71
+ return bytes;
72
+ }
73
+ const codec = codecCodes[cidObject.code as keyof typeof codecCodes];
74
+ const block = await decode({
75
+ bytes,
76
+ codec,
77
+ hasher: options?.hasher || defaultHasher,
78
+ });
79
+ return (block as Block<Uint8Array, any, any, any>).bytes;
80
+ }
81
+
28
82
  async get(
29
83
  cid: string,
30
- options?: {
31
- raw?: boolean;
32
- links?: string[];
33
- hasher?: any;
34
- remote: {
35
- timeout?: number;
36
- };
37
- },
84
+ options?: BlockGetOptions,
38
85
  ): Promise<Uint8Array | undefined> {
39
- const cidObject = cidifyString(cid);
40
86
  try {
41
87
  const bytes = await this._store.get(cid);
42
88
  if (!bytes) {
43
89
  return undefined;
44
90
  }
91
+ return this.decodeStoredBytes(cid, bytes, options);
92
+ } catch (error: any) {
45
93
  if (
46
- cidObject.code === raw.code &&
47
- (options?.hasher == null || options.hasher === defaultHasher)
94
+ typeof error?.code === "string" &&
95
+ error?.code?.indexOf("LEVEL_NOT_FOUND") !== -1
48
96
  ) {
49
- return bytes;
97
+ return undefined;
98
+ }
99
+ throw error;
100
+ }
101
+ }
102
+
103
+ async getMany(
104
+ cids: string[],
105
+ options?: BlockGetOptions,
106
+ ): Promise<Array<Uint8Array | undefined>> {
107
+ const store = this._store as AnyStore & {
108
+ getMany?: (keys: string[]) => Promise<Array<Uint8Array | undefined>>;
109
+ };
110
+ try {
111
+ if (typeof store.getMany === "function") {
112
+ const values = await store.getMany(cids);
113
+ return Promise.all(
114
+ values.map((bytes, index) =>
115
+ bytes
116
+ ? this.decodeStoredBytes(cids[index]!, bytes, options)
117
+ : undefined,
118
+ ),
119
+ );
50
120
  }
51
- const codec = (codecCodes as any)[cidObject.code];
52
- const block = await decode({
53
- bytes,
54
- codec,
55
- hasher: options?.hasher || defaultHasher,
56
- });
57
- return (block as Block<Uint8Array, any, any, any>).bytes;
121
+ return Promise.all(cids.map((cid) => this.get(cid, options)));
58
122
  } catch (error: any) {
59
123
  if (
60
124
  typeof error?.code === "string" &&
61
125
  error?.code?.indexOf("LEVEL_NOT_FOUND") !== -1
62
126
  ) {
63
- return undefined;
127
+ return Promise.all(cids.map((cid) => this.get(cid, options)));
64
128
  }
65
129
  throw error;
66
130
  }
@@ -75,13 +139,7 @@ export class AnyBlockStore implements Blocks {
75
139
  try {
76
140
  await this._store.put(put.cid, bbytes);
77
141
  } catch (error: any) {
78
- const status = await this._store.status();
79
- if (
80
- typeof error?.code === "string" &&
81
- error.code === "LEVEL_DATABASE_NOT_OPEN" &&
82
- this._closeController?.signal.aborted === true &&
83
- (status === "closing" || status === "closed")
84
- ) {
142
+ if (await this.isClosingStorePutError(error)) {
85
143
  // Late replication writes can outlive shutdown. At this point the
86
144
  // backing store is intentionally closing, so report the deterministic
87
145
  // CID while discarding the write instead of leaking an unhandled
@@ -93,10 +151,140 @@ export class AnyBlockStore implements Blocks {
93
151
  return put.cid;
94
152
  }
95
153
 
154
+ async putMany(
155
+ blocks: Array<Uint8Array | { block: Block<any, any, any, any>; cid: string }>,
156
+ ): Promise<string[]> {
157
+ const puts = await Promise.all(
158
+ blocks.map((bytes) =>
159
+ bytes instanceof Uint8Array ? calculateRawCid(bytes) : bytes,
160
+ ),
161
+ );
162
+ const store = this._store as AnyStore & {
163
+ putMany?: (
164
+ entries: Iterable<readonly [string, Uint8Array]>,
165
+ ) => Promise<void> | void;
166
+ };
167
+ try {
168
+ if (puts.length === 1) {
169
+ const put = puts[0]!;
170
+ const result = this.putKnown(put.cid, put.block.bytes);
171
+ if (isPromiseLike(result)) {
172
+ await result;
173
+ }
174
+ } else if (typeof store.putMany === "function") {
175
+ await store.putMany(puts.map((put) => [put.cid, put.block.bytes] as const));
176
+ } else {
177
+ for (const put of puts) {
178
+ await this._store.put(put.cid, put.block.bytes);
179
+ }
180
+ }
181
+ } catch (error: any) {
182
+ if (await this.isClosingStorePutError(error)) {
183
+ return puts.map((put) => put.cid);
184
+ }
185
+ throw error;
186
+ }
187
+ return puts.map((put) => put.cid);
188
+ }
189
+
190
+ putKnown(cid: string, bytes: Uint8Array): Promise<string> | string {
191
+ try {
192
+ const immutableStore = this._store as ImmutableBlockStore;
193
+ const result = immutableStore.putImmutable
194
+ ? immutableStore.putImmutable(cid, bytes)
195
+ : this._store.put(cid, bytes);
196
+ if (isPromiseLike(result)) {
197
+ return result
198
+ .then(() => cid)
199
+ .catch((error) => this.handleStorePutError(error, cid));
200
+ }
201
+ } catch (error: any) {
202
+ return this.handleStorePutError(error, cid);
203
+ }
204
+ return cid;
205
+ }
206
+
207
+ putKnownMany(
208
+ blocks: Array<readonly [cid: string, bytes: Uint8Array]>,
209
+ ): Promise<string[]> | string[] {
210
+ const store = this._store as AnyStore & {
211
+ putMany?: (
212
+ entries: Iterable<readonly [string, Uint8Array]>,
213
+ ) => Promise<void> | void;
214
+ putManyImmutable?: (
215
+ entries: Iterable<readonly [string, Uint8Array]>,
216
+ ) => Promise<void> | void;
217
+ };
218
+ if (blocks.length === 1) {
219
+ const [cid, bytes] = blocks[0]!;
220
+ const result = this.putKnown(cid, bytes);
221
+ return isPromiseLike(result) ? result.then(() => [cid]) : [cid];
222
+ }
223
+ return this.putKnownManyWithStoreBatch(blocks, store);
224
+ }
225
+
226
+ private async putKnownManyWithStoreBatch(
227
+ blocks: Array<readonly [cid: string, bytes: Uint8Array]>,
228
+ store: AnyStore & {
229
+ putMany?: (
230
+ entries: Iterable<readonly [string, Uint8Array]>,
231
+ ) => Promise<void> | void;
232
+ putManyImmutable?: (
233
+ entries: Iterable<readonly [string, Uint8Array]>,
234
+ ) => Promise<void> | void;
235
+ },
236
+ ): Promise<string[]> {
237
+ try {
238
+ if (typeof store.putManyImmutable === "function") {
239
+ await store.putManyImmutable(blocks);
240
+ } else if (typeof store.putMany === "function") {
241
+ await store.putMany(blocks);
242
+ } else {
243
+ for (const [cid, bytes] of blocks) {
244
+ await this._store.put(cid, bytes);
245
+ }
246
+ }
247
+ } catch (error: any) {
248
+ if (await this.isClosingStorePutError(error)) {
249
+ return blocks.map(([cid]) => cid);
250
+ }
251
+ throw error;
252
+ }
253
+ return blocks.map(([cid]) => cid);
254
+ }
255
+
256
+ private async handleStorePutError<T>(error: any, value: T): Promise<T> {
257
+ if (await this.isClosingStorePutError(error)) {
258
+ return value;
259
+ }
260
+ throw error;
261
+ }
262
+
263
+ private async isClosingStorePutError(error: any): Promise<boolean> {
264
+ const status = await this._store.status();
265
+ return (
266
+ typeof error?.code === "string" &&
267
+ error.code === "LEVEL_DATABASE_NOT_OPEN" &&
268
+ this._closeController?.signal.aborted === true &&
269
+ (status === "closing" || status === "closed")
270
+ );
271
+ }
272
+
96
273
  async rm(cid: string): Promise<void> {
97
274
  await this._store.del(cid);
98
275
  }
99
276
 
277
+ async rmMany(cids: string[]): Promise<number> {
278
+ const store = this._store as AnyStore & {
279
+ delMany?: (keys: string[]) => Promise<number>;
280
+ };
281
+ if (typeof store.delMany === "function") {
282
+ return store.delMany(cids);
283
+ }
284
+ await Promise.all(cids.map((cid) => this._store.del(cid)));
285
+ return cids.length;
286
+ }
287
+
100
288
  async *iterator(): AsyncGenerator<[string, Uint8Array], void, void> {
101
289
  for await (const [key, value] of this._store.iterator()) {
102
290
  yield [key, value];
@@ -104,7 +292,35 @@ export class AnyBlockStore implements Blocks {
104
292
  }
105
293
 
106
294
  async has(cid: string) {
107
- return !!(await this._store.get(cid));
295
+ try {
296
+ return !!(await this._store.get(cid));
297
+ } catch (error: any) {
298
+ if (
299
+ typeof error?.code === "string" &&
300
+ error?.code?.indexOf("LEVEL_NOT_FOUND") !== -1
301
+ ) {
302
+ return false;
303
+ }
304
+ throw error;
305
+ }
306
+ }
307
+
308
+ async hasMany(cids: string[]): Promise<boolean[]> {
309
+ const store = this._store as AnyStore & {
310
+ getMany?: (
311
+ keys: string[],
312
+ ) => Promise<Array<Uint8Array | undefined>> | Array<Uint8Array | undefined>;
313
+ hasMany?: (keys: string[]) => Promise<boolean[]> | boolean[];
314
+ };
315
+ if (typeof store.hasMany === "function") {
316
+ return store.hasMany(cids);
317
+ }
318
+ if (typeof store.getMany === "function") {
319
+ const values = await store.getMany(cids);
320
+ return values.map((value) => value != null);
321
+ }
322
+
323
+ return Promise.all(cids.map((cid) => this.has(cid)));
108
324
  }
109
325
 
110
326
  async start(): Promise<void> {
package/src/interface.ts CHANGED
@@ -1,10 +1,32 @@
1
1
  import type { MaybePromise } from "@peerbit/any-store-interface";
2
- import type { Blocks as IBlockStore } from "@peerbit/blocks-interface";
2
+ import type { Blocks as IBlockStore, GetOptions } from "@peerbit/blocks-interface";
3
+ import type { Block } from "multiformats/block";
3
4
 
4
5
  export type StoreStatus = MaybePromise<
5
6
  "open" | "opening" | "closed" | "closing"
6
7
  >;
7
8
  export interface BlockStore extends IBlockStore {
9
+ putMany(
10
+ blocks: Array<Uint8Array | { block: Block<any, any, any, any>; cid: string }>,
11
+ ): MaybePromise<string[]>;
12
+ putKnown(cid: string, bytes: Uint8Array): MaybePromise<string>;
13
+ putKnownMany(
14
+ blocks: Array<readonly [cid: string, bytes: Uint8Array]>,
15
+ ): MaybePromise<string[]>;
16
+ getMany(
17
+ cids: string[],
18
+ options?: GetOptions,
19
+ ): MaybePromise<Array<Uint8Array | undefined>>;
20
+ hasMany(cids: string[]): MaybePromise<boolean[]>;
21
+ rmMany(cids: string[]): MaybePromise<number | void>;
22
+ getNativeLogBlockStoreHandle?(): unknown;
23
+ /**
24
+ * Serialize a `BlockResponse` payload for a natively stored block without
25
+ * materializing the block bytes as a JS value. Only implemented by stores
26
+ * backed by the native log block store; `undefined` when the block is not
27
+ * held natively.
28
+ */
29
+ getBlockResponsePayload?(cid: string): Uint8Array | undefined;
8
30
  start(): Promise<void>;
9
31
  stop(): Promise<void>;
10
32
  status(): StoreStatus;
package/src/libp2p.ts CHANGED
@@ -1,16 +1,21 @@
1
1
  import { deserialize, serialize } from "@dao-xyz/borsh";
2
2
  import { createStore } from "@peerbit/any-store";
3
+ import type { AnyStore } from "@peerbit/any-store-interface";
3
4
  import type { GetOptions, Blocks as IBlocks } from "@peerbit/blocks-interface";
4
5
  import { getPublicKeyFromPeerId, type PublicSignKey } from "@peerbit/crypto";
5
6
  import { DirectStream } from "@peerbit/stream";
6
- import { type DirectStreamComponents } from "@peerbit/stream";
7
+ import {
8
+ type DirectStreamComponents,
9
+ type RustCoreStream,
10
+ } from "@peerbit/stream";
7
11
  import {
8
12
  createRequestTransportContext,
9
13
  type DataMessage,
10
14
  type WaitForPeersFn,
11
15
  } from "@peerbit/stream-interface";
16
+ import type { Block } from "multiformats/block";
12
17
  import { AnyBlockStore } from "./any-blockstore.js";
13
- import { BlockMessage, RemoteBlocks } from "./remote.js";
18
+ import { BlockMessage, BlockRequest, BlockResponse, RemoteBlocks } from "./remote.js";
14
19
 
15
20
  export type DirectBlockComponents = DirectStreamComponents;
16
21
 
@@ -23,6 +28,7 @@ export class DirectBlock extends DirectStream implements IBlocks {
23
28
  components: DirectBlockComponents,
24
29
  options?: {
25
30
  directory?: string;
31
+ localStore?: AnyStore;
26
32
  canRelayMessage?: boolean;
27
33
  localTimeout?: number;
28
34
  messageProcessingConcurrency?: number;
@@ -47,8 +53,20 @@ export class DirectBlock extends DirectStream implements IBlocks {
47
53
  maxProvidersPerCid?: number;
48
54
  };
49
55
  requeryOnReachable?: number;
56
+ /**
57
+ * Run the block-exchange protocol on the native DirectStream core
58
+ * (`@peerbit/network-rust`): codec, provider resolution and caches
59
+ * execute in wasm, and natively stored blocks are served without
60
+ * surfacing their bytes to JS. Defaults to the same rust-core mode
61
+ * as the underlying DirectStream.
62
+ */
63
+ rustCore?: RustCoreStream | false;
50
64
  },
51
65
  ) {
66
+ if (options?.directory && options.localStore) {
67
+ throw new Error("DirectBlock options cannot include both directory and localStore");
68
+ }
69
+
52
70
  super(components, ["/peerbit/direct-block/1.0.0"], {
53
71
  messageProcessingConcurrency: options?.messageProcessingConcurrency || 10,
54
72
  canRelayMessage: options?.canRelayMessage ?? true,
@@ -56,9 +74,29 @@ export class DirectBlock extends DirectStream implements IBlocks {
56
74
  dialer: false,
57
75
  pruner: false,
58
76
  },
77
+ rustCore: options?.rustCore,
59
78
  });
60
79
 
80
+ const blockExchange = this.rustCore?.blockExchange;
61
81
  const defaultResolveProviders = () => {
82
+ if (blockExchange) {
83
+ const negotiated = [...this.peers.keys()];
84
+ const connected: string[] = [];
85
+ for (const conn of this.components.connectionManager.getConnections()) {
86
+ try {
87
+ connected.push(
88
+ getPublicKeyFromPeerId(conn.remotePeer).hashcode(),
89
+ );
90
+ } catch {
91
+ // ignore unexpected key types
92
+ }
93
+ }
94
+ return blockExchange.defaultProviderCandidates(
95
+ negotiated,
96
+ connected,
97
+ this.publicKeyHash,
98
+ );
99
+ }
62
100
  const out: string[] = [];
63
101
  const push = (hash?: string) => {
64
102
  if (!hash) return;
@@ -89,8 +127,11 @@ export class DirectBlock extends DirectStream implements IBlocks {
89
127
  return out;
90
128
  };
91
129
  this.remoteBlocks = new RemoteBlocks({
92
- local: new AnyBlockStore(createStore(options?.directory)),
93
- publish: (message, options) => this.publish(serialize(message), options),
130
+ local: new AnyBlockStore(
131
+ options?.localStore ?? createStore(options?.directory),
132
+ ),
133
+ publish: (message, options) =>
134
+ this.publish(this.encodeBlockMessage(message), options),
94
135
  localTimeout: options?.localTimeout || 1000,
95
136
  messageProcessingConcurrency: options?.messageProcessingConcurrency || 10,
96
137
  waitFor: this.waitFor.bind(this) as WaitForPeersFn,
@@ -101,30 +142,97 @@ export class DirectBlock extends DirectStream implements IBlocks {
101
142
  onPut: options?.onPut,
102
143
  providerCache: options?.providerCache,
103
144
  requeryOnReachable: options?.requeryOnReachable,
145
+ rust: blockExchange
146
+ ? {
147
+ exchange: blockExchange,
148
+ publishRaw: (payload, options) => this.publish(payload, options),
149
+ }
150
+ : undefined,
104
151
  });
105
152
 
106
153
  this.onDataFn = (data: CustomEvent<DataMessage>) => {
107
- data.detail?.data?.length &&
108
- data.detail?.data.length > 0 &&
109
- this.remoteBlocks.onMessage(
110
- deserialize(data.detail.data!, BlockMessage),
111
- {
112
- from: data.detail.header.signatures?.publicKeys[0]?.hashcode(),
113
- transport: createRequestTransportContext(data.detail),
114
- },
115
- );
154
+ if (!(data.detail?.data?.length && data.detail.data.length > 0)) {
155
+ return;
156
+ }
157
+ let message: BlockMessage;
158
+ try {
159
+ message = this.decodeBlockMessage(data.detail.data!);
160
+ } catch {
161
+ // Drop undecodable frames instead of letting the throw escape
162
+ // the 'data' listener (an uncaughtException in Node). The TS
163
+ // borsh decoder tolerates malformed frames lossily and fails
164
+ // later inside handleFetchRequest, which is caught; the native
165
+ // decoder throws on e.g. non-UTF-8 cid bytes, so match the
166
+ // tolerant behaviour here.
167
+ return;
168
+ }
169
+ this.remoteBlocks.onMessage(message, {
170
+ from: data.detail.header.signatures?.publicKeys[0]?.hashcode(),
171
+ transport: createRequestTransportContext(data.detail),
172
+ });
116
173
  };
117
174
  this.onPeerConnectedFn = (evt: CustomEvent<PublicSignKey>) =>
118
175
  this.remoteBlocks.onReachable(evt.detail);
119
176
  }
120
177
 
121
- async put(bytes: Uint8Array): Promise<string> {
178
+ private encodeBlockMessage(message: BlockRequest | BlockResponse): Uint8Array {
179
+ const blockExchange = this.rustCore?.blockExchange;
180
+ if (blockExchange) {
181
+ if (message instanceof BlockRequest) {
182
+ return blockExchange.encodeBlockRequest(message.cid);
183
+ }
184
+ if (message instanceof BlockResponse) {
185
+ return blockExchange.encodeBlockResponse(message.cid, message.bytes);
186
+ }
187
+ }
188
+ return serialize(message);
189
+ }
190
+
191
+ private decodeBlockMessage(bytes: Uint8Array): BlockMessage {
192
+ const blockExchange = this.rustCore?.blockExchange;
193
+ if (blockExchange) {
194
+ const decoded = blockExchange.decodeBlockMessage(bytes);
195
+ return decoded.type === "request"
196
+ ? new BlockRequest(decoded.cid)
197
+ : new BlockResponse(decoded.cid, decoded.bytes);
198
+ }
199
+ return deserialize(bytes, BlockMessage);
200
+ }
201
+
202
+ getNativeLogBlockStoreHandle(): unknown {
203
+ return this.remoteBlocks.getNativeLogBlockStoreHandle();
204
+ }
205
+
206
+ async put(
207
+ bytes: Uint8Array | { block: Block<any, any, any, any>; cid: string },
208
+ ): Promise<string> {
122
209
  return this.remoteBlocks.put(bytes);
123
210
  }
124
211
 
212
+ async putMany(
213
+ blocks: Array<
214
+ Uint8Array | { block: Block<any, any, any, any>; cid: string }
215
+ >,
216
+ ): Promise<string[]> {
217
+ return this.remoteBlocks.putMany(blocks);
218
+ }
219
+
220
+ async putKnown(cid: string, bytes: Uint8Array): Promise<string> {
221
+ return this.remoteBlocks.putKnown(cid, bytes);
222
+ }
223
+
224
+ async putKnownMany(
225
+ blocks: Array<readonly [cid: string, bytes: Uint8Array]>,
226
+ ): Promise<string[]> {
227
+ return this.remoteBlocks.putKnownMany(blocks);
228
+ }
229
+
125
230
  async has(cid: string) {
126
231
  return this.remoteBlocks.has(cid);
127
232
  }
233
+ async hasMany(cids: string[]): Promise<boolean[]> {
234
+ return this.remoteBlocks.hasMany(cids);
235
+ }
128
236
  async get(
129
237
  cid: string,
130
238
  options?: GetOptions | undefined,
@@ -132,6 +240,13 @@ export class DirectBlock extends DirectStream implements IBlocks {
132
240
  return this.remoteBlocks.get(cid, options);
133
241
  }
134
242
 
243
+ async getMany(
244
+ cids: string[],
245
+ options?: GetOptions | undefined,
246
+ ): Promise<Array<Uint8Array | undefined>> {
247
+ return this.remoteBlocks.getMany(cids, options);
248
+ }
249
+
135
250
  hintProviders(cid: string, providers: string[]) {
136
251
  this.remoteBlocks.hintProviders(cid, providers);
137
252
  }
@@ -140,6 +255,10 @@ export class DirectBlock extends DirectStream implements IBlocks {
140
255
  return this.remoteBlocks.rm(cid);
141
256
  }
142
257
 
258
+ async rmMany(cids: string[]) {
259
+ return this.remoteBlocks.rmMany(cids);
260
+ }
261
+
143
262
  async *iterator(): AsyncGenerator<[string, Uint8Array], void, void> {
144
263
  for await (const [key, value] of this.remoteBlocks.iterator()) {
145
264
  yield [key, value];