@helia/utils 0.1.0-9c8a2c0 → 0.1.0-9ea934e

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.
@@ -5,75 +5,47 @@ import filter from 'it-filter'
5
5
  import forEach from 'it-foreach'
6
6
  import { CustomProgressEvent, type ProgressOptions } from 'progress-events'
7
7
  import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
8
- import type { BlockBroker, Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions, BlockRetrievalOptions } from '@helia/interface/blocks'
8
+ import type { BlockBroker, Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions, BlockRetrievalOptions, CreateSessionOptions, SessionBlockstore } from '@helia/interface/blocks'
9
9
  import type { AbortOptions, ComponentLogger, Logger, LoggerOptions, Startable } from '@libp2p/interface'
10
10
  import type { Blockstore } from 'interface-blockstore'
11
11
  import type { AwaitIterable } from 'interface-store'
12
12
  import type { CID } from 'multiformats/cid'
13
13
  import type { MultihashHasher } from 'multiformats/hashes/interface'
14
14
 
15
- export interface NetworkedStorageInit {
16
- root?: CID
17
- }
18
-
19
15
  export interface GetOptions extends AbortOptions {
20
16
  progress?(evt: Event): void
21
17
  }
22
18
 
23
- export interface NetworkedStorageComponents {
19
+ export interface StorageComponents {
24
20
  blockstore: Blockstore
25
21
  logger: ComponentLogger
26
22
  blockBrokers: BlockBroker[]
27
23
  hashers: Record<number, MultihashHasher>
28
24
  }
29
25
 
30
- /**
31
- * Networked storage wraps a regular blockstore - when getting blocks if the
32
- * blocks are not present Bitswap will be used to fetch them from network peers.
33
- */
34
- export class NetworkedStorage implements Blocks, Startable {
35
- private readonly child: Blockstore
36
- private readonly hashers: Record<number, MultihashHasher>
37
- private started: boolean
38
- private readonly log: Logger
39
- private readonly logger: ComponentLogger
40
- private readonly components: NetworkedStorageComponents
26
+ class Storage implements Blockstore {
27
+ protected readonly child: Blockstore
28
+ protected readonly hashers: Record<number, MultihashHasher>
29
+ protected log: Logger
30
+ protected readonly logger: ComponentLogger
31
+ protected readonly components: StorageComponents
41
32
 
42
33
  /**
43
34
  * Create a new BlockStorage
44
35
  */
45
- constructor (components: NetworkedStorageComponents, init: NetworkedStorageInit = {}) {
46
- this.log = components.logger.forComponent(`helia:networked-storage${init.root == null ? '' : `:${init.root}`}`)
36
+ constructor (components: StorageComponents) {
37
+ this.log = components.logger.forComponent('helia:networked-storage')
47
38
  this.logger = components.logger
48
39
  this.components = components
49
40
  this.child = new IdentityBlockstore(components.blockstore)
50
41
  this.hashers = components.hashers ?? {}
51
- this.started = false
52
- }
53
-
54
- isStarted (): boolean {
55
- return this.started
56
- }
57
-
58
- async start (): Promise<void> {
59
- await start(this.child, ...this.components.blockBrokers)
60
- this.started = true
61
- }
62
-
63
- async stop (): Promise<void> {
64
- await stop(this.child, ...this.components.blockBrokers)
65
- this.started = false
66
- }
67
-
68
- unwrap (): Blockstore {
69
- return this.child
70
42
  }
71
43
 
72
44
  /**
73
45
  * Put a block to the underlying datastore
74
46
  */
75
47
  async put (cid: CID, block: Uint8Array, options: AbortOptions & ProgressOptions<PutBlockProgressEvents> = {}): Promise<CID> {
76
- if (await this.child.has(cid)) {
48
+ if (await this.child.has(cid, options)) {
77
49
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:duplicate', cid))
78
50
  return cid
79
51
  }
@@ -94,7 +66,7 @@ export class NetworkedStorage implements Blocks, Startable {
94
66
  */
95
67
  async * putMany (blocks: AwaitIterable<{ cid: CID, block: Uint8Array }>, options: AbortOptions & ProgressOptions<PutManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
96
68
  const missingBlocks = filter(blocks, async ({ cid }): Promise<boolean> => {
97
- const has = await this.child.has(cid)
69
+ const has = await this.child.has(cid, options)
98
70
 
99
71
  if (has) {
100
72
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:duplicate', cid))
@@ -118,7 +90,7 @@ export class NetworkedStorage implements Blocks, Startable {
118
90
  * Get a block by cid
119
91
  */
120
92
  async get (cid: CID, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetBlockProgressEvents> = {}): Promise<Uint8Array> {
121
- if (options.offline !== true && !(await this.child.has(cid))) {
93
+ if (options.offline !== true && !(await this.child.has(cid, options))) {
122
94
  // we do not have the block locally, get it from a block provider
123
95
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:get', cid))
124
96
  const block = await raceBlockRetrievers(cid, this.components.blockBrokers, this.hashers[cid.multihash.code], {
@@ -149,7 +121,7 @@ export class NetworkedStorage implements Blocks, Startable {
149
121
  options.onProgress?.(new CustomProgressEvent('blocks:get-many:blockstore:get-many'))
150
122
 
151
123
  yield * this.child.getMany(forEach(cids, async (cid): Promise<void> => {
152
- if (options.offline !== true && !(await this.child.has(cid))) {
124
+ if (options.offline !== true && !(await this.child.has(cid, options))) {
153
125
  // we do not have the block locally, get it from a block provider
154
126
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:get', cid))
155
127
  const block = await raceBlockRetrievers(cid, this.components.blockBrokers, this.hashers[cid.multihash.code], {
@@ -197,17 +169,54 @@ export class NetworkedStorage implements Blocks, Startable {
197
169
  options.onProgress?.(new CustomProgressEvent('blocks:get-all:blockstore:get-many'))
198
170
  yield * this.child.getAll(options)
199
171
  }
172
+ }
173
+
174
+ export type NetworkedStorageComponents = StorageComponents
175
+
176
+ /**
177
+ * Networked storage wraps a regular blockstore - when getting blocks if the
178
+ * blocks are not present, the configured BlockBrokers will be used to fetch them.
179
+ */
180
+ export class NetworkedStorage extends Storage implements Blocks, Startable {
181
+ private started: boolean
200
182
 
201
- async createSession (root: CID, options?: AbortOptions & ProgressOptions<GetBlockProgressEvents>): Promise<Blocks> {
202
- const blockBrokers = await Promise.all(this.components.blockBrokers.map(async broker => {
183
+ /**
184
+ * Create a new BlockStorage
185
+ */
186
+ constructor (components: NetworkedStorageComponents) {
187
+ super(components)
188
+
189
+ this.started = false
190
+ }
191
+
192
+ isStarted (): boolean {
193
+ return this.started
194
+ }
195
+
196
+ async start (): Promise<void> {
197
+ await start(this.child, ...this.components.blockBrokers)
198
+ this.started = true
199
+ }
200
+
201
+ async stop (): Promise<void> {
202
+ await stop(this.child, ...this.components.blockBrokers)
203
+ this.started = false
204
+ }
205
+
206
+ unwrap (): Blockstore {
207
+ return this.child
208
+ }
209
+
210
+ createSession (root: CID, options?: CreateSessionOptions): SessionBlockstore {
211
+ const blockBrokers = this.components.blockBrokers.map(broker => {
203
212
  if (broker.createSession == null) {
204
213
  return broker
205
214
  }
206
215
 
207
- return broker.createSession(root, options)
208
- }))
216
+ return broker.createSession(options)
217
+ })
209
218
 
210
- return new NetworkedStorage({
219
+ return new SessionStorage({
211
220
  blockstore: this.child,
212
221
  blockBrokers,
213
222
  hashers: this.hashers,
@@ -218,9 +227,167 @@ export class NetworkedStorage implements Blocks, Startable {
218
227
  }
219
228
  }
220
229
 
230
+ interface SessionStorageInit {
231
+ root: CID
232
+ }
233
+
234
+ /**
235
+ * Storage subclass that can cancel any ongoing operation at any point.
236
+ */
237
+ class SessionStorage extends Storage implements SessionBlockstore {
238
+ private readonly closeController: AbortController
239
+
240
+ constructor (components: StorageComponents, init: SessionStorageInit) {
241
+ super(components)
242
+
243
+ // because brokers are allowed to continue searching for providers after the
244
+ // session has been created, we need a way to tell them that the user has
245
+ // finished using the session any in-flight requests should be cancelled
246
+ this.closeController = new AbortController()
247
+ setMaxListeners(Infinity, this.closeController.signal)
248
+
249
+ this.log = components.logger.forComponent(`helia:session-storage${init.root}`)
250
+ }
251
+
252
+ close (): void {
253
+ this.closeController.abort()
254
+ }
255
+
256
+ /**
257
+ * Put a block to the underlying datastore
258
+ */
259
+ async put (cid: CID, block: Uint8Array, options: AbortOptions & ProgressOptions<PutBlockProgressEvents> = {}): Promise<CID> {
260
+ const signal = anySignal([this.closeController.signal, options.signal])
261
+ setMaxListeners(Infinity, signal)
262
+
263
+ try {
264
+ return await super.put(cid, block, {
265
+ ...options,
266
+ signal
267
+ })
268
+ } finally {
269
+ signal.clear()
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Put a multiple blocks to the underlying datastore
275
+ */
276
+ async * putMany (blocks: AwaitIterable<{ cid: CID, block: Uint8Array }>, options: AbortOptions & ProgressOptions<PutManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
277
+ const signal = anySignal([this.closeController.signal, options.signal])
278
+ setMaxListeners(Infinity, signal)
279
+
280
+ try {
281
+ yield * super.putMany(blocks, {
282
+ ...options,
283
+ signal
284
+ })
285
+ } finally {
286
+ signal.clear()
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Get a block by cid
292
+ */
293
+ async get (cid: CID, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetBlockProgressEvents> = {}): Promise<Uint8Array> {
294
+ const signal = anySignal([this.closeController.signal, options.signal])
295
+ setMaxListeners(Infinity, signal)
296
+
297
+ try {
298
+ return await super.get(cid, {
299
+ ...options,
300
+ signal
301
+ })
302
+ } finally {
303
+ signal.clear()
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Get multiple blocks back from an (async) iterable of cids
309
+ */
310
+ async * getMany (cids: AwaitIterable<CID>, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetManyBlocksProgressEvents> = {}): AsyncIterable<Pair> {
311
+ const signal = anySignal([this.closeController.signal, options.signal])
312
+ setMaxListeners(Infinity, signal)
313
+
314
+ try {
315
+ yield * super.getMany(cids, {
316
+ ...options,
317
+ signal
318
+ })
319
+ } finally {
320
+ signal.clear()
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Delete a block from the blockstore
326
+ */
327
+ async delete (cid: CID, options: AbortOptions & ProgressOptions<DeleteBlockProgressEvents> = {}): Promise<void> {
328
+ const signal = anySignal([this.closeController.signal, options.signal])
329
+ setMaxListeners(Infinity, signal)
330
+
331
+ try {
332
+ await super.delete(cid, {
333
+ ...options,
334
+ signal
335
+ })
336
+ } finally {
337
+ signal.clear()
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Delete multiple blocks from the blockstore
343
+ */
344
+ async * deleteMany (cids: AwaitIterable<CID>, options: AbortOptions & ProgressOptions<DeleteManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
345
+ const signal = anySignal([this.closeController.signal, options.signal])
346
+ setMaxListeners(Infinity, signal)
347
+
348
+ try {
349
+ yield * super.deleteMany(cids, {
350
+ ...options,
351
+ signal
352
+ })
353
+ } finally {
354
+ signal.clear()
355
+ }
356
+ }
357
+
358
+ async has (cid: CID, options: AbortOptions = {}): Promise<boolean> {
359
+ const signal = anySignal([this.closeController.signal, options.signal])
360
+ setMaxListeners(Infinity, signal)
361
+
362
+ try {
363
+ return await super.has(cid, {
364
+ ...options,
365
+ signal
366
+ })
367
+ } finally {
368
+ signal.clear()
369
+ }
370
+ }
371
+
372
+ async * getAll (options: AbortOptions & ProgressOptions<GetAllBlocksProgressEvents> = {}): AwaitIterable<Pair> {
373
+ const signal = anySignal([this.closeController.signal, options.signal])
374
+ setMaxListeners(Infinity, signal)
375
+
376
+ try {
377
+ yield * super.getAll({
378
+ ...options,
379
+ signal
380
+ })
381
+ } finally {
382
+ signal.clear()
383
+ }
384
+ }
385
+ }
386
+
221
387
  function isRetrievingBlockBroker (broker: BlockBroker): broker is Required<Pick<BlockBroker, 'retrieve'>> {
222
388
  return typeof broker.retrieve === 'function'
223
389
  }
390
+
224
391
  export const getCidBlockVerifierFunction = (cid: CID, hasher: MultihashHasher): Required<BlockRetrievalOptions>['validateFn'] => {
225
392
  if (hasher == null) {
226
393
  throw new CodeError(`No hasher configured for multihash code 0x${cid.multihash.code.toString(16)}, please configure one. You can look up which hash this is at https://github.com/multiformats/multicodec/blob/master/table.csv`, 'ERR_UNKNOWN_HASH_ALG')