@helia/car 4.0.3-c0bf36e → 4.0.4-313e2c1

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 (63) hide show
  1. package/README.md +34 -17
  2. package/dist/index.min.js +1 -1
  3. package/dist/src/car.d.ts +17 -0
  4. package/dist/src/car.d.ts.map +1 -0
  5. package/dist/src/car.js +162 -0
  6. package/dist/src/car.js.map +1 -0
  7. package/dist/src/constants.d.ts +10 -0
  8. package/dist/src/constants.d.ts.map +1 -0
  9. package/dist/src/constants.js +10 -0
  10. package/dist/src/constants.js.map +1 -0
  11. package/dist/src/errors.d.ts +9 -0
  12. package/dist/src/errors.d.ts.map +1 -0
  13. package/dist/src/errors.js +9 -0
  14. package/dist/src/errors.js.map +1 -0
  15. package/dist/src/export-strategies/block-exporter.d.ts +10 -0
  16. package/dist/src/export-strategies/block-exporter.d.ts.map +1 -0
  17. package/dist/src/export-strategies/block-exporter.js +12 -0
  18. package/dist/src/export-strategies/block-exporter.js.map +1 -0
  19. package/dist/src/export-strategies/index.d.ts +4 -0
  20. package/dist/src/export-strategies/index.d.ts.map +1 -0
  21. package/dist/src/export-strategies/index.js +4 -0
  22. package/dist/src/export-strategies/index.js.map +1 -0
  23. package/dist/src/export-strategies/subgraph-exporter.d.ts +14 -0
  24. package/dist/src/export-strategies/subgraph-exporter.d.ts.map +1 -0
  25. package/dist/src/export-strategies/subgraph-exporter.js +17 -0
  26. package/dist/src/export-strategies/subgraph-exporter.js.map +1 -0
  27. package/dist/src/export-strategies/unixfs-exporter.d.ts +11 -0
  28. package/dist/src/export-strategies/unixfs-exporter.d.ts.map +1 -0
  29. package/dist/src/export-strategies/unixfs-exporter.js +18 -0
  30. package/dist/src/export-strategies/unixfs-exporter.js.map +1 -0
  31. package/dist/src/index.d.ts +89 -24
  32. package/dist/src/index.d.ts.map +1 -1
  33. package/dist/src/index.js +38 -95
  34. package/dist/src/index.js.map +1 -1
  35. package/dist/src/traversal-strategies/cid-path.d.ts +16 -0
  36. package/dist/src/traversal-strategies/cid-path.d.ts.map +1 -0
  37. package/dist/src/traversal-strategies/cid-path.js +22 -0
  38. package/dist/src/traversal-strategies/cid-path.js.map +1 -0
  39. package/dist/src/traversal-strategies/graph-search.d.ts +14 -0
  40. package/dist/src/traversal-strategies/graph-search.d.ts.map +1 -0
  41. package/dist/src/traversal-strategies/graph-search.js +19 -0
  42. package/dist/src/traversal-strategies/graph-search.js.map +1 -0
  43. package/dist/src/traversal-strategies/index.d.ts +4 -0
  44. package/dist/src/traversal-strategies/index.d.ts.map +1 -0
  45. package/dist/src/traversal-strategies/index.js +4 -0
  46. package/dist/src/traversal-strategies/index.js.map +1 -0
  47. package/dist/src/traversal-strategies/unixfs-path.d.ts +13 -0
  48. package/dist/src/traversal-strategies/unixfs-path.d.ts.map +1 -0
  49. package/dist/src/traversal-strategies/unixfs-path.js +42 -0
  50. package/dist/src/traversal-strategies/unixfs-path.js.map +1 -0
  51. package/package.json +12 -6
  52. package/src/car.ts +218 -0
  53. package/src/constants.ts +11 -0
  54. package/src/errors.ts +8 -0
  55. package/src/export-strategies/block-exporter.ts +13 -0
  56. package/src/export-strategies/index.ts +3 -0
  57. package/src/export-strategies/subgraph-exporter.ts +18 -0
  58. package/src/export-strategies/unixfs-exporter.ts +22 -0
  59. package/src/index.ts +99 -119
  60. package/src/traversal-strategies/cid-path.ts +29 -0
  61. package/src/traversal-strategies/graph-search.ts +25 -0
  62. package/src/traversal-strategies/index.ts +3 -0
  63. package/src/traversal-strategies/unixfs-path.ts +58 -0
