@helia/car 5.0.1 → 5.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.
- package/README.md +54 -4
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +4 -4
- package/dist/src/car.d.ts +1 -2
- package/dist/src/car.d.ts.map +1 -1
- package/dist/src/car.js +46 -124
- package/dist/src/car.js.map +1 -1
- package/dist/src/errors.d.ts +8 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +8 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/export-strategies/block-exporter.d.ts +5 -2
- package/dist/src/export-strategies/block-exporter.d.ts.map +1 -1
- package/dist/src/export-strategies/block-exporter.js +9 -3
- package/dist/src/export-strategies/block-exporter.js.map +1 -1
- package/dist/src/export-strategies/subgraph-exporter.d.ts +5 -2
- package/dist/src/export-strategies/subgraph-exporter.d.ts.map +1 -1
- package/dist/src/export-strategies/subgraph-exporter.js +8 -3
- package/dist/src/export-strategies/subgraph-exporter.js.map +1 -1
- package/dist/src/export-strategies/unixfs-exporter.d.ts +10 -4
- package/dist/src/export-strategies/unixfs-exporter.d.ts.map +1 -1
- package/dist/src/export-strategies/unixfs-exporter.js +15 -8
- package/dist/src/export-strategies/unixfs-exporter.js.map +1 -1
- package/dist/src/index.d.ts +64 -12
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +56 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/traversal-strategies/cid-path.d.ts +9 -6
- package/dist/src/traversal-strategies/cid-path.d.ts.map +1 -1
- package/dist/src/traversal-strategies/cid-path.js +35 -12
- package/dist/src/traversal-strategies/cid-path.js.map +1 -1
- package/dist/src/traversal-strategies/graph-search.d.ts +15 -7
- package/dist/src/traversal-strategies/graph-search.d.ts.map +1 -1
- package/dist/src/traversal-strategies/graph-search.js +57 -11
- package/dist/src/traversal-strategies/graph-search.js.map +1 -1
- package/dist/src/traversal-strategies/unixfs-path.d.ts +53 -3
- package/dist/src/traversal-strategies/unixfs-path.d.ts.map +1 -1
- package/dist/src/traversal-strategies/unixfs-path.js +70 -30
- package/dist/src/traversal-strategies/unixfs-path.js.map +1 -1
- package/dist/typedoc-urls.json +1 -0
- package/package.json +4 -2
- package/src/car.ts +54 -163
- package/src/errors.ts +10 -0
- package/src/export-strategies/block-exporter.ts +13 -4
- package/src/export-strategies/subgraph-exporter.ts +13 -4
- package/src/export-strategies/unixfs-exporter.ts +20 -9
- package/src/index.ts +66 -15
- package/src/traversal-strategies/cid-path.ts +43 -13
- package/src/traversal-strategies/graph-search.ts +70 -12
- package/src/traversal-strategies/unixfs-path.ts +77 -41
package/src/car.ts
CHANGED
|
@@ -1,50 +1,22 @@
|
|
|
1
1
|
import { CarWriter } from '@ipld/car'
|
|
2
|
-
import { Queue } from '@libp2p/utils'
|
|
3
2
|
import drain from 'it-drain'
|
|
4
3
|
import map from 'it-map'
|
|
5
|
-
import toBuffer from 'it-to-buffer'
|
|
6
|
-
import { createUnsafe } from 'multiformats/block'
|
|
7
4
|
import { raceSignal } from 'race-signal'
|
|
8
|
-
import {
|
|
5
|
+
import { DAG_PB_CODEC_CODE } from './constants.ts'
|
|
9
6
|
import { SubgraphExporter } from './export-strategies/subgraph-exporter.js'
|
|
10
|
-
import {
|
|
11
|
-
import type { CarComponents, Car as CarInterface, ExportCarOptions
|
|
7
|
+
import { UnixFSExporter } from './index.js'
|
|
8
|
+
import type { CarComponents, Car as CarInterface, ExportCarOptions } from './index.js'
|
|
12
9
|
import type { PutManyBlocksProgressEvents } from '@helia/interface/blocks'
|
|
13
10
|
import type { CarReader } from '@ipld/car'
|
|
14
11
|
import type { AbortOptions, Logger } from '@libp2p/interface'
|
|
15
12
|
import type { CID } from 'multiformats/cid'
|
|
16
13
|
import type { ProgressOptions } from 'progress-events'
|
|
17
14
|
|
|
18
|
-
/**
|
|
19
|
-
* Context for the traversal process.
|
|
20
|
-
*/
|
|
21
|
-
interface TraversalContext {
|
|
22
|
-
currentPath: CID[]
|
|
23
|
-
pathsToTarget: CID[][] | null // collect all target paths
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface WalkDagContext<Strategy> {
|
|
27
|
-
cid: CID
|
|
28
|
-
queue: Queue
|
|
29
|
-
strategy: Strategy
|
|
30
|
-
options?: ExportCarOptions
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface ExportWalkDagContext extends WalkDagContext<ExportStrategy> {
|
|
34
|
-
writer: Pick<CarWriter, 'put'>
|
|
35
|
-
recursive?: boolean
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface TraversalWalkDagContext extends WalkDagContext<TraversalStrategy> {
|
|
39
|
-
traversalContext: TraversalContext
|
|
40
|
-
parentPath: CID[]
|
|
41
|
-
}
|
|
42
|
-
|
|
43
15
|
export class Car implements CarInterface {
|
|
44
16
|
private readonly components: CarComponents
|
|
45
17
|
private readonly log: Logger
|
|
46
18
|
|
|
47
|
-
constructor (components: CarComponents
|
|
19
|
+
constructor (components: CarComponents) {
|
|
48
20
|
this.components = components
|
|
49
21
|
this.log = components.logger.forComponent('helia:car')
|
|
50
22
|
}
|
|
@@ -91,148 +63,67 @@ export class Car implements CarInterface {
|
|
|
91
63
|
}
|
|
92
64
|
|
|
93
65
|
private async _export (root: CID | CID[], writer: Pick<CarWriter, 'put' | 'close'>, options?: ExportCarOptions): Promise<void> {
|
|
94
|
-
const deferred = Promise.withResolvers<Error | void>()
|
|
95
66
|
const roots = Array.isArray(root) ? root : [root]
|
|
96
|
-
|
|
97
|
-
// Create traversal-specific context
|
|
98
|
-
const traversalContext: TraversalContext = {
|
|
99
|
-
currentPath: [],
|
|
100
|
-
pathsToTarget: null
|
|
101
|
-
}
|
|
102
|
-
|
|
103
67
|
const traversalStrategy = options?.traversal
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
// Process the target block (which will recursively export its DAG)
|
|
138
|
-
queue.add(async () => {
|
|
139
|
-
await this.#exportDagNode({ cid: targetCid, queue, writer, strategy: exportStrategy, options })
|
|
140
|
-
})
|
|
141
|
-
.catch((err) => {
|
|
142
|
-
this.log.error('error during queue operation - %e', err)
|
|
68
|
+
|
|
69
|
+
for (const root of roots) {
|
|
70
|
+
const exportStrategy = options?.exporter ?? (root.code === DAG_PB_CODEC_CODE ? new UnixFSExporter() : new SubgraphExporter())
|
|
71
|
+
let current = root
|
|
72
|
+
let underRoot = false
|
|
73
|
+
|
|
74
|
+
if (traversalStrategy != null) {
|
|
75
|
+
for await (const { cid, bytes } of traversalStrategy.traverse(current, this.components.blockstore, this.components.getCodec, options)) {
|
|
76
|
+
this.log.trace('next CID on path to %c is %c', root, cid)
|
|
77
|
+
current = cid
|
|
78
|
+
|
|
79
|
+
// the traversal is under the root we are exporting
|
|
80
|
+
if (root.equals(cid)) {
|
|
81
|
+
underRoot = true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// include the traversal block if we are under the root CID or it has
|
|
85
|
+
// been explicitly requested
|
|
86
|
+
if (underRoot || options?.includeTraversalBlocks === true) {
|
|
87
|
+
// eslint-disable-next-line max-depth
|
|
88
|
+
if (options?.blockFilter?.has(cid.multihash.bytes) === true) {
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// mark as processed
|
|
93
|
+
options?.blockFilter?.add(cid.multihash.bytes)
|
|
94
|
+
|
|
95
|
+
// store block on path to target
|
|
96
|
+
await writer.put({
|
|
97
|
+
cid,
|
|
98
|
+
bytes
|
|
143
99
|
})
|
|
100
|
+
}
|
|
144
101
|
}
|
|
145
|
-
} else {
|
|
146
|
-
// queue is idle, we haven't started exporting yet, and we don't have
|
|
147
|
-
// path(s) to the target(s), so we can't start the export process.
|
|
148
|
-
// this should not happen without a separate error during traversal, but
|
|
149
|
-
// we'll handle it here anyway.
|
|
150
|
-
this.log.trace('no paths to target, skipping export')
|
|
151
|
-
deferred.reject(new Error('Could not traverse to target CID(s)'))
|
|
152
102
|
}
|
|
153
|
-
})
|
|
154
|
-
queue.addEventListener('failure', (evt) => {
|
|
155
|
-
queue.clear()
|
|
156
|
-
deferred.reject(evt.detail.error)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
for (const root of roots) {
|
|
160
|
-
void queue.add(async () => {
|
|
161
|
-
this.log.trace('traversing dag from %c', root)
|
|
162
|
-
await this.#traverseDagNode({ cid: root, queue, strategy: traversalStrategy ?? new GraphSearch(root), traversalContext, parentPath: [], options })
|
|
163
|
-
})
|
|
164
|
-
.catch((err) => {
|
|
165
|
-
this.log.error('error during queue operation - %e', err)
|
|
166
|
-
})
|
|
167
|
-
}
|
|
168
103
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
await writer.close()
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Traverse a DAG and stop when we reach the target node
|
|
179
|
-
*/
|
|
180
|
-
async #traverseDagNode ({ cid, queue, strategy, traversalContext, parentPath, options }: TraversalWalkDagContext): Promise<void> {
|
|
181
|
-
// if we are traversing, we need to gather path(s) to the target(s)
|
|
182
|
-
const currentPath = [...parentPath, cid]
|
|
183
|
-
|
|
184
|
-
if (strategy.isTarget(cid)) {
|
|
185
|
-
traversalContext.pathsToTarget = traversalContext.pathsToTarget ?? []
|
|
186
|
-
traversalContext.pathsToTarget.push([...currentPath])
|
|
187
|
-
this.log.trace('found path to target %c', cid)
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const codec = await this.components.getCodec(cid.code)
|
|
192
|
-
const bytes = await toBuffer(this.components.blockstore.get(cid, options))
|
|
193
|
-
|
|
194
|
-
// we are recursively traversing the dag
|
|
195
|
-
const decodedBlock = createUnsafe({ bytes, cid, codec })
|
|
196
|
-
|
|
197
|
-
for await (const nextCid of strategy.traverse(cid, decodedBlock)) {
|
|
198
|
-
void queue.add(async () => {
|
|
199
|
-
await this.#traverseDagNode({ cid: nextCid, queue, strategy, traversalContext, parentPath: currentPath ?? [], options })
|
|
200
|
-
})
|
|
201
|
-
.catch((err) => {
|
|
202
|
-
this.log.error('error during traversal queue operation - %e', err)
|
|
203
|
-
})
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Use an ExportStrategy to export part of all of a DAG
|
|
209
|
-
*/
|
|
210
|
-
async #exportDagNode ({ cid, queue, writer, strategy, options, recursive = true }: ExportWalkDagContext): Promise<void> {
|
|
211
|
-
if (options?.blockFilter?.has(cid.multihash.bytes) === true) {
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const codec = await this.components.getCodec(cid.code)
|
|
216
|
-
const bytes = await toBuffer(this.components.blockstore.get(cid, options))
|
|
217
|
-
|
|
218
|
-
// Mark as processed
|
|
219
|
-
options?.blockFilter?.add(cid.multihash.bytes)
|
|
104
|
+
for await (const { cid, bytes } of exportStrategy.export(current, this.components.blockstore, this.components.getCodec, options)) {
|
|
105
|
+
if (options?.blockFilter?.has(cid.multihash.bytes) === true) {
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
220
108
|
|
|
221
|
-
|
|
222
|
-
|
|
109
|
+
// skip the export target block if we have already included it during
|
|
110
|
+
// traversal
|
|
111
|
+
if (underRoot && cid.equals(current)) {
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
223
114
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const decodedBlock = createUnsafe({ bytes, cid, codec })
|
|
115
|
+
// mark as processed
|
|
116
|
+
options?.blockFilter?.add(cid.multihash.bytes)
|
|
227
117
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
118
|
+
// write to CAR
|
|
119
|
+
await writer.put({
|
|
120
|
+
cid,
|
|
121
|
+
bytes
|
|
231
122
|
})
|
|
232
|
-
.catch((err) => {
|
|
233
|
-
this.log.error('error during export queue operation - %e', err)
|
|
234
|
-
})
|
|
235
123
|
}
|
|
236
124
|
}
|
|
125
|
+
|
|
126
|
+
// wait for the writer to end
|
|
127
|
+
await writer.close()
|
|
237
128
|
}
|
|
238
129
|
}
|
package/src/errors.ts
CHANGED
|
@@ -6,3 +6,13 @@ export class NotUnixFSError extends Error {
|
|
|
6
6
|
message = 'Not a UnixFS node'
|
|
7
7
|
name = 'NotUnixFSError'
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
export class NotDescendantError extends Error {
|
|
11
|
+
static name = 'NotDescendantError'
|
|
12
|
+
name = 'NotDescendantError'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class InvalidTraversalError extends Error {
|
|
16
|
+
static name = 'InvalidTraversalError'
|
|
17
|
+
name = 'InvalidTraversalError'
|
|
18
|
+
}
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
import toBuffer from 'it-to-buffer'
|
|
2
|
+
import { createUnsafe } from 'multiformats/block'
|
|
1
3
|
import type { ExportStrategy } from '../index.js'
|
|
2
|
-
import type {
|
|
4
|
+
import type { CodecLoader } from '@helia/interface'
|
|
5
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
6
|
+
import type { Blockstore } from 'interface-blockstore'
|
|
7
|
+
import type { BlockView } from 'multiformats'
|
|
3
8
|
import type { CID } from 'multiformats/cid'
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Yields the first block from the first CID and stops
|
|
7
12
|
*/
|
|
8
13
|
export class BlockExporter implements ExportStrategy {
|
|
9
|
-
async * export (cid: CID,
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
async * export (cid: CID, blockstore: Blockstore, getCodec: CodecLoader, options?: AbortOptions): AsyncGenerator<BlockView<unknown, number, number, 0 | 1>, void, undefined> {
|
|
15
|
+
const bytes = await toBuffer(blockstore.get(cid, options))
|
|
16
|
+
yield createUnsafe({
|
|
17
|
+
cid,
|
|
18
|
+
bytes,
|
|
19
|
+
codec: await getCodec(cid.code)
|
|
20
|
+
})
|
|
12
21
|
}
|
|
13
22
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { breadthFirstWalker } from '@helia/utils'
|
|
1
2
|
import type { ExportStrategy } from '../index.js'
|
|
2
|
-
import type {
|
|
3
|
+
import type { CodecLoader } from '@helia/interface'
|
|
4
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
5
|
+
import type { Blockstore } from 'interface-blockstore'
|
|
6
|
+
import type { BlockView } from 'multiformats'
|
|
3
7
|
import type { CID } from 'multiformats/cid'
|
|
4
8
|
|
|
5
9
|
/**
|
|
@@ -10,9 +14,14 @@ import type { CID } from 'multiformats/cid'
|
|
|
10
14
|
* the helia config.
|
|
11
15
|
*/
|
|
12
16
|
export class SubgraphExporter implements ExportStrategy {
|
|
13
|
-
async * export (
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
async * export (cid: CID, blockstore: Blockstore, getCodec: CodecLoader, options?: AbortOptions): AsyncGenerator<BlockView<unknown, number, number, 0 | 1>, void, undefined> {
|
|
18
|
+
const walker = breadthFirstWalker({
|
|
19
|
+
blockstore,
|
|
20
|
+
getCodec
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
for await (const node of walker.walk(cid, options)) {
|
|
24
|
+
yield node.block
|
|
16
25
|
}
|
|
17
26
|
}
|
|
18
27
|
}
|
|
@@ -1,22 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { depthFirstWalker } from '@helia/utils'
|
|
2
|
+
import { DAG_PB_CODEC_CODE, RAW_PB_CODEC_CODE } from '../constants.ts'
|
|
3
|
+
import { NotUnixFSError } from '../errors.ts'
|
|
3
4
|
import type { ExportStrategy } from '../index.js'
|
|
4
|
-
import type {
|
|
5
|
+
import type { CodecLoader } from '@helia/interface'
|
|
6
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
7
|
+
import type { Blockstore } from 'interface-blockstore'
|
|
8
|
+
import type { BlockView } from 'multiformats'
|
|
5
9
|
import type { CID } from 'multiformats/cid'
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* Traverses the DAG depth-first starting at the target CID and yields all
|
|
13
|
+
* encountered blocks.
|
|
14
|
+
*
|
|
15
|
+
* Blocks linked to from the target block are traversed using codecs defined in
|
|
16
|
+
* the helia config.
|
|
10
17
|
*/
|
|
11
18
|
export class UnixFSExporter implements ExportStrategy {
|
|
12
|
-
async * export (cid: CID,
|
|
19
|
+
async * export (cid: CID, blockstore: Blockstore, getCodec: CodecLoader, options?: AbortOptions): AsyncGenerator<BlockView<unknown, number, number, 0 | 1>, void, undefined> {
|
|
13
20
|
if (cid.code !== DAG_PB_CODEC_CODE && cid.code !== RAW_PB_CODEC_CODE) {
|
|
14
21
|
throw new NotUnixFSError('Target CID was not UnixFS - use the SubGraphExporter to export arbitrary graphs')
|
|
15
22
|
}
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
const walker = depthFirstWalker({
|
|
25
|
+
blockstore,
|
|
26
|
+
getCodec
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
for await (const node of walker.walk(cid, options)) {
|
|
30
|
+
yield node.block
|
|
20
31
|
}
|
|
21
32
|
}
|
|
22
33
|
}
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
* const c = car(helia)
|
|
24
24
|
* const out = nodeFs.createWriteStream('example.car')
|
|
25
25
|
*
|
|
26
|
-
* for await (const buf of c.export(cid
|
|
26
|
+
* for await (const buf of c.export(cid, {
|
|
27
|
+
* signal: AbortSignal.timeout(5_000)
|
|
28
|
+
* })) {
|
|
27
29
|
* out.write(buf)
|
|
28
30
|
* }
|
|
29
31
|
*
|
|
@@ -32,6 +34,14 @@
|
|
|
32
34
|
*
|
|
33
35
|
* @example Exporting a part of a UnixFS DAG as a CAR file
|
|
34
36
|
*
|
|
37
|
+
* Here the graph traversal will start at `root` and include the blocks for
|
|
38
|
+
* `root`, `/foo`, `/bar`, and all the blocks that make up `baz.txt`.
|
|
39
|
+
*
|
|
40
|
+
* If there are other files/directories in the UnixFS DAG under `root`, they
|
|
41
|
+
* will not be included.
|
|
42
|
+
*
|
|
43
|
+
* `root` will be the only entry in the CAR file roots.
|
|
44
|
+
*
|
|
35
45
|
* ```typescript
|
|
36
46
|
* import { createHelia } from 'helia'
|
|
37
47
|
* import { car, UnixFSPath } from '@helia/car'
|
|
@@ -39,12 +49,13 @@
|
|
|
39
49
|
* import nodeFs from 'node:fs'
|
|
40
50
|
*
|
|
41
51
|
* const helia = await createHelia()
|
|
42
|
-
* const
|
|
52
|
+
* const root = CID.parse('QmFoo...')
|
|
43
53
|
*
|
|
44
54
|
* const c = car(helia)
|
|
45
55
|
* const out = nodeFs.createWriteStream('example.car')
|
|
46
56
|
*
|
|
47
|
-
* for await (const buf of c.export(
|
|
57
|
+
* for await (const buf of c.export(root, {
|
|
58
|
+
* signal: AbortSignal.timeout(5_000),
|
|
48
59
|
* traversal: new UnixFSPath('/foo/bar/baz.txt')
|
|
49
60
|
* })) {
|
|
50
61
|
* out.write(buf)
|
|
@@ -53,6 +64,43 @@
|
|
|
53
64
|
* out.end()
|
|
54
65
|
* ```
|
|
55
66
|
*
|
|
67
|
+
* @example Including traversal path above the root in a CAR
|
|
68
|
+
*
|
|
69
|
+
* The `includeTraversalBlocks` option will include the traversal blocks in the
|
|
70
|
+
* CAR when they would otherwise be excluded (for example when the traversal
|
|
71
|
+
* starts in a parent of the export root).
|
|
72
|
+
*
|
|
73
|
+
* Here `baz` is the CID for `baz.txt`.
|
|
74
|
+
*
|
|
75
|
+
* The CAR file will include the blocks for `parent`, `/foo`, `/bar`, and
|
|
76
|
+
* `/baz.txt`.
|
|
77
|
+
*
|
|
78
|
+
* `baz` will be the only entry in the CAR file roots.
|
|
79
|
+
*
|
|
80
|
+
* ```typescript
|
|
81
|
+
* import { createHelia } from 'helia'
|
|
82
|
+
* import { car, UnixFSPath } from '@helia/car'
|
|
83
|
+
* import { CID } from 'multiformats/cid'
|
|
84
|
+
* import nodeFs from 'node:fs'
|
|
85
|
+
*
|
|
86
|
+
* const helia = await createHelia()
|
|
87
|
+
* const parent = CID.parse('QmFoo...')
|
|
88
|
+
* const baz = CID.parse('QmBar...')
|
|
89
|
+
*
|
|
90
|
+
* const c = car(helia)
|
|
91
|
+
* const out = nodeFs.createWriteStream('example.car')
|
|
92
|
+
*
|
|
93
|
+
* for await (const buf of c.export(baz, {
|
|
94
|
+
* signal: AbortSignal.timeout(5_000),
|
|
95
|
+
* traversal: new UnixFSPath(parent, '/foo/bar/baz.txt'),
|
|
96
|
+
* includeTraversalBlocks: true
|
|
97
|
+
* })) {
|
|
98
|
+
* out.write(buf)
|
|
99
|
+
* }
|
|
100
|
+
*
|
|
101
|
+
* out.end()
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
56
104
|
* @example Importing all blocks from a CAR file
|
|
57
105
|
*
|
|
58
106
|
* ```typescript
|
|
@@ -72,7 +120,9 @@
|
|
|
72
120
|
* const reader = await CarReader.fromIterable(inStream)
|
|
73
121
|
*
|
|
74
122
|
* const c = car(helia)
|
|
75
|
-
* await c.import(reader
|
|
123
|
+
* await c.import(reader, {
|
|
124
|
+
* signal: AbortSignal.timeout(5_000)
|
|
125
|
+
* })
|
|
76
126
|
* ```
|
|
77
127
|
*/
|
|
78
128
|
|
|
@@ -102,13 +152,7 @@ export interface TraversalStrategy {
|
|
|
102
152
|
/**
|
|
103
153
|
* Traverse the DAG and yield the next CID to traverse
|
|
104
154
|
*/
|
|
105
|
-
traverse
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Returns true if the current CID is the target and we should switch to the
|
|
109
|
-
* export strategy
|
|
110
|
-
*/
|
|
111
|
-
isTarget(cid: CID): boolean
|
|
155
|
+
traverse(root: CID, blockstore: Blockstore, getCodec: CodecLoader, options?: AbortOptions): AsyncGenerator<BlockView<unknown, number, number, 0 | 1>, void, undefined>
|
|
112
156
|
}
|
|
113
157
|
|
|
114
158
|
/**
|
|
@@ -121,14 +165,13 @@ export interface ExportStrategy {
|
|
|
121
165
|
/**
|
|
122
166
|
* Export the DAG and yield the next CID to traverse
|
|
123
167
|
*/
|
|
124
|
-
export
|
|
168
|
+
export(cid: CID, blockstore: Blockstore, getCodec: CodecLoader, options?: AbortOptions): AsyncGenerator<BlockView<unknown, number, number, 0 | 1>, void, undefined>
|
|
125
169
|
}
|
|
126
170
|
|
|
127
171
|
export * from './export-strategies/index.js'
|
|
128
172
|
export * from './traversal-strategies/index.js'
|
|
129
173
|
|
|
130
174
|
export interface ExportCarOptions extends AbortOptions, ProgressOptions<GetBlockProgressEvents>, ProviderOptions {
|
|
131
|
-
|
|
132
175
|
/**
|
|
133
176
|
* If true, the blockstore will not do any network requests.
|
|
134
177
|
*
|
|
@@ -153,6 +196,14 @@ export interface ExportCarOptions extends AbortOptions, ProgressOptions<GetBlock
|
|
|
153
196
|
* blocks included in the exported car file. (e.g. https://specs.ipfs.tech/http-gateways/trustless-gateway/#dag-scope-request-query-parameter)
|
|
154
197
|
*/
|
|
155
198
|
exporter?: ExportStrategy
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* If `true`, and the traversal strategy starts above the root, include the
|
|
202
|
+
* traversed blocks in the CAR file before the root and subsequent blocks
|
|
203
|
+
*
|
|
204
|
+
* @default false
|
|
205
|
+
*/
|
|
206
|
+
includeTraversalBlocks?: boolean
|
|
156
207
|
}
|
|
157
208
|
|
|
158
209
|
/**
|
|
@@ -208,6 +259,6 @@ export interface Car {
|
|
|
208
259
|
/**
|
|
209
260
|
* Create a {@link Car} instance for use with {@link https://github.com/ipfs/helia Helia}
|
|
210
261
|
*/
|
|
211
|
-
export function car (helia: CarComponents
|
|
212
|
-
return new CarClass(helia
|
|
262
|
+
export function car (helia: CarComponents): Car {
|
|
263
|
+
return new CarClass(helia)
|
|
213
264
|
}
|
|
@@ -1,29 +1,59 @@
|
|
|
1
|
+
import toBuffer from 'it-to-buffer'
|
|
2
|
+
import { createUnsafe } from 'multiformats/block'
|
|
3
|
+
import { InvalidTraversalError, NotDescendantError } from '../errors.js'
|
|
1
4
|
import type { TraversalStrategy } from '../index.js'
|
|
2
|
-
import type {
|
|
5
|
+
import type { CodecLoader } from '@helia/interface'
|
|
6
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
7
|
+
import type { Blockstore } from 'interface-blockstore'
|
|
8
|
+
import type { BlockView } from 'multiformats'
|
|
3
9
|
import type { CID } from 'multiformats/cid'
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Simple strategy that traverses a known path to a target CID.
|
|
7
13
|
*
|
|
8
14
|
* All this strategy does is yield the next CID in the known path.
|
|
15
|
+
*
|
|
16
|
+
* The path should end with the CID to be exported
|
|
9
17
|
*/
|
|
10
18
|
export class CIDPath implements TraversalStrategy {
|
|
11
|
-
private readonly
|
|
12
|
-
private readonly target: CID
|
|
19
|
+
private readonly path: CID[]
|
|
13
20
|
|
|
14
|
-
constructor (
|
|
15
|
-
this.
|
|
16
|
-
this.target = pathToTarget[pathToTarget.length - 1]
|
|
21
|
+
constructor (path: CID[]) {
|
|
22
|
+
this.path = path
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
async * traverse (root: CID, blockstore: Blockstore, getCodec: CodecLoader, options?: AbortOptions): AsyncGenerator<BlockView<unknown, number, number, 0 | 1>, void, undefined> {
|
|
26
|
+
if (!this.path.some(c => c.equals(root))) {
|
|
27
|
+
throw new InvalidTraversalError(`CIDPath traversal must include ${root}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let parentBlock: BlockView<unknown, number, number, 0 | 1> | undefined
|
|
31
|
+
|
|
32
|
+
for (const cid of this.path) {
|
|
33
|
+
if (parentBlock != null) {
|
|
34
|
+
let isChild = false
|
|
35
|
+
|
|
36
|
+
for (const [, child] of parentBlock.links()) {
|
|
37
|
+
if (child.equals(cid)) {
|
|
38
|
+
isChild = true
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!isChild) {
|
|
44
|
+
throw new NotDescendantError(`${cid} is not a child of ${parentBlock.cid}`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
22
47
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
48
|
+
const bytes = await toBuffer(blockstore.get(cid, options))
|
|
49
|
+
const block = createUnsafe({
|
|
50
|
+
cid,
|
|
51
|
+
bytes,
|
|
52
|
+
codec: await getCodec(cid.code)
|
|
53
|
+
})
|
|
26
54
|
|
|
27
|
-
|
|
55
|
+
parentBlock = block
|
|
56
|
+
yield block
|
|
57
|
+
}
|
|
28
58
|
}
|
|
29
59
|
}
|