@helia/verified-fetch 0.0.0 → 1.0.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 (81) hide show
  1. package/README.md +353 -56
  2. package/dist/index.min.js +7 -29
  3. package/dist/src/index.d.ts +384 -69
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +345 -77
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/singleton.d.ts +3 -0
  8. package/dist/src/singleton.d.ts.map +1 -0
  9. package/dist/src/singleton.js +15 -0
  10. package/dist/src/singleton.js.map +1 -0
  11. package/dist/src/types.d.ts +2 -0
  12. package/dist/src/types.d.ts.map +1 -0
  13. package/dist/src/types.js +2 -0
  14. package/dist/src/types.js.map +1 -0
  15. package/dist/src/utils/dag-cbor-to-safe-json.d.ts +7 -0
  16. package/dist/src/utils/dag-cbor-to-safe-json.d.ts.map +1 -0
  17. package/dist/src/utils/dag-cbor-to-safe-json.js +37 -0
  18. package/dist/src/utils/dag-cbor-to-safe-json.js.map +1 -0
  19. package/dist/src/utils/get-content-disposition-filename.d.ts +6 -0
  20. package/dist/src/utils/get-content-disposition-filename.d.ts.map +1 -0
  21. package/dist/src/utils/get-content-disposition-filename.js +16 -0
  22. package/dist/src/utils/get-content-disposition-filename.js.map +1 -0
  23. package/dist/src/utils/get-e-tag.d.ts +28 -0
  24. package/dist/src/utils/get-e-tag.d.ts.map +1 -0
  25. package/dist/src/utils/get-e-tag.js +18 -0
  26. package/dist/src/utils/get-e-tag.js.map +1 -0
  27. package/dist/src/utils/get-stream-from-async-iterable.d.ts +10 -0
  28. package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +1 -0
  29. package/dist/src/utils/{get-stream-and-content-type.js → get-stream-from-async-iterable.js} +11 -11
  30. package/dist/src/utils/get-stream-from-async-iterable.js.map +1 -0
  31. package/dist/src/utils/get-tar-stream.d.ts +4 -0
  32. package/dist/src/utils/get-tar-stream.d.ts.map +1 -0
  33. package/dist/src/utils/get-tar-stream.js +46 -0
  34. package/dist/src/utils/get-tar-stream.js.map +1 -0
  35. package/dist/src/utils/parse-resource.d.ts +6 -1
  36. package/dist/src/utils/parse-resource.d.ts.map +1 -1
  37. package/dist/src/utils/parse-resource.js +2 -2
  38. package/dist/src/utils/parse-resource.js.map +1 -1
  39. package/dist/src/utils/parse-url-string.d.ts +10 -3
  40. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  41. package/dist/src/utils/parse-url-string.js +8 -4
  42. package/dist/src/utils/parse-url-string.js.map +1 -1
  43. package/dist/src/utils/responses.d.ts +5 -0
  44. package/dist/src/utils/responses.d.ts.map +1 -0
  45. package/dist/src/utils/responses.js +27 -0
  46. package/dist/src/utils/responses.js.map +1 -0
  47. package/dist/src/utils/select-output-type.d.ts +12 -0
  48. package/dist/src/utils/select-output-type.d.ts.map +1 -0
  49. package/dist/src/utils/select-output-type.js +148 -0
  50. package/dist/src/utils/select-output-type.js.map +1 -0
  51. package/dist/src/utils/walk-path.d.ts +2 -1
  52. package/dist/src/utils/walk-path.d.ts.map +1 -1
  53. package/dist/src/utils/walk-path.js +1 -3
  54. package/dist/src/utils/walk-path.js.map +1 -1
  55. package/dist/src/verified-fetch.d.ts +24 -27
  56. package/dist/src/verified-fetch.d.ts.map +1 -1
  57. package/dist/src/verified-fetch.js +297 -150
  58. package/dist/src/verified-fetch.js.map +1 -1
  59. package/dist/typedoc-urls.json +25 -18
  60. package/package.json +58 -116
  61. package/src/index.ts +391 -72
  62. package/src/singleton.ts +20 -0
  63. package/src/types.ts +1 -0
  64. package/src/utils/dag-cbor-to-safe-json.ts +44 -0
  65. package/src/utils/get-content-disposition-filename.ts +18 -0
  66. package/src/utils/get-e-tag.ts +36 -0
  67. package/src/utils/{get-stream-and-content-type.ts → get-stream-from-async-iterable.ts} +10 -9
  68. package/src/utils/get-tar-stream.ts +68 -0
  69. package/src/utils/parse-url-string.ts +17 -3
  70. package/src/utils/responses.ts +29 -0
  71. package/src/utils/select-output-type.ts +167 -0
  72. package/src/utils/walk-path.ts +4 -5
  73. package/src/verified-fetch.ts +340 -153
  74. package/dist/src/utils/get-content-type.d.ts +0 -11
  75. package/dist/src/utils/get-content-type.d.ts.map +0 -1
  76. package/dist/src/utils/get-content-type.js +0 -43
  77. package/dist/src/utils/get-content-type.js.map +0 -1
  78. package/dist/src/utils/get-stream-and-content-type.d.ts +0 -9
  79. package/dist/src/utils/get-stream-and-content-type.d.ts.map +0 -1
  80. package/dist/src/utils/get-stream-and-content-type.js.map +0 -1
  81. package/src/utils/get-content-type.ts +0 -55