@@ -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,4 @@
1
+ export * from './graph-search.js';
2
+ export * from './cid-path.js';
3
+ export * from './unixfs-path.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,4 @@
1
+ export * from './graph-search.js';
2
+ export * from './cid-path.js';
3
+ export * from './unixfs-path.js';
4
+ //# sourceMappingURL=index.js.map
@@ -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-c0bf36e",
3
+ "version": "4.0.4-313e2c1",
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": "5.2.1-c0bf36e",
57
+ "@helia/interface": "5.2.1-313e2c1",
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": "4.0.3-c0bf36e",
71
- "@helia/unixfs": "4.0.3-c0bf36e",
72
- "@ipld/dag-pb": "^4.1.3",
72
+ "@helia/mfs": "5.0.0-313e2c1",
73
+ "@helia/unixfs": "5.0.0-313e2c1",
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-to-buffer": "^4.0.7"
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
+ }
@@ -0,0 +1,11 @@
1
+ export const DAG_WALK_QUEUE_CONCURRENCY = 1
2
+
3
+ /**
4
+ * The multicodec code for dag-pb binary data
5
+ */
6
+ export const DAG_PB_CODEC_CODE = 0x70
7
+
8
+ /**
9
+ * The multicodec code for raw binary data
10
+ */
11
+ export const RAW_PB_CODEC_CODE = 0x55
package/src/errors.ts ADDED
@@ -0,0 +1,8 @@
1
+ export class NotUnixFSError extends Error {
2
+ static code = 'ERR_NOT_UNIXFS'
3
+ static message = 'Not a UnixFS node'
4
+ static name = 'NotUnixFSError'
5
+ code = 'ERR_NOT_UNIXFS'
6
+ message = 'Not a UnixFS node'
7
+ name = 'NotUnixFSError'
8
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './subgraph-exporter.js'
2
+ export * from './block-exporter.js'
3
+ export * from './unixfs-exporter.js'
@@ -0,0 +1,18 @@
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
+ * Traverses the DAG breadth-first starting at the target CID and yields all
7
+ * encountered blocks.
8
+ *
9
+ * Blocks linked to from the target block are traversed using codecs defined in
10
+ * the helia config.
11
+ */
12
+ export class SubgraphExporter implements ExportStrategy {
13
+ async * export (_cid: CID, block: BlockView<any, any, any, 0 | 1>): AsyncGenerator<CID, void, undefined> {
14
+ for await (const [, linkedCid] of block.links()) {
15
+ yield linkedCid
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,22 @@
1
+ import { DAG_PB_CODEC_CODE, RAW_PB_CODEC_CODE } from '../constants.js'
2
+ import { NotUnixFSError } from '../errors.js'
3
+ import type { ExportStrategy } from '../index.js'
4
+ import type { BlockView } from 'multiformats/block/interface'
5
+ import type { CID } from 'multiformats/cid'
6
+
7
+ /**
8
+ * This exporter is used when you want to generate a car file that contains a
9
+ * single UnixFS file or directory
10
+ */
11
+ export class UnixFSExporter implements ExportStrategy {
12
+ async * export (cid: CID, block: BlockView<any, any, any, 0 | 1>): AsyncGenerator<CID, void, undefined> {
13
+ if (cid.code !== DAG_PB_CODEC_CODE && cid.code !== RAW_PB_CODEC_CODE) {
14
+ throw new NotUnixFSError('Target CID was not UnixFS - use the SubGraphExporter to export arbitrary graphs')
15
+ }
16
+
17
+ // yield all the blocks that make up the file or directory
18
+ for await (const [, linkedCid] of block.links()) {
19
+ yield linkedCid
20
+ }
21
+ }
22
+ }