@helia/verified-fetch 6.0.0 → 6.1.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 (40) hide show
  1. package/dist/index.min.js +56 -56
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/index.d.ts +9 -2
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
  7. package/dist/src/plugins/plugin-handle-car.js +7 -3
  8. package/dist/src/plugins/plugin-handle-car.js.map +1 -1
  9. package/dist/src/plugins/plugin-handle-ipld.d.ts.map +1 -1
  10. package/dist/src/plugins/plugin-handle-ipld.js +4 -15
  11. package/dist/src/plugins/plugin-handle-ipld.js.map +1 -1
  12. package/dist/src/plugins/plugin-handle-raw.d.ts +11 -0
  13. package/dist/src/plugins/plugin-handle-raw.d.ts.map +1 -0
  14. package/dist/src/plugins/plugin-handle-raw.js +41 -0
  15. package/dist/src/plugins/plugin-handle-raw.js.map +1 -0
  16. package/dist/src/plugins/plugin-handle-unixfs.d.ts.map +1 -1
  17. package/dist/src/plugins/plugin-handle-unixfs.js +32 -14
  18. package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -1
  19. package/dist/src/url-resolver.d.ts +2 -3
  20. package/dist/src/url-resolver.d.ts.map +1 -1
  21. package/dist/src/url-resolver.js +19 -55
  22. package/dist/src/url-resolver.js.map +1 -1
  23. package/dist/src/utils/error-to-response.js +1 -1
  24. package/dist/src/utils/error-to-response.js.map +1 -1
  25. package/dist/src/utils/get-tar-stream.d.ts.map +1 -1
  26. package/dist/src/utils/get-tar-stream.js +22 -9
  27. package/dist/src/utils/get-tar-stream.js.map +1 -1
  28. package/dist/src/verified-fetch.d.ts.map +1 -1
  29. package/dist/src/verified-fetch.js +28 -23
  30. package/dist/src/verified-fetch.js.map +1 -1
  31. package/package.json +3 -3
  32. package/src/index.ts +10 -2
  33. package/src/plugins/plugin-handle-car.ts +8 -3
  34. package/src/plugins/plugin-handle-ipld.ts +4 -14
  35. package/src/plugins/plugin-handle-raw.ts +52 -0
  36. package/src/plugins/plugin-handle-unixfs.ts +36 -14
  37. package/src/url-resolver.ts +21 -63
  38. package/src/utils/error-to-response.ts +1 -1
  39. package/src/utils/get-tar-stream.ts +26 -10
  40. package/src/verified-fetch.ts +23 -18
@@ -1,15 +1,9 @@
1
1
  import { DoesNotExistError } from '@helia/unixfs/errors'
2
- import * as dagCbor from '@ipld/dag-cbor'
3
- import * as dagJson from '@ipld/dag-json'
4
- import * as dagPb from '@ipld/dag-pb'
5
2
  import { peerIdFromString } from '@libp2p/peer-id'
6
3
  import { InvalidParametersError, walkPath } from 'ipfs-unixfs-exporter'
7
- import toBuffer from 'it-to-buffer'
8
4
  import { CID } from 'multiformats/cid'
9
- import * as json from 'multiformats/codecs/json'
10
- import * as raw from 'multiformats/codecs/raw'
11
5
  import QuickLRU from 'quick-lru'
12
- import { SESSION_CACHE_MAX_SIZE, SESSION_CACHE_TTL_MS, CODEC_CBOR, CODEC_IDENTITY } from './constants.ts'
6
+ import { SESSION_CACHE_MAX_SIZE, SESSION_CACHE_TTL_MS } from './constants.ts'
13
7
  import { ServerTiming } from './utils/server-timing.ts'
14
8
  import type { ResolveURLResult, URLResolver as URLResolverInterface } from './index.ts'
15
9
  import type { DNSLink } from '@helia/dnslink'
@@ -17,49 +11,30 @@ import type { IPNSResolver } from '@helia/ipns'
17
11
  import type { AbortOptions } from '@libp2p/interface'
18
12
  import type { Helia, SessionBlockstore } from 'helia'
19
13
  import type { Blockstore } from 'interface-blockstore'
20
- import type { UnixFSEntry } from 'ipfs-unixfs-exporter'
14
+ import type { PathEntry } from 'ipfs-unixfs-exporter'
21
15
 
22
16
  // 1 year in seconds for ipfs content
23
17
  const IPFS_CONTENT_TTL = 29030400
24
18
 