@@ -0,0 +1,44 @@
1
+ import { decode } from 'cborg'
2
+ import { encode } from 'cborg/json'
3
+ import { CID } from 'multiformats/cid'
4
+ import type { TagDecoder } from 'cborg'
5
+
6
+ // https://github.com/ipfs/go-ipfs/issues/3570#issuecomment-273931692
7
+ const CID_CBOR_TAG = 0x2A
8
+
9
+ function cidDecoder (bytes: Uint8Array): CID {
10
+ if (bytes[0] !== 0) {
11
+ throw new Error('Invalid CID for CBOR tag 42; expected leading 0x00')
12
+ }
13
+
14
+ return CID.decode(bytes.subarray(1)) // ignore leading 0x00
15
+ }
16
+
17
+ /**
18
+ * Take a `DAG-CBOR` encoded `Uint8Array`, deserialize it as an object and
19
+ * re-serialize it in a form that can be passed to `JSON.serialize` and then
20
+ * `JSON.parse` without losing any data.
21
+ */
22
+ export function dagCborToSafeJSON (buf: Uint8Array): string {
23
+ const tags: TagDecoder[] = []
24
+ tags[CID_CBOR_TAG] = cidDecoder
25
+
26
+ const obj = decode(buf, {
27
+ allowIndefinite: false,
28
+ coerceUndefinedToNull: true,
29
+ allowNaN: false,
30
+ allowInfinity: false,
31
+ strict: true,
32
+ useMaps: false,
33
+ rejectDuplicateMapKeys: true,
34
+ tags,
35
+
36
+ // this is different to `DAG-CBOR` - the reason we disallow BigInts is
37
+ // because we are about to re-encode to `JSON` which does not support
38
+ // BigInts. Blocks containing large numbers should be deserialized using a
39
+ // cbor decoder instead
40
+ allowBigInt: false
41
+ })
42
+
43
+ return new TextDecoder().decode(encode(obj))
44
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Takes a filename URL param and returns a string for use in a
3
+ * `Content-Disposition` header
4
+ */
5
+ export function getContentDispositionFilename (filename: string): string {
6
+ const asciiOnly = replaceNonAsciiCharacters(filename)
7
+
8
+ if (asciiOnly === filename) {
9
+ return `filename="${filename}"`
10
+ }
11
+
12
+ return `filename="${asciiOnly}"; filename*=UTF-8''${encodeURIComponent(filename)}`
13
+ }
14
+
15
+ function replaceNonAsciiCharacters (filename: string): string {
16
+ // eslint-disable-next-line no-control-regex
17
+ return filename.replace(/[^\x00-\x7F]/g, '_')
18
+ }
@@ -0,0 +1,36 @@
1
+ import type { RequestFormatShorthand } from '../types.js'
2
+ import type { CID } from 'multiformats/cid'
3
+
4
+ interface GetETagArg {
5
+ cid: CID
6
+ reqFormat?: RequestFormatShorthand
7
+ rangeStart?: number
8
+ rangeEnd?: number
9
+ /**
10
+ * Weak Etag is used when we can't guarantee byte-for-byte-determinism (generated, or mutable content).
11
+ * Some examples:
12
+ * - IPNS requests
13
+ * - CAR streamed with blocks in non-deterministic order
14
+ * - TAR streamed with files in non-deterministic order
15
+ */
16
+ weak?: boolean
17
+ }
18
+
19
+ /**
20
+ * etag
21
+ * you need to wrap cid with ""
22
+ * we use strong Etags for immutable responses and weak one (prefixed with W/ ) for mutable/generated ones (ipns and generated HTML).
23
+ * block and car responses should have different etag than deserialized one, so you can add some prefix like we do in existing gateway
24
+ *
25
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
26
+ * @see https://specs.ipfs.tech/http-gateways/path-gateway/#etag-response-header
27
+ */
28
+ export function getETag ({ cid, reqFormat, weak, rangeStart, rangeEnd }: GetETagArg): string {
29
+ const prefix = weak === true ? 'W/' : ''
30
+ let suffix = reqFormat == null ? '' : `.${reqFormat}`
31
+ if (rangeStart != null || rangeEnd != null) {
32
+ suffix += `.${rangeStart ?? '0'}-${rangeEnd ?? 'N'}`
33
+ }
34
+
35
+ return `${prefix}"${cid.toString()}${suffix}"`
36
+ }
@@ -1,27 +1,25 @@
1
1
  import { CustomProgressEvent } from 'progress-events'
2
- import { getContentType } from './get-content-type.js'
3
2
  import type { VerifiedFetchInit } from '../index.js'
4
3
  import type { ComponentLogger } from '@libp2p/interface'
5
4
 
6
5
  /**
7
- * Converts an async iterator of Uint8Array bytes to a stream and attempts to determine the content type of those bytes.
6
+ * Converts an async iterator of Uint8Array bytes to a stream and returns the first chunk of bytes
8
7
  */
9
- export async function getStreamAndContentType (iterator: AsyncIterable<Uint8Array>, path: string, logger: ComponentLogger, options?: Pick<VerifiedFetchInit, 'onProgress'>): Promise<{ contentType: string, stream: ReadableStream<Uint8Array> }> {
10
- const log = logger.forComponent('helia:verified-fetch:get-stream-and-content-type')
8
+ export async function getStreamFromAsyncIterable (iterator: AsyncIterable<Uint8Array>, path: string, logger: ComponentLogger, options?: Pick<VerifiedFetchInit, 'onProgress'>): Promise<{ stream: ReadableStream<Uint8Array>, firstChunk: Uint8Array }> {
9
+ const log = logger.forComponent('helia:verified-fetch:get-stream-from-async-iterable')
11
10
  const reader = iterator[Symbol.asyncIterator]()
12
- const { value, done } = await reader.next()
13
- options?.onProgress?.(new CustomProgressEvent<void>('verified-fetch:request:progress:chunk'))
11
+ const { value: firstChunk, done } = await reader.next()
14
12
 
15
13
  if (done === true) {
16
14
  log.error('No content found for path', path)
17
15
  throw new Error('No content found')
18
16
  }
19
17
 
20
- const contentType = await getContentType({ bytes: value, path })
21
18
  const stream = new ReadableStream({
22
19
  async start (controller) {
23
20
  // the initial value is already available
24
- controller.enqueue(value)
21
+ options?.onProgress?.(new CustomProgressEvent<void>('verified-fetch:request:progress:chunk'))
22
+ controller.enqueue(firstChunk)
25
23
  },
26
24
  async pull (controller) {
27
25
  const { value, done } = await reader.next()
@@ -40,5 +38,8 @@ export async function getStreamAndContentType (iterator: AsyncIterable<Uint8Arra
40
38
  }
41
39
  })
42
40
 
43
- return { contentType, stream }
41
+ return {
42
+ stream,
43
+ firstChunk
44
+ }
44
45
  }
@@ -0,0 +1,68 @@
1
+ import { CodeError } from '@libp2p/interface'
2
+ import { exporter, recursive, type UnixFSEntry } from 'ipfs-unixfs-exporter'
3
+ import map from 'it-map'
4
+ import { pipe } from 'it-pipe'
5
+ import { pack, type TarEntryHeader, type TarImportCandidate } from 'it-tar'
6
+ import type { AbortOptions } from '@libp2p/interface'
7
+ import type { Blockstore } from 'interface-blockstore'
8
+
9
+ const EXPORTABLE = ['file', 'raw', 'directory']
10
+
11
+ function toHeader (file: UnixFSEntry): Partial<TarEntryHeader> & { name: string } {
12
+ let mode: number | undefined
13
+ let mtime: Date | undefined
14
+
15
+ if (file.type === 'file' || file.type === 'directory') {
16
+ mode = file.unixfs.mode
17
+ mtime = file.unixfs.mtime != null ? new Date(Number(file.unixfs.mtime.secs * 1000n)) : undefined
18
+ }
19
+
20
+ return {
21
+ name: file.path,
22
+ mode,
23
+ mtime,
24
+ size: Number(file.size),
25
+ type: file.type === 'directory' ? 'directory' : 'file'
26
+ }
27
+ }
28
+
29
+ function toTarImportCandidate (entry: UnixFSEntry): TarImportCandidate {
30
+ if (!EXPORTABLE.includes(entry.type)) {
31
+ throw new CodeError('Not a UnixFS node', 'ERR_NOT_UNIXFS')
32
+ }
33
+
34
+ const candidate: TarImportCandidate = {
35
+ header: toHeader(entry)
36
+ }
37
+
38
+ if (entry.type === 'file' || entry.type === 'raw') {
39
+ candidate.body = entry.content()
40
+ }
41
+
42
+ return candidate
43
+ }
44
+
45
+ export async function * tarStream (ipfsPath: string, blockstore: Blockstore, options?: AbortOptions): AsyncGenerator<Uint8Array> {
46
+ const file = await exporter(ipfsPath, blockstore, options)
47
+
48
+ if (file.type === 'file' || file.type === 'raw') {
49
+ yield * pipe(
50
+ [toTarImportCandidate(file)],
51
+ pack()
52
+ )
53
+
54
+ return
55
+ }
56
+
57
+ if (file.type === 'directory') {
58
+ yield * pipe(
59
+ recursive(ipfsPath, blockstore, options),
60
+ (source) => map(source, (entry) => toTarImportCandidate(entry)),
61
+ pack()
62
+ )
63
+
64
+ return
65
+ }
66
+
67
+ throw new CodeError('Not a UnixFS node', 'ERR_NOT_UNIXFS')
68
+ }
@@ -1,6 +1,7 @@
1
1
  import { peerIdFromString } from '@libp2p/peer-id'
2
2
  import { CID } from 'multiformats/cid'
3
3
  import { TLRU } from './tlru.js'
4
+ import type { RequestFormatShorthand } from '../types.js'
4
5
  import type { IPNS, IPNSRoutingEvents, ResolveDnsLinkProgressEvents, ResolveProgressEvents, ResolveResult } from '@helia/ipns'
5
6
  import type { ComponentLogger } from '@libp2p/interface'
6
7
  import type { ProgressOptions } from 'progress-events'
@@ -16,11 +17,17 @@ export interface ParseUrlStringOptions extends ProgressOptions<ResolveProgressEv
16
17
 
17
18
  }
18
19
 
20
+ export interface ParsedUrlQuery extends Record<string, string | unknown> {
21
+ format?: RequestFormatShorthand
22
+ download?: boolean
23
+ filename?: string
24
+ }
25
+
19
26
  export interface ParsedUrlStringResults {
20
27
  protocol: string
21
28
  path: string
22
29
  cid: CID
23
- query: Record<string, string>
30
+ query: ParsedUrlQuery
24
31
  }
25
32
 
26
33
  const URL_REGEX = /^(?<protocol>ip[fn]s):\/\/(?<cidOrPeerIdOrDnsLink>[^/$?]+)\/?(?<path>[^$?]*)\??(?<queryString>.*)$/
@@ -31,7 +38,6 @@ const URL_REGEX = /^(?<protocol>ip[fn]s):\/\/(?<cidOrPeerIdOrDnsLink>[^/$?]+)\/?
31
38
  * After determining the protocol successfully, we process the cidOrPeerIdOrDnsLink:
32
39
  * * If it's ipfs, it parses the CID or throws an Aggregate error
33
40
  * * If it's ipns, it attempts to resolve the PeerId and then the DNSLink. If both fail, an Aggregate error is thrown.
34
- *
35
41
  */
36
42
  export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStringInput, options?: ParseUrlStringOptions): Promise<ParsedUrlStringResults> {
37
43
  const log = logger.forComponent('helia:verified-fetch:parse-url-string')
@@ -105,7 +111,7 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
105
111
  }
106
112
 
107
113
  // parse query string
108
- const query: Record<string, string> = {}
114
+ const query: Record<string, any> = {}
109
115
 
110
116
  if (queryString != null && queryString.length > 0) {
111
117
  const queryParts = queryString.split('&')
@@ -113,6 +119,14 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
113
119
  const [key, value] = part.split('=')
114
120
  query[key] = decodeURIComponent(value)
115
121
  }
122
+
123
+ if (query.download != null) {
124
+ query.download = query.download === 'true'
125
+ }
126
+
127
+ if (query.filename != null) {
128
+ query.filename = query.filename.toString()
129
+ }
116
130
  }
