@helia/car 4.0.3 → 4.0.4-bb2ab74
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 +34 -17
- package/dist/index.min.js +2 -1
- package/dist/index.min.js.map +7 -0
- package/dist/src/car.d.ts +17 -0
- package/dist/src/car.d.ts.map +1 -0
- package/dist/src/car.js +162 -0
- package/dist/src/car.js.map +1 -0
- package/dist/src/constants.d.ts +10 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +10 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +9 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/export-strategies/block-exporter.d.ts +10 -0
- package/dist/src/export-strategies/block-exporter.d.ts.map +1 -0
- package/dist/src/export-strategies/block-exporter.js +12 -0
- package/dist/src/export-strategies/block-exporter.js.map +1 -0
- package/dist/src/export-strategies/index.d.ts +4 -0
- package/dist/src/export-strategies/index.d.ts.map +1 -0
- package/dist/src/export-strategies/index.js +4 -0
- package/dist/src/export-strategies/index.js.map +1 -0
- package/dist/src/export-strategies/subgraph-exporter.d.ts +14 -0
- package/dist/src/export-strategies/subgraph-exporter.d.ts.map +1 -0
- package/dist/src/export-strategies/subgraph-exporter.js +17 -0
- package/dist/src/export-strategies/subgraph-exporter.js.map +1 -0
- package/dist/src/export-strategies/unixfs-exporter.d.ts +11 -0
- package/dist/src/export-strategies/unixfs-exporter.d.ts.map +1 -0
- package/dist/src/export-strategies/unixfs-exporter.js +18 -0
- package/dist/src/export-strategies/unixfs-exporter.js.map +1 -0
- package/dist/src/index.d.ts +89 -24
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +38 -95
- package/dist/src/index.js.map +1 -1
- package/dist/src/traversal-strategies/cid-path.d.ts +16 -0
- package/dist/src/traversal-strategies/cid-path.d.ts.map +1 -0
- package/dist/src/traversal-strategies/cid-path.js +22 -0
- package/dist/src/traversal-strategies/cid-path.js.map +1 -0
- package/dist/src/traversal-strategies/graph-search.d.ts +14 -0
- package/dist/src/traversal-strategies/graph-search.d.ts.map +1 -0
- package/dist/src/traversal-strategies/graph-search.js +19 -0
- package/dist/src/traversal-strategies/graph-search.js.map +1 -0
- package/dist/src/traversal-strategies/index.d.ts +4 -0
- package/dist/src/traversal-strategies/index.d.ts.map +1 -0
- package/dist/src/traversal-strategies/index.js +4 -0
- package/dist/src/traversal-strategies/index.js.map +1 -0
- package/dist/src/traversal-strategies/unixfs-path.d.ts +13 -0
- package/dist/src/traversal-strategies/unixfs-path.d.ts.map +1 -0
- package/dist/src/traversal-strategies/unixfs-path.js +42 -0
- package/dist/src/traversal-strategies/unixfs-path.js.map +1 -0
- package/package.json +12 -6
- package/src/car.ts +218 -0
- package/src/constants.ts +11 -0
- package/src/errors.ts +8 -0
- package/src/export-strategies/block-exporter.ts +13 -0
- package/src/export-strategies/index.ts +3 -0
- package/src/export-strategies/subgraph-exporter.ts +18 -0
- package/src/export-strategies/unixfs-exporter.ts +22 -0
- package/src/index.ts +99 -119
- package/src/traversal-strategies/cid-path.ts +29 -0
- package/src/traversal-strategies/graph-search.ts +25 -0
- package/src/traversal-strategies/index.ts +3 -0
- package/src/traversal-strategies/unixfs-path.ts +58 -0
- package/dist/typedoc-urls.json +0 -10
package/dist/src/index.js
CHANGED
|
@@ -1,39 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @packageDocumentation
|
|
3
3
|
*
|
|
4
|
-
* `@helia/car` provides `import` and `export` methods to read/write Car files
|
|
4
|
+
* `@helia/car` provides `import` and `export` methods to read/write Car files
|
|
5
|
+
* to {@link https://github.com/ipfs/helia Helia}'s blockstore.
|
|
5
6
|
*
|
|
6
7
|
* See the {@link Car} interface for all available operations.
|
|
7
8
|
*
|
|
8
|
-
* By default it supports `dag-pb`, `dag-cbor`, `dag-json` and `raw` CIDs, more
|
|
9
|
+
* By default it supports `dag-pb`, `dag-cbor`, `dag-json` and `raw` CIDs, more
|
|
10
|
+
* esoteric DAG walkers can be passed as an init option.
|
|
9
11
|
*
|
|
10
12
|
* @example Exporting a DAG as a CAR file
|
|
11
13
|
*
|
|
12
14
|
* ```typescript
|
|
13
15
|
* import { createHelia } from 'helia'
|
|
14
|
-
* import { unixfs } from '@helia/unixfs'
|
|
15
16
|
* import { car } from '@helia/car'
|
|
16
|
-
* import {
|
|
17
|
-
* import { Readable } from 'node:stream'
|
|
17
|
+
* import { CID } from 'multiformats/cid'
|
|
18
18
|
* import nodeFs from 'node:fs'
|
|
19
19
|
*
|
|
20
|
-
* const helia = await createHelia(
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* const
|
|
20
|
+
* const helia = await createHelia()
|
|
21
|
+
* const cid = CID.parse('QmFoo...')
|
|
22
|
+
*
|
|
23
|
+
* const c = car(helia)
|
|
24
|
+
* const out = nodeFs.createWriteStream('example.car')
|
|
25
|
+
*
|
|
26
|
+
* for await (const buf of c.stream(cid)) {
|
|
27
|
+
* out.write(buf)
|
|
28
|
+
* }
|
|
24
29
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
30
|
+
* out.end()
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example Exporting a part of a UnixFS DAG as a CAR file
|
|
34
|
+
*
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { createHelia } from 'helia'
|
|
37
|
+
* import { car, UnixFSPath } from '@helia/car'
|
|
38
|
+
* import { CID } from 'multiformats/cid'
|
|
39
|
+
* import nodeFs from 'node:fs'
|
|
40
|
+
*
|
|
41
|
+
* const helia = await createHelia()
|
|
42
|
+
* const cid = CID.parse('QmFoo...')
|
|
27
43
|
*
|
|
28
|
-
* // export it as a Car
|
|
29
44
|
* const c = car(helia)
|
|
30
|
-
* const
|
|
45
|
+
* const out = nodeFs.createWriteStream('example.car')
|
|
31
46
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
47
|
+
* for await (const buf of c.stream(cid, {
|
|
48
|
+
* traversal: new UnixFSPath('/foo/bar/baz.txt')
|
|
49
|
+
* })) {
|
|
50
|
+
* out.write(buf)
|
|
51
|
+
* }
|
|
34
52
|
*
|
|
35
|
-
*
|
|
36
|
-
* await c.export(cid, writer)
|
|
53
|
+
* out.end()
|
|
37
54
|
* ```
|
|
38
55
|
*
|
|
39
56
|
* @example Importing all blocks from a CAR file
|
|
@@ -58,87 +75,13 @@
|
|
|
58
75
|
* await c.import(reader)
|
|
59
76
|
* ```
|
|
60
77
|
*/
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
import { createUnsafe } from 'multiformats/block';
|
|
65
|
-
import defer from 'p-defer';
|
|
66
|
-
import PQueue from 'p-queue';
|
|
67
|
-
const DAG_WALK_QUEUE_CONCURRENCY = 1;
|
|
68
|
-
class DefaultCar {
|
|
69
|
-
components;
|
|
70
|
-
constructor(components, init) {
|
|
71
|
-
this.components = components;
|
|
72
|
-
}
|
|
73
|
-
async import(reader, options) {
|
|
74
|
-
await drain(this.components.blockstore.putMany(map(reader.blocks(), ({ cid, bytes }) => ({ cid, block: bytes })), options));
|
|
75
|
-
}
|
|
76
|
-
async export(root, writer, options) {
|
|
77
|
-
const deferred = defer();
|
|
78
|
-
const roots = Array.isArray(root) ? root : [root];
|
|
79
|
-
// use a queue to walk the DAG instead of recursion so we can traverse very large DAGs
|
|
80
|
-
const queue = new PQueue({
|
|
81
|
-
concurrency: DAG_WALK_QUEUE_CONCURRENCY
|
|
82
|
-
});
|
|
83
|
-
queue.on('idle', () => {
|
|
84
|
-
deferred.resolve();
|
|
85
|
-
});
|
|
86
|
-
queue.on('error', (err) => {
|
|
87
|
-
queue.clear();
|
|
88
|
-
deferred.reject(err);
|
|
89
|
-
});
|
|
90
|
-
for (const root of roots) {
|
|
91
|
-
void queue.add(async () => {
|
|
92
|
-
await this.#walkDag(root, queue, async (cid, bytes) => {
|
|
93
|
-
// if a filter has been passed, skip blocks that have already been written
|
|
94
|
-
if (options?.blockFilter?.has(cid.multihash.bytes) === true) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
options?.blockFilter?.add(cid.multihash.bytes);
|
|
98
|
-
await writer.put({ cid, bytes });
|
|
99
|
-
}, options);
|
|
100
|
-
})
|
|
101
|
-
.catch(() => { });
|
|
102
|
-
}
|
|
103
|
-
// wait for the writer to end
|
|
104
|
-
try {
|
|
105
|
-
await deferred.promise;
|
|
106
|
-
}
|
|
107
|
-
finally {
|
|
108
|
-
await writer.close();
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async *stream(root, options) {
|
|
112
|
-
const { writer, out } = CarWriter.create(root);
|
|
113
|
-
// has to be done async so we write to `writer` and read from `out` at the
|
|
114
|
-
// same time
|
|
115
|
-
this.export(root, writer, options)
|
|
116
|
-
.catch(() => { });
|
|
117
|
-
for await (const buf of out) {
|
|
118
|
-
yield buf;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Walk the DAG behind the passed CID, ensure all blocks are present in the blockstore
|
|
123
|
-
* and update the pin count for them
|
|
124
|
-
*/
|
|
125
|
-
async #walkDag(cid, queue, withBlock, options) {
|
|
126
|
-
const codec = await this.components.getCodec(cid.code);
|
|
127
|
-
const bytes = await this.components.blockstore.get(cid, options);
|
|
128
|
-
await withBlock(cid, bytes);
|
|
129
|
-
const block = createUnsafe({ bytes, cid, codec });
|
|
130
|
-
// walk dag, ensure all blocks are present
|
|
131
|
-
for await (const [, cid] of block.links()) {
|
|
132
|
-
void queue.add(async () => {
|
|
133
|
-
await this.#walkDag(cid, queue, withBlock, options);
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
78
|
+
import { Car as CarClass } from './car.js';
|
|
79
|
+
export * from './export-strategies/index.js';
|
|
80
|
+
export * from './traversal-strategies/index.js';
|
|
138
81
|
/**
|
|
139
82
|
* Create a {@link Car} instance for use with {@link https://github.com/ipfs/helia Helia}
|
|
140
83
|
*/
|
|
141
84
|
export function car(helia, init = {}) {
|
|
142
|
-
return new
|
|
85
|
+
return new CarClass(helia, init);
|
|
143
86
|
}
|
|
144
87
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AAEH,OAAO,EAAE,GAAG,IAAI,QAAQ,EAAE,MAAM,UAAU,CAAA;AAgD1C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,iCAAiC,CAAA;AAgH/C;;GAEG;AACH,MAAM,UAAU,GAAG,CAAE,KAAoB,EAAE,OAAY,EAAE;IACvD,OAAO,IAAI,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;AAClC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TraversalStrategy } from '../index.js';
|
|
2
|
+
import type { BlockView } from 'multiformats/block/interface';
|
|
3
|
+
import type { CID } from 'multiformats/cid';
|
|
4
|
+
/**
|
|
5
|
+
* Simple strategy that traverses a known path to a target CID.
|
|
6
|
+
*
|
|
7
|
+
* All this strategy does is yield the next CID in the known path.
|
|
8
|
+
*/
|
|
9
|
+
export declare class CIDPath implements TraversalStrategy {
|
|
10
|
+
private readonly pathToTarget;
|
|
11
|
+
private readonly target;
|
|
12
|
+
constructor(pathToTarget: CID[]);
|
|
13
|
+
isTarget(cid: CID): boolean;
|
|
14
|
+
traverse<T extends BlockView<any, any, any, 0 | 1>>(cid: CID, _block?: T): AsyncGenerator<CID, void, undefined>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=cid-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cid-path.d.ts","sourceRoot":"","sources":["../../../src/traversal-strategies/cid-path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAE3C;;;;GAIG;AACH,qBAAa,OAAQ,YAAW,iBAAiB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAO;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAK;gBAEf,YAAY,EAAE,GAAG,EAAE;IAKhC,QAAQ,CAAE,GAAG,EAAE,GAAG,GAAG,OAAO;IAIpB,QAAQ,CAAE,CAAC,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC;CAMzH"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple strategy that traverses a known path to a target CID.
|
|
3
|
+
*
|
|
4
|
+
* All this strategy does is yield the next CID in the known path.
|
|
5
|
+
*/
|
|
6
|
+
export class CIDPath {
|
|
7
|
+
pathToTarget;
|
|
8
|
+
target;
|
|
9
|
+
constructor(pathToTarget) {
|
|
10
|
+
this.pathToTarget = pathToTarget;
|
|
11
|
+
this.target = pathToTarget[pathToTarget.length - 1];
|
|
12
|
+
}
|
|
13
|
+
isTarget(cid) {
|
|
14
|
+
return this.target.equals(cid);
|
|
15
|
+
}
|
|
16
|
+
async *traverse(cid, _block) {
|
|
17
|
+
const givenCidIndex = this.pathToTarget.indexOf(cid);
|
|
18
|
+
const nextCid = this.pathToTarget[givenCidIndex + 1];
|
|
19
|
+
yield nextCid;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=cid-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cid-path.js","sourceRoot":"","sources":["../../../src/traversal-strategies/cid-path.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,OAAO,OAAO;IACD,YAAY,CAAO;IACnB,MAAM,CAAK;IAE5B,YAAa,YAAmB;QAC9B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;QAChC,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACrD,CAAC;IAED,QAAQ,CAAE,GAAQ;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAChC,CAAC;IAED,KAAK,CAAC,CAAE,QAAQ,CAA6C,GAAQ,EAAE,MAAU;QAC/E,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACpD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,aAAa,GAAG,CAAC,CAAC,CAAA;QAEpD,MAAM,OAAO,CAAA;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TraversalStrategy } from '../index.js';
|
|
2
|
+
import type { BlockView } from 'multiformats/block/interface';
|
|
3
|
+
import type { CID } from 'multiformats/cid';
|
|
4
|
+
/**
|
|
5
|
+
* A traversal strategy that performs a breadth-first search (so as to not load blocks unnecessarily) looking for a
|
|
6
|
+
* target CID. Traversal stops when we reach the target CID or run out of nodes.
|
|
7
|
+
*/
|
|
8
|
+
export declare class GraphSearch implements TraversalStrategy {
|
|
9
|
+
private readonly target;
|
|
10
|
+
constructor(target: CID);
|
|
11
|
+
isTarget(cid: CID): boolean;
|
|
12
|
+
traverse<T extends BlockView<any, any, any, 0 | 1>>(cid: CID, block: T): AsyncGenerator<CID, void, undefined>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=graph-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-search.d.ts","sourceRoot":"","sources":["../../../src/traversal-strategies/graph-search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAE3C;;;GAGG;AACH,qBAAa,WAAY,YAAW,iBAAiB;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAK;gBAEf,MAAM,EAAE,GAAG;IAIxB,QAAQ,CAAE,GAAG,EAAE,GAAG,GAAG,OAAO;IAIpB,QAAQ,CAAE,CAAC,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC;CAKvH"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A traversal strategy that performs a breadth-first search (so as to not load blocks unnecessarily) looking for a
|
|
3
|
+
* target CID. Traversal stops when we reach the target CID or run out of nodes.
|
|
4
|
+
*/
|
|
5
|
+
export class GraphSearch {
|
|
6
|
+
target;
|
|
7
|
+
constructor(target) {
|
|
8
|
+
this.target = target;
|
|
9
|
+
}
|
|
10
|
+
isTarget(cid) {
|
|
11
|
+
return this.target.equals(cid);
|
|
12
|
+
}
|
|
13
|
+
async *traverse(cid, block) {
|
|
14
|
+
for await (const [, linkedCid] of block.links()) {
|
|
15
|
+
yield linkedCid;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=graph-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-search.js","sourceRoot":"","sources":["../../../src/traversal-strategies/graph-search.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,OAAO,WAAW;IACL,MAAM,CAAK;IAE5B,YAAa,MAAW;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,QAAQ,CAAE,GAAQ;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAChC,CAAC;IAED,KAAK,CAAC,CAAE,QAAQ,CAA6C,GAAQ,EAAE,KAAQ;QAC7E,IAAI,KAAK,EAAE,MAAM,CAAC,EAAE,SAAS,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;YAChD,MAAM,SAAS,CAAA;QACjB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/traversal-strategies/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/traversal-strategies/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TraversalStrategy } from '../index.js';
|
|
2
|
+
import type { BlockView } from 'multiformats/block/interface';
|
|
3
|
+
import type { CID } from 'multiformats/cid';
|
|
4
|
+
/**
|
|
5
|
+
* Traverses a DAG containing UnixFS directories
|
|
6
|
+
*/
|
|
7
|
+
export declare class UnixFSPath implements TraversalStrategy {
|
|
8
|
+
private readonly path;
|
|
9
|
+
constructor(path: string);
|
|
10
|
+
isTarget(): boolean;
|
|
11
|
+
traverse<T extends BlockView<any, any, any, 0 | 1>>(cid: CID, block: T): AsyncGenerator<CID, void, undefined>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=unixfs-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unixfs-path.d.ts","sourceRoot":"","sources":["../../../src/traversal-strategies/unixfs-path.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAE3C;;GAEG;AACH,qBAAa,UAAW,YAAW,iBAAiB;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAU;gBAElB,IAAI,EAAE,MAAM;IAKzB,QAAQ,IAAK,OAAO;IAIZ,QAAQ,CAAE,CAAC,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC;CAkCvH"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { decode } from '@ipld/dag-pb';
|
|
2
|
+
import { UnixFS } from 'ipfs-unixfs';
|
|
3
|
+
import { DAG_PB_CODEC_CODE } from '../constants.js';
|
|
4
|
+
import { NotUnixFSError } from '../errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Traverses a DAG containing UnixFS directories
|
|
7
|
+
*/
|
|
8
|
+
export class UnixFSPath {
|
|
9
|
+
path;
|
|
10
|
+
constructor(path) {
|
|
11
|
+
// "/foo/bar/baz.txt" -> ['foo', 'bar', 'baz.txt']
|
|
12
|
+
this.path = path.replace(/^\//, '').split('/');
|
|
13
|
+
}
|
|
14
|
+
isTarget() {
|
|
15
|
+
return this.path.length === 0;
|
|
16
|
+
}
|
|
17
|
+
async *traverse(cid, block) {
|
|
18
|
+
if (cid.code !== DAG_PB_CODEC_CODE) {
|
|
19
|
+
throw new NotUnixFSError('Target CID is not UnixFS');
|
|
20
|
+
}
|
|
21
|
+
const segment = this.path.shift();
|
|
22
|
+
if (segment == null) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const pb = decode(block.bytes);
|
|
26
|
+
if (pb.Data == null) {
|
|
27
|
+
throw new NotUnixFSError('Target CID has no UnixFS data in decoded bytes');
|
|
28
|
+
}
|
|
29
|
+
const unixfs = UnixFS.unmarshal(pb.Data);
|
|
30
|
+
if (unixfs.type === 'directory') {
|
|
31
|
+
const link = pb.Links.filter(link => link.Name === segment).pop();
|
|
32
|
+
if (link == null) {
|
|
33
|
+
throw new NotUnixFSError(`Target CID directory has no link with name ${segment}`);
|
|
34
|
+
}
|
|
35
|
+
yield link.Hash;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// TODO: HAMT support
|
|
39
|
+
throw new NotUnixFSError('Target CID is not a UnixFS directory');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=unixfs-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unixfs-path.js","sourceRoot":"","sources":["../../../src/traversal-strategies/unixfs-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAK7C;;GAEG;AACH,MAAM,OAAO,UAAU;IACJ,IAAI,CAAU;IAE/B,YAAa,IAAY;QACvB,kDAAkD;QAClD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAChD,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAA;IAC/B,CAAC;IAED,KAAK,CAAC,CAAE,QAAQ,CAA6C,GAAQ,EAAE,KAAQ;QAC7E,IAAI,GAAG,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACnC,MAAM,IAAI,cAAc,CAAC,0BAA0B,CAAC,CAAA;QACtD,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;QAEjC,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACpB,OAAM;QACR,CAAC;QAED,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAE9B,IAAI,EAAE,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,cAAc,CAAC,gDAAgD,CAAC,CAAA;QAC5E,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;QAExC,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAChC,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,GAAG,EAAE,CAAA;YAEjE,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;gBACjB,MAAM,IAAI,cAAc,CAAC,8CAA8C,OAAO,EAAE,CAAC,CAAA;YACnF,CAAC;YAED,MAAM,IAAI,CAAC,IAAI,CAAA;YACf,OAAM;QACR,CAAC;QAED,qBAAqB;QAErB,MAAM,IAAI,cAAc,CAAC,sCAAsC,CAAC,CAAA;IAClE,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helia/car",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4-bb2ab74",
|
|
4
4
|
"description": "Import/export car files from Helia",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"homepage": "https://github.com/ipfs/helia/tree/main/packages/car#readme",
|
|
@@ -54,11 +54,13 @@
|
|
|
54
54
|
"test:electron-main": "aegir test -t electron-main"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@helia/interface": "
|
|
57
|
+
"@helia/interface": "5.2.1-bb2ab74",
|
|
58
58
|
"@ipld/car": "^5.3.3",
|
|
59
|
+
"@ipld/dag-pb": "^4.1.3",
|
|
59
60
|
"@libp2p/interface": "^2.2.1",
|
|
60
61
|
"@libp2p/utils": "^6.2.1",
|
|
61
62
|
"interface-blockstore": "^5.3.1",
|
|
63
|
+
"ipfs-unixfs": "^11.2.1",
|
|
62
64
|
"it-drain": "^3.0.7",
|
|
63
65
|
"it-map": "^3.1.1",
|
|
64
66
|
"multiformats": "^13.3.1",
|
|
@@ -67,14 +69,18 @@
|
|
|
67
69
|
"progress-events": "^1.0.1"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
|
-
"@helia/mfs": "
|
|
71
|
-
"@helia/unixfs": "
|
|
72
|
-
"@ipld/dag-
|
|
72
|
+
"@helia/mfs": "5.0.0-bb2ab74",
|
|
73
|
+
"@helia/unixfs": "5.0.0-bb2ab74",
|
|
74
|
+
"@ipld/dag-cbor": "^9.2.2",
|
|
75
|
+
"@libp2p/logger": "^5.1.15",
|
|
73
76
|
"aegir": "^45.1.1",
|
|
74
77
|
"blockstore-core": "^5.0.2",
|
|
75
78
|
"datastore-core": "^10.0.2",
|
|
76
79
|
"ipfs-unixfs-importer": "^15.3.1",
|
|
77
|
-
"it-
|
|
80
|
+
"it-foreach": "^2.1.2",
|
|
81
|
+
"it-length": "^3.0.7",
|
|
82
|
+
"it-to-buffer": "^4.0.7",
|
|
83
|
+
"sinon": "^19.0.2"
|
|
78
84
|
},
|
|
79
85
|
"sideEffects": false
|
|
80
86
|
}
|
package/src/car.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { CarWriter } from '@ipld/car'
|
|
2
|
+
import drain from 'it-drain'
|
|
3
|
+
import map from 'it-map'
|
|
4
|
+
import { createUnsafe } from 'multiformats/block'
|
|
5
|
+
import { type CID } from 'multiformats/cid'
|
|
6
|
+
import defer from 'p-defer'
|
|
7
|
+
import PQueue from 'p-queue'
|
|
8
|
+
import { DAG_WALK_QUEUE_CONCURRENCY } from './constants.js'
|
|
9
|
+
import { SubgraphExporter } from './export-strategies/subgraph-exporter.js'
|
|
10
|
+
import { GraphSearch } from './traversal-strategies/graph-search.js'
|
|
11
|
+
import type { CarComponents, Car as CarInterface, ExportCarOptions, ExportStrategy, TraversalStrategy } from './index.js'
|
|
12
|
+
import type { PutManyBlocksProgressEvents } from '@helia/interface/blocks'
|
|
13
|
+
import type { CarReader } from '@ipld/car'
|
|
14
|
+
import type { AbortOptions, Logger } from '@libp2p/interface'
|
|
15
|
+
import type { ProgressOptions } from 'progress-events'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Context for the traversal process.
|
|
19
|
+
*/
|
|
20
|
+
interface TraversalContext {
|
|
21
|
+
currentPath: CID[]
|
|
22
|
+
pathsToTarget: CID[][] | null // collect all target paths
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface WalkDagContext<Strategy> {
|
|
26
|
+
cid: CID
|
|
27
|
+
queue: PQueue
|
|
28
|
+
strategy: Strategy
|
|
29
|
+
options?: ExportCarOptions
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ExportWalkDagContext extends WalkDagContext<ExportStrategy> {
|
|
33
|
+
writer: Pick<CarWriter, 'put'>
|
|
34
|
+
recursive?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface TraversalWalkDagContext extends WalkDagContext<TraversalStrategy> {
|
|
38
|
+
traversalContext: TraversalContext
|
|
39
|
+
parentPath: CID[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class Car implements CarInterface {
|
|
43
|
+
private readonly components: CarComponents
|
|
44
|
+
private readonly log: Logger
|
|
45
|
+
|
|
46
|
+
constructor (components: CarComponents, init: any) {
|
|
47
|
+
this.components = components
|
|
48
|
+
this.log = components.logger.forComponent('helia:car')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async import (reader: Pick<CarReader, 'blocks'>, options?: AbortOptions & ProgressOptions<PutManyBlocksProgressEvents>): Promise<void> {
|
|
52
|
+
await drain(this.components.blockstore.putMany(
|
|
53
|
+
map(reader.blocks(), ({ cid, bytes }) => ({ cid, block: bytes })),
|
|
54
|
+
options
|
|
55
|
+
))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async export (root: CID | CID[], writer: Pick<CarWriter, 'put' | 'close'>, options?: ExportCarOptions): Promise<void> {
|
|
59
|
+
const deferred = defer<Error | undefined>()
|
|
60
|
+
const roots = Array.isArray(root) ? root : [root]
|
|
61
|
+
|
|
62
|
+
// Create traversal-specific context
|
|
63
|
+
const traversalContext: TraversalContext = {
|
|
64
|
+
currentPath: [],
|
|
65
|
+
pathsToTarget: null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const traversalStrategy = options?.traversal
|
|
69
|
+
const exportStrategy = options?.exporter ?? new SubgraphExporter()
|
|
70
|
+
|
|
71
|
+
// use a queue to walk the DAG instead of recursion so we can traverse very
|
|
72
|
+
// large DAGs
|
|
73
|
+
const queue = new PQueue({
|
|
74
|
+
concurrency: DAG_WALK_QUEUE_CONCURRENCY
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
let startedExport = false
|
|
78
|
+
queue.on('idle', () => {
|
|
79
|
+
if (startedExport) {
|
|
80
|
+
// idle event was called, and started exporting, so we are done.
|
|
81
|
+
deferred.resolve()
|
|
82
|
+
} else if (!startedExport && traversalContext.pathsToTarget?.length === roots.length) {
|
|
83
|
+
// queue is idle, we haven't started exporting yet, and we have path(s)
|
|
84
|
+
// to the target(s), so we can start the export process.
|
|
85
|
+
this.log.trace('starting export of blocks to the car file')
|
|
86
|
+
startedExport = true
|
|
87
|
+
|
|
88
|
+
for (const path of traversalContext.pathsToTarget) {
|
|
89
|
+
const targetIndex = path.length - 1
|
|
90
|
+
const targetCid = path[targetIndex]
|
|
91
|
+
|
|
92
|
+
// Process all verification blocks in the path except the target
|
|
93
|
+
path.slice(0, -1).forEach(cid => {
|
|
94
|
+
void queue.add(async () => {
|
|
95
|
+
await this.#exportDagNode({ cid, queue, writer, strategy: exportStrategy, options, recursive: false })
|
|
96
|
+
})
|
|
97
|
+
.catch((err) => {
|
|
98
|
+
this.log.error('error during queue operation - %e', err)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// Process the target block (which will recursively export its DAG)
|
|
103
|
+
void queue.add(async () => {
|
|
104
|
+
await this.#exportDagNode({ cid: targetCid, queue, writer, strategy: exportStrategy, options })
|
|
105
|
+
})
|
|
106
|
+
.catch((err) => {
|
|
107
|
+
this.log.error('error during queue operation - %e', err)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
// queue is idle, we haven't started exporting yet, and we don't have
|
|
112
|
+
// path(s) to the target(s), so we can't start the export process.
|
|
113
|
+
// this should not happen without a separate error during traversal, but
|
|
114
|
+
// we'll handle it here anyway.
|
|
115
|
+
this.log.trace('no paths to target, skipping export')
|
|
116
|
+
deferred.reject(new Error('Could not traverse to target CID(s)'))
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
queue.on('error', (err) => {
|
|
120
|
+
queue.clear()
|
|
121
|
+
deferred.reject(err)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
for (const root of roots) {
|
|
125
|
+
void queue.add(async () => {
|
|
126
|
+
this.log.trace('traversing dag from %c', root)
|
|
127
|
+
await this.#traverseDagNode({ cid: root, queue, strategy: traversalStrategy ?? new GraphSearch(root), traversalContext, parentPath: [], options })
|
|
128
|
+
})
|
|
129
|
+
.catch((err) => {
|
|
130
|
+
this.log.error('error during queue operation - %e', err)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// wait for the writer to end
|
|
135
|
+
try {
|
|
136
|
+
await deferred.promise
|
|
137
|
+
} finally {
|
|
138
|
+
await writer.close()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async * stream (root: CID | CID[], options?: ExportCarOptions): AsyncGenerator<Uint8Array, void, undefined> {
|
|
143
|
+
const { writer, out } = CarWriter.create(root)
|
|
144
|
+
|
|
145
|
+
// has to be done async so we write to `writer` and read from `out` at the
|
|
146
|
+
// same time
|
|
147
|
+
this.export(root, writer, options)
|
|
148
|
+
.catch((err) => {
|
|
149
|
+
this.log.error('error during streaming export - %e', err)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
for await (const buf of out) {
|
|
153
|
+
yield buf
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Traverse a DAG and stop when we reach the target node
|
|
159
|
+
*/
|
|
160
|
+
async #traverseDagNode ({ cid, queue, strategy, traversalContext, parentPath, options }: TraversalWalkDagContext): Promise<void> {
|
|
161
|
+
// if we are traversing, we need to gather path(s) to the target(s)
|
|
162
|
+
const currentPath = [...parentPath, cid]
|
|
163
|
+
|
|
164
|
+
if (strategy.isTarget(cid)) {
|
|
165
|
+
traversalContext.pathsToTarget = traversalContext.pathsToTarget ?? []
|
|
166
|
+
traversalContext.pathsToTarget.push([...currentPath])
|
|
167
|
+
this.log.trace('found path to target %c', cid)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const codec = await this.components.getCodec(cid.code)
|
|
172
|
+
const bytes = await this.components.blockstore.get(cid, options)
|
|
173
|
+
|
|
174
|
+
// we are recursively traversing the dag
|
|
175
|
+
const decodedBlock = createUnsafe({ bytes, cid, codec })
|
|
176
|
+
|
|
177
|
+
for await (const nextCid of strategy.traverse(cid, decodedBlock)) {
|
|
178
|
+
void queue.add(async () => {
|
|
179
|
+
await this.#traverseDagNode({ cid: nextCid, queue, strategy, traversalContext, parentPath: currentPath ?? [], options })
|
|
180
|
+
})
|
|
181
|
+
.catch((err) => {
|
|
182
|
+
this.log.error('error during traversal queue operation - %e', err)
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Use an ExportStrategy to export part of all of a DAG
|
|
189
|
+
*/
|
|
190
|
+
async #exportDagNode ({ cid, queue, writer, strategy, options, recursive = true }: ExportWalkDagContext): Promise<void> {
|
|
191
|
+
if (options?.blockFilter?.has(cid.multihash.bytes) === true) {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const codec = await this.components.getCodec(cid.code)
|
|
196
|
+
const bytes = await this.components.blockstore.get(cid, options)
|
|
197
|
+
|
|
198
|
+
// Mark as processed
|
|
199
|
+
options?.blockFilter?.add(cid.multihash.bytes)
|
|
200
|
+
|
|
201
|
+
// Write to CAR
|
|
202
|
+
await writer.put({ cid, bytes })
|
|
203
|
+
|
|
204
|
+
if (recursive) {
|
|
205
|
+
// we are recursively traversing the dag
|
|
206
|
+
const decodedBlock = createUnsafe({ bytes, cid, codec })
|
|
207
|
+
|
|
208
|
+
for await (const nextCid of strategy.export(cid, decodedBlock)) {
|
|
209
|
+
void queue.add(async () => {
|
|
210
|
+
await this.#exportDagNode({ cid: nextCid, queue, writer, strategy, options })
|
|
211
|
+
})
|
|
212
|
+
.catch((err) => {
|
|
213
|
+
this.log.error('error during export queue operation - %e', err)
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
package/src/constants.ts
ADDED
package/src/errors.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type CID } from 'multiformats/cid'
|
|
2
|
+
import { type ExportStrategy } from '../index.js'
|
|
3
|
+
import type { BlockView } from 'multiformats/block/interface'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Yields the first block from the first CID and stops
|
|
7
|
+
*/
|
|
8
|
+
export class BlockExporter implements ExportStrategy {
|
|
9
|
+
async * export (cid: CID, block: BlockView<any, any, any, 0 | 1>): AsyncGenerator<CID, void, undefined> {
|
|
10
|
+
// don't yield the block, index.ts will add it to the car file and then
|
|
11
|
+
// we're done
|
|
12
|
+
}
|
|
13
|
+
}
|