@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.
package/src/remote.ts CHANGED
@@ -11,7 +11,12 @@ import {
11
11
  import { Cache } from "@peerbit/cache";
12
12
  import { PublicSignKey } from "@peerbit/crypto";
13
13
  import { logger as loggerFn } from "@peerbit/logger";
14
- import { type PublishOptions, dontThrowIfDeliveryError } from "@peerbit/stream";
14
+ import {
15
+ type PublishOptions,
16
+ type RustBlockExchange,
17
+ type RustBlockProviderCache,
18
+ dontThrowIfDeliveryError,
19
+ } from "@peerbit/stream";
15
20
  import {
16
21
  type RequestTransportContext,
17
22
  type PeerRefs,
@@ -24,7 +29,6 @@ import { AbortError } from "@peerbit/time";
24
29
  import { CID } from "multiformats";
25
30
  import { type Block } from "multiformats/block";
26
31
  import PQueue from "p-queue";
27
- import { AnyBlockStore } from "./any-blockstore.js";
28
32
  import type { BlockStore } from "./interface.js";
29
33
 
30
34
  export const logger = loggerFn("peerbit:transport:blocks");
@@ -72,22 +76,42 @@ type RemoteReadOptions = Exclude<GetOptions["remote"], boolean | undefined> & {
72
76
  hasher?: any;
73
77
  };
74
78
 
79
+ /**
80
+ * Shared shape of the TS eager-block cache (`Cache<Uint8Array>`) and the
81
+ * native-backed one (`RustEagerBlockCache`).
82
+ */
83
+ type EagerBlockCache = {
84
+ add(cid: string, bytes: Uint8Array): void;
85
+ get(cid: string): Uint8Array | null | undefined;
86
+ del(cid: string): unknown;
87
+ clear(): void;
88
+ };
89
+
75
90
  export class RemoteBlocks implements IBlocks {
76
91
  localStore: BlockStore;
77
92
 
93
+ // Assigned in the constructor only when the local store supports it:
94
+ // callers feature-detect this method (the log's columnar raw-receive
95
+ // fast path), so it must not exist without a delegation target.
96
+ putKnownManyColumns?: (cids: string[], bytes: Uint8Array[]) => string[];
97
+
78
98
  private _responseHandler?: (
79
99
  data: BlockMessage,
80
100
  context?: BlockMessageContext,
81
101
  ) => any;
82
102
  private _resolvers: Map<string, (data: Uint8Array) => Promise<void>>;
83
- private _blockCache?: Cache<Uint8Array>;
103
+ private _blockCache?: EagerBlockCache;
84
104
  private _providerCache?: Cache<string[]>;
105
+ private _rustProviderCache?: RustBlockProviderCache;
106
+ private readonly rustExchange?: RustBlockExchange;
85
107
  private readonly publicKeyHash: string;
86
108
  private readonly maxProviderHintsPerCid: number;
87
109
  private readonly maxRequeryOnReachable: number;
88
110
 
89
111
  private _loadFetchQueue: PQueue;
90
112
  private _readFromPeersPromises: Map<string, InFlightRead>;
113
+ private _deferredStoredNotificationCids?: Set<string>;
114
+ private _deferredStoredNotificationTimer?: ReturnType<typeof setTimeout>;
91
115
  _open = false;
92
116
  private _events: TypedEventEmitter<{
93
117
  "peer:reachable": CustomEvent<PublicSignKey>;
@@ -97,7 +121,7 @@ export class RemoteBlocks implements IBlocks {
97
121
 
98
122
  constructor(
99
123
  readonly options: {
100
- local: AnyBlockStore;
124
+ local: BlockStore;
101
125
  localTimeout?: number;
102
126
  messageProcessingConcurrency?: number;
103
127
  publicKey: PublicSignKey;
@@ -158,25 +182,59 @@ export class RemoteBlocks implements IBlocks {
158
182
  options: PublishOptions,
159
183
  ) => Promise<Uint8Array | undefined | void>;
160
184
  waitFor: WaitForPeersFn;
185
+ /**
186
+ * Native block-exchange components (rust-core mode). When set, the
187
+ * provider caches/decisions and eager-block bookkeeping run in the
188
+ * native core; `publishRaw` additionally enables serving natively
189
+ * stored blocks as wasm-serialized payloads that never surface the
190
+ * block bytes to JS.
191
+ */
192
+ rust?: {
193
+ exchange: RustBlockExchange;
194
+ publishRaw?: (
195
+ payload: Uint8Array,
196
+ options: PublishOptions,
197
+ ) => Promise<Uint8Array | undefined | void>;
198
+ };
161
199
  },
162
200
  ) {
163
201
  const localTimeout = options?.localTimeout || 1000;
164
202
  this.publicKeyHash = options.publicKey.hashcode();
203
+ this.rustExchange = options.rust?.exchange;
165
204
  this._loadFetchQueue = new PQueue({
166
205
  concurrency: options?.messageProcessingConcurrency || 10,
167
206
  });
168
207
  this.localStore = options?.local;
208
+ const localPutKnownManyColumns = (
209
+ this.localStore as {
210
+ putKnownManyColumns?: (
211
+ cids: string[],
212
+ bytes: Uint8Array[],
213
+ ) => string[];
214
+ }
215
+ )?.putKnownManyColumns;
216
+ if (typeof localPutKnownManyColumns === "function") {
217
+ this.putKnownManyColumns = (cids, bytes) => {
218
+ const stored = localPutKnownManyColumns.call(
219
+ this.localStore,
220
+ cids,
221
+ bytes,
222
+ );
223
+ void this.notifyPuts(stored);
224
+ return stored;
225
+ };
226
+ }
169
227
  this._resolvers = new Map();
170
228
  this._readFromPeersPromises = new Map();
171
- this._blockCache = options?.eagerBlocks
172
- ? new Cache<Uint8Array>({
173
- max:
174
- typeof options.eagerBlocks === "boolean"
175
- ? 1e3
176
- : (options.eagerBlocks.cacheSize ?? 1e3),
177
- ttl: 1e4,
178
- })
179
- : undefined;
229
+ if (options?.eagerBlocks) {
230
+ const eagerBlocksMax =
231
+ typeof options.eagerBlocks === "boolean"
232
+ ? 1e3
233
+ : (options.eagerBlocks.cacheSize ?? 1e3);
234
+ this._blockCache = this.rustExchange
235
+ ? this.rustExchange.createEagerCache({ max: eagerBlocksMax, ttl: 1e4 })
236
+ : new Cache<Uint8Array>({ max: eagerBlocksMax, ttl: 1e4 });
237
+ }
180
238
  type ProviderCacheOptions = {
181
239
  maxEntries?: number;
182
240
  ttlMs?: number;
@@ -188,12 +246,21 @@ export class RemoteBlocks implements IBlocks {
188
246
  : typeof options.providerCache === "object"
189
247
  ? options.providerCache
190
248
  : {};
191
- this._providerCache = providerCache
192
- ? new Cache<string[]>({
249
+ if (providerCache) {
250
+ if (this.rustExchange) {
251
+ this._rustProviderCache = this.rustExchange.createProviderCache({
252
+ me: this.publicKeyHash,
253
+ maxEntries: providerCache.maxEntries ?? 2048,
254
+ ttlMs: providerCache.ttlMs ?? 10 * 60 * 1000,
255
+ maxProvidersPerCid: providerCache.maxProvidersPerCid ?? 8,
256
+ });
257
+ } else {
258
+ this._providerCache = new Cache<string[]>({
193
259
  max: providerCache.maxEntries ?? 2048,
194
260
  ttl: providerCache.ttlMs ?? 10 * 60 * 1000,
195
- })
196
- : undefined;
261
+ });
262
+ }
263
+ }
197
264
  this.maxProviderHintsPerCid = providerCache?.maxProvidersPerCid ?? 8;
198
265
  this.maxRequeryOnReachable = options.requeryOnReachable ?? 4;
199
266
 
@@ -234,11 +301,22 @@ export class RemoteBlocks implements IBlocks {
234
301
  };
235
302
  }
236
303
 
304
+ getNativeLogBlockStoreHandle(): unknown {
305
+ return this.localStore.getNativeLogBlockStoreHandle?.();
306
+ }
307
+
237
308
  private normalizeProviderHints(
238
309
  providers: string[] | undefined,
239
310
  limit = this.maxProviderHintsPerCid || 8,
240
311
  ): string[] {
241
312
  if (!providers || providers.length === 0) return [];
313
+ if (this.rustExchange) {
314
+ return this.rustExchange.normalizeProviderHints(
315
+ providers,
316
+ this.publicKeyHash,
317
+ limit,
318
+ );
319
+ }
242
320
  const out: string[] = [];
243
321
  for (const p of providers) {
244
322
  if (!p) continue;
@@ -252,6 +330,10 @@ export class RemoteBlocks implements IBlocks {
252
330
  }
253
331
 
254
332
  private rememberProvider(cidString: string, providerHash: string) {
333
+ if (this._rustProviderCache) {
334
+ this._rustProviderCache.rememberProvider(cidString, providerHash);
335
+ return;
336
+ }
255
337
  if (!this._providerCache) return;
256
338
  if (!providerHash || providerHash === this.publicKeyHash) return;
257
339
  const current = this._providerCache.get(cidString) ?? [];
@@ -266,13 +348,30 @@ export class RemoteBlocks implements IBlocks {
266
348
  }
267
349
 
268
350
  private rememberProviderHints(cidString: string, providers: string[]) {
351
+ if (this._rustProviderCache) {
352
+ this._rustProviderCache.rememberHints(cidString, providers);
353
+ return;
354
+ }
269
355
  if (!this._providerCache) return;
270
356
  const normalized = this.normalizeProviderHints(providers);
271
357
  if (normalized.length === 0) return;
272
358
  this._providerCache.add(cidString, normalized);
273
359
  }
274
360
 
361
+ private getCachedProviders(cidString: string): string[] | undefined {
362
+ return this._rustProviderCache
363
+ ? this._rustProviderCache.get(cidString)
364
+ : (this._providerCache?.get(cidString) ?? undefined);
365
+ }
366
+
275
367
  private pickRequestBatch(providers: string[], attempt: number): string[] {
368
+ if (this.rustExchange) {
369
+ return this.rustExchange.pickRequestBatch(
370
+ providers,
371
+ this.publicKeyHash,
372
+ attempt,
373
+ );
374
+ }
276
375
  if (providers.length <= 1) {
277
376
  return providers;
278
377
  }
@@ -293,7 +392,7 @@ export class RemoteBlocks implements IBlocks {
293
392
  // 1. cached providers (from previous reads)
294
393
  // 2. resolveProviders hook (e.g. program-level replicators, DHT, tracker)
295
394
  const cached = this.normalizeProviderHints(
296
- this._providerCache?.get(cidString) ?? undefined,
395
+ this.getCachedProviders(cidString),
297
396
  );
298
397
  if (!this.options.resolveProviders) return cached;
299
398
  if (cached.length > 0 && !options?.refresh) return cached;
@@ -316,18 +415,147 @@ export class RemoteBlocks implements IBlocks {
316
415
  throw new Error("Local store not set");
317
416
  }
318
417
  const cid = await this.localStore!.put(bytes);
418
+ await this.notifyPut(cid);
419
+ return cid;
420
+ }
421
+
422
+ async putMany(
423
+ blocks: Array<Uint8Array | { block: Block<any, any, any, any>; cid: string }>,
424
+ ): Promise<string[]> {
425
+ if (!this.localStore) {
426
+ throw new Error("Local store not set");
427
+ }
428
+ const cids = await this.localStore.putMany(blocks);
429
+ await this.notifyPuts(cids);
430
+ return cids;
431
+ }
432
+
433
+ async putKnown(cid: string, bytes: Uint8Array): Promise<string> {
434
+ if (!this.localStore) {
435
+ throw new Error("Local store not set");
436
+ }
437
+ const storedCid = await this.localStore.putKnown(cid, bytes);
438
+ await this.notifyPut(storedCid);
439
+ return storedCid;
440
+ }
441
+
442
+ async putKnownMany(
443
+ blocks: Array<readonly [cid: string, bytes: Uint8Array]>,
444
+ ): Promise<string[]> {
445
+ if (!this.localStore) {
446
+ throw new Error("Local store not set");
447
+ }
448
+ const cids = await this.localStore.putKnownMany(blocks);
449
+ await this.notifyPuts(cids);
450
+ return cids;
451
+ }
452
+
453
+ hasNotifyStoredHook(): boolean {
454
+ return !!this.options.onPut;
455
+ }
456
+
457
+ notifyStored(cid: string): Promise<void> | void {
458
+ return this.notifyPut(cid);
459
+ }
460
+
461
+ notifyStoredMany(cids: string[]): Promise<void> | void {
462
+ return this.notifyPuts(cids);
463
+ }
464
+
465
+ notifyStoredDeferred(cid: string): void {
466
+ this.notifyStoredManyDeferred([cid]);
467
+ }
468
+
469
+ notifyStoredManyDeferred(cids: string[]): void {
470
+ if (!this.options.onPut || cids.length === 0) {
471
+ return;
472
+ }
473
+ let pending = this._deferredStoredNotificationCids;
474
+ if (!pending) {
475
+ pending = new Set();
476
+ this._deferredStoredNotificationCids = pending;
477
+ }
478
+ for (const cid of cids) {
479
+ if (cid) {
480
+ pending.add(cid);
481
+ }
482
+ }
483
+ if (pending.size === 0 || this._deferredStoredNotificationTimer) {
484
+ return;
485
+ }
486
+ this._deferredStoredNotificationTimer = setTimeout(() => {
487
+ this._deferredStoredNotificationTimer = undefined;
488
+ const flushed = this.flushDeferredStoredNotifications();
489
+ if (flushed && typeof flushed.catch === "function") {
490
+ flushed.catch((): void => undefined);
491
+ }
492
+ }, 0);
493
+ }
494
+
495
+ private flushDeferredStoredNotifications(): Promise<void> | void {
496
+ const pending = this._deferredStoredNotificationCids;
497
+ if (!pending || pending.size === 0) {
498
+ return;
499
+ }
500
+ this._deferredStoredNotificationCids = undefined;
501
+ const waits: Promise<void>[] = [];
502
+ for (const cid of pending) {
503
+ try {
504
+ const result = this.notifyStored(cid);
505
+ if (result && typeof result.then === "function") {
506
+ waits.push(result);
507
+ }
508
+ } catch {
509
+ // ignore best-effort hooks
510
+ }
511
+ }
512
+ if (waits.length > 0) {
513
+ return Promise.all(waits).then((): void => undefined);
514
+ }
515
+ }
516
+
517
+ private notifyPut(cid: string): Promise<void> | void {
518
+ const onPut = this.options.onPut;
519
+ if (!onPut) {
520
+ return;
521
+ }
319
522
  try {
320
- await this.options.onPut?.(cid);
523
+ const result = onPut(cid);
524
+ if (result && typeof result.then === "function") {
525
+ return result.catch((): void => undefined);
526
+ }
321
527
  } catch {
322
528
  // ignore best-effort hooks
323
529
  }
324
- return cid;
530
+ }
531
+
532
+ private notifyPuts(cids: string[]): Promise<void> | void {
533
+ const onPut = this.options.onPut;
534
+ if (!onPut || cids.length === 0) {
535
+ return;
536
+ }
537
+ if (cids.length === 1) {
538
+ return this.notifyPut(cids[0]!);
539
+ }
540
+ return Promise.all(
541
+ cids.map(async (cid) => {
542
+ try {
543
+ await onPut(cid);
544
+ } catch {
545
+ // ignore best-effort hooks
546
+ }
547
+ }),
548
+ ).then((): void => undefined);
325
549
  }
326
550
 
327
551
  async has(cid: string) {
328
552
  return this.localStore.has(cid);
329
553
  }
330
554
 
555
+ async hasMany(cids: string[]): Promise<boolean[]> {
556
+ return this.localStore.hasMany(cids);
557
+ }
558
+
331
559
  async get(
332
560
  cid: string,
333
561
  options?: GetOptions | undefined,
@@ -351,6 +579,16 @@ export class RemoteBlocks implements IBlocks {
351
579
  return value;
352
580
  }
353
581
 
582
+ async getMany(
583
+ cids: string[],
584
+ options?: GetOptions | undefined,
585
+ ): Promise<Array<Uint8Array | undefined>> {
586
+ if (!options?.remote) {
587
+ return this.localStore.getMany(cids, options);
588
+ }
589
+ return Promise.all(cids.map((cid) => this.get(cid, options)));
590
+ }
591
+
354
592
  hintProviders(cid: string, providers: string[]) {
355
593
  const cidString = stringifyCid(cid);
356
594
  this.rememberProviderHints(cidString, providers);
@@ -363,6 +601,10 @@ export class RemoteBlocks implements IBlocks {
363
601
  await this.localStore?.rm(cid);
364
602
  }
365
603
 
604
+ async rmMany(cids: string[]) {
605
+ await this.localStore?.rmMany(cids);
606
+ }
607
+
366
608
  async *iterator(): AsyncGenerator<[string, Uint8Array], void, void> {
367
609
  for await (const [key, value] of this.localStore.iterator()) {
368
610
  yield [key, value];
@@ -396,6 +638,22 @@ export class RemoteBlocks implements IBlocks {
396
638
  return;
397
639
  }
398
640
  const cid = stringifyCid(request.cid);
641
+ const publishRaw = this.options.rust?.publishRaw;
642
+ if (publishRaw) {
643
+ // Native-store-served response: the borsh BlockResponse payload is
644
+ // serialized inside wasm straight from the native log block store,
645
+ // so the block bytes never materialize as a JS value.
646
+ const payload = this.localStore.getBlockResponsePayload?.(cid);
647
+ if (payload) {
648
+ const responsePublishOptions = context?.transport
649
+ ? context.transport.withResponseOptions({ to: [from] })
650
+ : { to: [from] };
651
+ await publishRaw(payload, responsePublishOptions).catch(
652
+ dontThrowIfDeliveryError,
653
+ );
654
+ return;
655
+ }
656
+ }
399
657
  let bytes = await this.localStore.get(cid, {
400
658
  remote: {
401
659
  timeout: localTimeout,
@@ -459,7 +717,7 @@ export class RemoteBlocks implements IBlocks {
459
717
  cidObject: CID,
460
718
  options: RemoteReadOptions = {},
461
719
  ): Promise<Uint8Array | undefined> {
462
- const codec = (codecCodes as any)[cidObject.code];
720
+ const codec = codecCodes[cidObject.code as keyof typeof codecCodes];
463
721
 
464
722
  const tryDecode = async (bytes: Uint8Array) => {
465
723
  const value = await checkDecodeBlock(cidObject, bytes, {
@@ -756,6 +1014,11 @@ export class RemoteBlocks implements IBlocks {
756
1014
 
757
1015
  // Wait for processing request
758
1016
  this.closeController.abort();
1017
+ if (this._deferredStoredNotificationTimer) {
1018
+ clearTimeout(this._deferredStoredNotificationTimer);
1019
+ this._deferredStoredNotificationTimer = undefined;
1020
+ }
1021
+ await this.flushDeferredStoredNotifications();
759
1022
  this._loadFetchQueue.clear();
760
1023
  await this._loadFetchQueue.onIdle(); // wait for pending
761
1024
  await this.localStore?.stop();
@@ -763,6 +1026,7 @@ export class RemoteBlocks implements IBlocks {
763
1026
  this._resolvers.clear();
764
1027
  this._blockCache?.clear();
765
1028
  this._providerCache?.clear();
1029
+ this._rustProviderCache?.clear();
766
1030
  this._open = false;
767
1031
  // we dont cleanup subscription because we dont know if someone else is sbuscribing also
768
1032
  }