117
131
 
118
132
  /**
@@ -0,0 +1,29 @@
1
+ export function okResponse (body?: BodyInit | null): Response {
2
+ return new Response(body, {
3
+ status: 200,
4
+ statusText: 'OK'
5
+ })
6
+ }
7
+
8
+ export function notSupportedResponse (body?: BodyInit | null): Response {
9
+ const response = new Response(body, {
10
+ status: 501,
11
+ statusText: 'Not Implemented'
12
+ })
13
+ response.headers.set('X-Content-Type-Options', 'nosniff') // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
14
+ return response
15
+ }
16
+
17
+ export function notAcceptableResponse (body?: BodyInit | null): Response {
18
+ return new Response(body, {
19
+ status: 406,
20
+ statusText: 'Not Acceptable'
21
+ })
22
+ }
23
+
24
+ export function badRequestResponse (body?: BodyInit | null): Response {
25
+ return new Response(body, {
26
+ status: 400,
27
+ statusText: 'Bad Request'
28
+ })
29
+ }
@@ -0,0 +1,167 @@
1
+ import { code as dagCborCode } from '@ipld/dag-cbor'
2
+ import { code as dagJsonCode } from '@ipld/dag-json'
3
+ import { code as dagPbCode } from '@ipld/dag-pb'
4
+ import { code as jsonCode } from 'multiformats/codecs/json'
5
+ import { code as rawCode } from 'multiformats/codecs/raw'
6
+ import type { RequestFormatShorthand } from '../types.js'
7
+ import type { CID } from 'multiformats/cid'
8
+
9
+ /**
10
+ * This maps supported response types for each codec supported by verified-fetch
11
+ */
12
+ const CID_TYPE_MAP: Record<number, string[]> = {
13
+ [dagCborCode]: [
14
+ 'application/json',
15
+ 'application/vnd.ipld.dag-cbor',
16
+ 'application/cbor',
17
+ 'application/vnd.ipld.dag-json',
18
+ 'application/octet-stream',
19
+ 'application/vnd.ipld.raw',
20
+ 'application/vnd.ipfs.ipns-record',
21
+ 'application/vnd.ipld.car'
22
+ ],
23
+ [dagJsonCode]: [
24
+ 'application/json',
25
+ 'application/vnd.ipld.dag-cbor',
26
+ 'application/cbor',
27
+ 'application/vnd.ipld.dag-json',
28
+ 'application/octet-stream',
29
+ 'application/vnd.ipld.raw',
30
+ 'application/vnd.ipfs.ipns-record',
31
+ 'application/vnd.ipld.car'
32
+ ],
33
+ [jsonCode]: [
34
+ 'application/json',
35
+ 'application/vnd.ipld.dag-cbor',
36
+ 'application/cbor',
37
+ 'application/vnd.ipld.dag-json',
38
+ 'application/octet-stream',
39
+ 'application/vnd.ipld.raw',
40
+ 'application/vnd.ipfs.ipns-record',
41
+ 'application/vnd.ipld.car'
42
+ ],
43
+ [dagPbCode]: [
44
+ 'application/octet-stream',
45
+ 'application/json',
46
+ 'application/vnd.ipld.dag-cbor',
47
+ 'application/cbor',
48
+ 'application/vnd.ipld.dag-json',
49
+ 'application/vnd.ipld.raw',
50
+ 'application/vnd.ipfs.ipns-record',
51
+ 'application/vnd.ipld.car',
52
+ 'application/x-tar'
53
+ ],
54
+ [rawCode]: [
55
+ 'application/octet-stream',
56
+ 'application/vnd.ipld.raw',
57
+ 'application/vnd.ipfs.ipns-record',
58
+ 'application/vnd.ipld.car',
59
+ 'application/x-tar'
60
+ ]
61
+ }
62
+
63
+ /**
64
+ * Selects an output mime-type based on the CID and a passed `Accept` header
65
+ */
66
+ export function selectOutputType (cid: CID, accept?: string): string | undefined {
67
+ const cidMimeTypes = CID_TYPE_MAP[cid.code]
68
+
69
+ if (accept != null) {
70
+ return chooseMimeType(accept, cidMimeTypes)
71
+ }
72
+ }
73
+
74
+ function chooseMimeType (accept: string, validMimeTypes: string[]): string | undefined {
75
+ const requestedMimeTypes = accept
76
+ .split(',')
77
+ .map(s => {
78
+ const parts = s.trim().split(';')
79
+
80
+ return {
81
+ mimeType: `${parts[0]}`.trim(),
82
+ weight: parseQFactor(parts[1])
83
+ }
84
+ })
85
+ .sort((a, b) => {
86
+ if (a.weight === b.weight) {
87
+ return 0
88
+ }
89
+
90
+ if (a.weight > b.weight) {
91
+ return -1
92
+ }
93
+
94
+ return 1
95
+ })
96
+ .map(s => s.mimeType)
97
+
98
+ for (const headerFormat of requestedMimeTypes) {
99
+ for (const mimeType of validMimeTypes) {
100
+ if (headerFormat.includes(mimeType)) {
101
+ return mimeType
102
+ }
103
+
104
+ if (headerFormat === '*/*') {
105
+ return mimeType
106
+ }
107
+
108
+ if (headerFormat.startsWith('*/') && mimeType.split('/')[1] === headerFormat.split('/')[1]) {
109
+ return mimeType
110
+ }
111
+
112
+ if (headerFormat.endsWith('/*') && mimeType.split('/')[0] === headerFormat.split('/')[0]) {
113
+ return mimeType
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Parses q-factor weighting from the accept header to allow letting some mime
121
+ * types take precedence over others.
122
+ *
123
+ * If the q-factor for an acceptable mime representation is omitted it defaults
124
+ * to `1`.
125
+ *
126
+ * All specified values should be in the range 0-1.
127
+ *
128
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept#q
129
+ */
130
+ function parseQFactor (str?: string): number {
131
+ if (str != null) {
132
+ str = str.trim()
133
+ }
134
+
135
+ if (str == null || !str.startsWith('q=')) {
136
+ return 1
137
+ }
138
+
139
+ const factor = parseFloat(str.replace('q=', ''))
140
+
141
+ if (isNaN(factor)) {
142
+ return 0
143
+ }
144
+
145
+ return factor
146
+ }
147
+
148
+ const FORMAT_TO_MIME_TYPE: Record<RequestFormatShorthand, string> = {
149
+ raw: 'application/vnd.ipld.raw',
150
+ car: 'application/vnd.ipld.car',
151
+ 'dag-json': 'application/vnd.ipld.dag-json',
152
+ 'dag-cbor': 'application/vnd.ipld.dag-cbor',
153
+ json: 'application/json',
154
+ cbor: 'application/cbor',
155
+ 'ipns-record': 'application/vnd.ipfs.ipns-record',
156
+ tar: 'application/x-tar'
157
+ }
158
+
159
+ /**
160
+ * Converts a `format=...` query param to a mime type as would be found in the
161
+ * `Accept` header, if a valid mapping is available
162
+ */
163
+ export function queryFormatToAcceptHeader (format?: RequestFormatShorthand): string | undefined {
164
+ if (format != null) {
165
+ return FORMAT_TO_MIME_TYPE[format]
166
+ }
167
+ }
@@ -1,10 +1,11 @@
1
1
  import { walkPath as exporterWalk, type ExporterOptions, type ReadableStorage, type UnixFSEntry } from 'ipfs-unixfs-exporter'
2
+ import type { CID } from 'multiformats/cid'
2
3
 
3
4
  export interface PathWalkerOptions extends ExporterOptions {
4
5
 
5
6
  }
6
7
  export interface PathWalkerResponse {
7
- ipfsRoots: string[]
8
+ ipfsRoots: CID[]
8
9
  terminalElement: UnixFSEntry
9
10
 
10
11
  }
@@ -14,13 +15,11 @@ export interface PathWalkerFn {
14
15
  }
15
16
 
16
17
  export async function walkPath (blockstore: ReadableStorage, path: string, options?: PathWalkerOptions): Promise<PathWalkerResponse> {
17
- const entries: UnixFSEntry[] = []
18
- const ipfsRoots: string[] = []
18
+ const ipfsRoots: CID[] = []
19
19
  let terminalElement: UnixFSEntry | undefined
20
20
 
21
21
  for await (const entry of exporterWalk(path, blockstore, options)) {
22
- entries.push(entry)
23
- ipfsRoots.push(entry.cid.toString())
22
+ ipfsRoots.push(entry.cid)
24
23
  terminalElement = entry
25
24
  }
26
25