25
- const ENTITY_CODECS = [
26
- CODEC_CBOR,
27
- json.code,
28
- raw.code
29
- ]
30
-
31
- /**
32
- * These are supported by the UnixFS exporter
33
- */
34
- const EXPORTABLE_CODECS = [
35
- dagPb.code,
36
- dagCbor.code,
37
- dagJson.code,
38
- raw.code
39
- ]
40
-
41
19
  interface GetBlockstoreOptions extends AbortOptions {
42
20
  session?: boolean
43
21
  }
44
22
 
45
23
  export interface WalkPathResult {
46
24
  ipfsRoots: CID[]
47
- terminalElement: UnixFSEntry
25
+ terminalElement: PathEntry
48
26
  blockstore: Blockstore
49
27
  }
50
28
 
51
- function basicEntry (type: 'raw' | 'object', cid: CID, bytes: Uint8Array): UnixFSEntry {
29
+ function basicEntry (cid: CID): PathEntry {
52
30
  return {
31
+ cid,
53
32
  name: cid.toString(),
54
33
  path: cid.toString(),
55
- depth: 0,
56
- type,
57
- node: bytes,
58
- cid,
59
- size: BigInt(bytes.byteLength),
60
- content: async function * () {
61
- yield bytes
62
- }
34
+ roots: [
35
+ cid
36
+ ],
37
+ remainder: []
63
38
  }
64
39
  }
65
40
 
@@ -77,7 +52,6 @@ export interface URLResolverInit {
77
52
  export interface ResolveURLOptions extends AbortOptions {
78
53
  session?: boolean
79
54
  isRawBlockRequest?: boolean
80
- onlyIfCached?: boolean
81
55
  }
82
56
 
83
57
  export class URLResolver implements URLResolverInterface {
@@ -195,16 +169,14 @@ export class URLResolver implements URLResolverInterface {
195
169
  const cid = CID.parse(url.hostname)
196
170
  const blockstore = this.getBlockstore(cid, options)
197
171
 
198
- if (EXPORTABLE_CODECS.includes(cid.code)) {
172
+ try {
199
173
  const ipfsRoots: CID[] = []
200
- let terminalElement: UnixFSEntry | undefined
174
+ let terminalElement: PathEntry | undefined
201
175
  const ipfsPath = toIPFSPath(url)
202
176
 
203
- // @ts-expect-error offline is a helia option
204
177
  for await (const entry of walkPath(ipfsPath, blockstore, {
205
178
  ...options,
206
- offline: options.onlyIfCached === true,
207
- extended: options.isRawBlockRequest !== true
179
+ yieldSubShards: true
208
180
  })) {
209
181
  ipfsRoots.push(entry.cid)
210
182
  terminalElement = entry
@@ -219,31 +191,17 @@ export class URLResolver implements URLResolverInterface {
219
191
  terminalElement,
220
192
  blockstore
221
193
  }
222
- }
223
-
224
- let bytes: Uint8Array
225
-
226
- if (cid.multihash.code === CODEC_IDENTITY) {
227
- bytes = cid.multihash.digest
228
- } else {
229
- bytes = await toBuffer(blockstore.get(cid, options))
230
- }
231
-
232
- // entity codecs contain all the bytes for an entity in one block and no
233
- // path walking outside of that block is possible
234
- if (ENTITY_CODECS.includes(cid.code)) {
235
- return {
236
- ipfsRoots: [cid],
237
- terminalElement: basicEntry('object', cid, bytes),
238
- blockstore
194
+ } catch (err: any) {
195
+ if (err.name === 'NoResolverError') {
196
+ // may be an unknown codec
197
+ return {
198
+ ipfsRoots: [cid],
199
+ terminalElement: basicEntry(cid),
200
+ blockstore
201
+ }
239
202
  }
240
- }
241
203
 
242
- // may be an unknown codec
243
- return {
244
- ipfsRoots: [cid],
245
- terminalElement: basicEntry('raw', cid, bytes),
246
- blockstore
204
+ throw err
247
205
  }
248
206
  }
249
207
  }
@@ -26,7 +26,7 @@ export function errorToResponse (resource: Resource | string, err: any, init?: R
26
26
  }
27
27
 
28
28
  // path was not under DAG root
29
- if (['ERR_NO_PROP', 'ERR_NO_TERMINAL_ELEMENT', 'ERR_NOT_FOUND'].includes(err.code)) {
29
+ if (['ERR_BAD_PATH', 'ERR_NO_TERMINAL_ELEMENT', 'ERR_NOT_FOUND'].includes(err.code)) {
30
30
  return notFoundResponse(resource)
31
31
  }
32
32
 
@@ -1,5 +1,7 @@
1
1
  import { NotUnixFSError } from '@helia/unixfs/errors'
2
- import { exporter, recursive } from 'ipfs-unixfs-exporter'
2
+ import { InvalidParametersError } from '@libp2p/interface'
3
+ import { exporter, recursive, walkPath } from 'ipfs-unixfs-exporter'
4
+ import last from 'it-last'
3
5
  import map from 'it-map'
4
6
  import { pipe } from 'it-pipe'
5
7
  import { pack } from 'it-tar'
@@ -10,9 +12,14 @@ import type { TarEntryHeader, TarImportCandidate } from 'it-tar'
10
12
 
11
13
  const EXPORTABLE = ['file', 'raw', 'directory']
12
14
 
13
- function toHeader (file: UnixFSEntry): Partial<TarEntryHeader> & { name: string } {
15
+ function toHeader (file: UnixFSEntry, path: string): Partial<TarEntryHeader> & { name: string } {
14
16
  let mode: number | undefined
15
17
  let mtime: Date | undefined
18
+ let size = 0n
19
+
20
+ if (file.type === 'file' || file.type === 'raw' || file.type === 'identity') {
21
+ size = file.size
22
+ }
16
23
 
17
24
  if (file.type === 'file' || file.type === 'directory') {
18
25
  mode = file.unixfs.mode
@@ -20,21 +27,21 @@ function toHeader (file: UnixFSEntry): Partial<TarEntryHeader> & { name: string
20
27
  }
21
28
 
22
29
  return {
23
- name: file.path,
30
+ name: path,
24
31
  mode,
25
32
  mtime,
26
- size: Number(file.size),
33
+ size: Number(size),
27
34
  type: file.type === 'directory' ? 'directory' : 'file'
28
35
  }
29
36
  }
30
37
 
31
- function toTarImportCandidate (entry: UnixFSEntry): TarImportCandidate {
38
+ function toTarImportCandidate (entry: UnixFSEntry, path: string): TarImportCandidate {
32
39
  if (!EXPORTABLE.includes(entry.type)) {
33
40
  throw new NotUnixFSError(`${entry.type} is not a UnixFS node`)
34
41
  }
35
42
 
36
43
  const candidate: TarImportCandidate = {
37
- header: toHeader(entry)
44
+ header: toHeader(entry, path)
38
45
  }
39
46
 
40
47
  if (entry.type === 'file' || entry.type === 'raw') {
@@ -45,11 +52,17 @@ function toTarImportCandidate (entry: UnixFSEntry): TarImportCandidate {
45
52
  }
46
53
 
47
54
  export async function * tarStream (ipfsPath: string, blockstore: Blockstore, options?: AbortOptions): AsyncGenerator<Uint8Array> {
48
- const file = await exporter(ipfsPath, blockstore, options)
55
+ const entry = await last(walkPath(ipfsPath, blockstore, options))
56
+
57
+ if (entry == null) {
58
+ throw new InvalidParametersError(`Could not walk path "${ipfsPath}"`)
59
+ }
60
+
61
+ const file = await exporter(entry.cid, blockstore, options)
49
62
 
50
63
  if (file.type === 'file' || file.type === 'raw') {
51
64
  yield * pipe(
52
- [toTarImportCandidate(file)],
65
+ [toTarImportCandidate(file, entry.path)],
53
66
  pack()
54
67
  )
55
68
 
@@ -58,8 +71,11 @@ export async function * tarStream (ipfsPath: string, blockstore: Blockstore, opt
58
71
 
59
72
  if (file.type === 'directory') {
60
73
  yield * pipe(
61
- recursive(ipfsPath, blockstore, options),
62
- (source) => map(source, (entry) => toTarImportCandidate(entry)),
74
+ recursive(file.cid, blockstore, options),
75
+ (source) => map(source, async (entry) => {
76
+ const file = await exporter(entry.cid, blockstore, options)
77
+ return toTarImportCandidate(file, entry.path)
78
+ }),
63
79
  pack()
64
80
  )
65
81
 
@@ -1,6 +1,6 @@
1
1
  import { dnsLink } from '@helia/dnslink'
2
2
  import { ipnsResolver } from '@helia/ipns'
3
- import { AbortError, isPeerId, isPublicKey } from '@libp2p/interface'
3
+ import { isPeerId, isPublicKey } from '@libp2p/interface'
4
4
  import { CID } from 'multiformats/cid'
5
5
  import { CustomProgressEvent } from 'progress-events'
6
6
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
@@ -8,6 +8,7 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
8
8
  import { CarPlugin } from './plugins/plugin-handle-car.js'
9
9
  import { IpldPlugin } from './plugins/plugin-handle-ipld.js'
10
10
  import { IpnsRecordPlugin } from './plugins/plugin-handle-ipns-record.js'
11
+ import { RawPlugin } from './plugins/plugin-handle-raw.ts'
11
12
  import { TarPlugin } from './plugins/plugin-handle-tar.js'
12
13
  import { UnixFSPlugin } from './plugins/plugin-handle-unixfs.js'
13
14
  import { URLResolver } from './url-resolver.ts'
@@ -19,7 +20,7 @@ import { getETag, ifNoneMatches } from './utils/get-e-tag.js'
19
20
  import { getRangeHeader } from './utils/get-range-header.ts'
20
21
  import { stringToIpfsUrl } from './utils/parse-resource.ts'
21
22
  import { setCacheControlHeader } from './utils/response-headers.js'
22
- import { internalServerErrorResponse, notAcceptableResponse, notImplementedResponse, notModifiedResponse } from './utils/responses.js'
23
+ import { notAcceptableResponse, notImplementedResponse, notModifiedResponse } from './utils/responses.js'
23
24
  import { ServerTiming } from './utils/server-timing.js'
24
25
  import type { AcceptHeader, CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions, VerifiedFetchPlugin, PluginContext, PluginOptions } from './index.js'
25
26
  import type { DNSLink } from '@helia/dnslink'
@@ -108,7 +109,8 @@ export class VerifiedFetch {
108
109
  new IpldPlugin(pluginOptions),
109
110
  new CarPlugin(pluginOptions),
110
111
  new TarPlugin(pluginOptions),
111
- new IpnsRecordPlugin(pluginOptions)
112
+ new IpnsRecordPlugin(pluginOptions),
113
+ new RawPlugin(pluginOptions)
112
114
  ]
113
115
 
114
116
  const customPlugins = init.plugins?.map((pluginFactory) => pluginFactory(pluginOptions)) ?? []
@@ -149,6 +151,10 @@ export class VerifiedFetch {
149
151
  const headers = new Headers(options?.headers)
150
152
  const serverTiming = new ServerTiming()
151
153
 
154
+ if (options != null) {
155
+ options.offline ??= headers.get('cache-control') === 'only-if-cached'
156
+ }
157
+
152
158
  options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
153
159
 
154
160
  const range = getRangeHeader(resource, headers)
@@ -201,8 +207,7 @@ export class VerifiedFetch {
201
207
 
202
208
  const resolveResult = await this.urlResolver.resolve(url, serverTiming, {
203
209
  ...options,
204
- isRawBlockRequest: isRawBlockRequest(headers),
205
- onlyIfCached: headers.get('cache-control') === 'only-if-cached'
210
+ isRawBlockRequest: isRawBlockRequest(headers)
206
211
  })
207
212
 
208
213
  options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', {
@@ -408,21 +413,21 @@ export class VerifiedFetch {
408
413
  let pluginHandled = false
409
414
 
410
415
  for (const plugin of plugins) {
411
- try {
412
- this.log('invoking plugin: %s', plugin.id)
413
- pluginsUsed.add(plugin.id)
416
+ // try {
417
+ this.log('invoking plugin: %s', plugin.id)
418
+ pluginsUsed.add(plugin.id)
414
419
 
415
- const maybeResponse = await plugin.handle(context)
420
+ const maybeResponse = await plugin.handle(context)
416
421
 
417
- this.log('plugin response %s %o', plugin.id, maybeResponse)
422
+ this.log('plugin response %s %o', plugin.id, maybeResponse)
418
423
 
419
- if (maybeResponse != null) {
420
- // if a plugin returns a final Response, short-circuit
421
- finalResponse = maybeResponse
422
- pluginHandled = true
423
- break
424
- }
425
- } catch (err: any) {
424
+ if (maybeResponse != null) {
425
+ // if a plugin returns a final Response, short-circuit
426
+ finalResponse = maybeResponse
427
+ pluginHandled = true
428
+ break
429
+ }
430
+ /* } catch (err: any) {
426
431
  if (context.options?.signal?.aborted) {
427
432
  throw new AbortError(context.options?.signal?.reason)
428
433
  }
@@ -430,7 +435,7 @@ export class VerifiedFetch {
430
435
  this.log.error('error in plugin %s - %e', plugin.id, err)
431
436
 
432
437
  return internalServerErrorResponse(context.resource, err)
433
- }
438
+ } */
434
439
 
435
440
  if (finalResponse != null) {
436
441
  this.log.trace('plugin %s produced final response', plugin.id)