Convert files to content-addressed archives (.car) and back
The npm package "ipfs-car" offers a streamlined solution for converting files to content-addressed archives (.car) and vice versa, crucial for developers working with decentralized storage systems like IPFS (InterPlanetary File System). By leveraging the content-addressable feature, ipfs-car ensures that each file is uniquely identified by its content rather than its location, enhancing data integrity and retrievability. This tool is particularly beneficial in environments where data consistency and verification are paramount, making it an essential utility for developers engaged in building decentralized applications (dApps) or managing large datasets across distributed networks.
To begin using ipfs-car in your project, you can easily integrate it by running the "npm install ipfs-car" command in your development environment. This installation process adds the package to your project, allowing you to harness the power of content-addressable technology with minimal setup. Once installed, ipfs-car can be used to package entire directories into a single .car file, which can then be easily transported or stored within the IPFS ecosystem. This capability not only simplifies data management but also enhances the security and efficiency of data transmission across decentralized networks.
Ipfs-car not only supports the conversion of files into .car format but also facilitates the extraction of these files, promoting a versatile approach to file management. Developers can thus maintain a consistent workflow whether they are packaging new data or retrieving previously stored data. The use of the .car format is particularly advantageous in decentralized contexts, where ensuring the authenticity and integrity of data is crucial. With ipfs-car, developers have a reliable tool at their fingertips to maintain robust data management practices in their applications, contributing to the overall resilience and performance of their systems.
Core dependencies of this npm package and its dev dependencies.
@ipld/car, @ipld/dag-cbor, @ipld/dag-json, @ipld/dag-pb, @ipld/unixfs, @web3-storage/car-block-validator, files-from-path, ipfs-unixfs-exporter, multiformats, sade, varint, assert, execa, hundreds, mocha, playwright-test, standard
A README file for the ipfs-car code repository. View Code
Convert files to content-addressable archives (.car) and back
ipfs-car
is a library and CLI tool to pack & unpack files from Content Addressable aRchives (CAR) file. A thin wrapper over @ipld/car and unix-fs.
Content-addressable archives store data as blocks (a sequence of bytes) each prefixed with the Content ID (CID) derived from the hash of the data; typically in a file with a .car
extension.
Use ipfs-car
to pack your files into a .car; a portable, verifiable, IPFS compatible archive.
$ ipfs-car pack path/to/files --output my-files.car
or unpack files from a .car, and verify that every block matches it's CID
$ ipfs-car unpack my-files.car --output path/to/write/to
Fetch and locally verify files from a IPFS gateway over http
curl "https://ipfs.io/ipfs/bafybeidd2gyhagleh47qeg77xqndy2qy3yzn4vkxmk775bg2t5lpuy7pcu?format=car" | ipfs-car unpack -o images
# install it as a dependency
$ npm i ipfs-car
# OR use the cli without installing via `npx`
$ npx ipfs-car --help
Pack files into a .car
# write a content addressed archive to stdout.
$ ipfs-car pack path/to/file/or/dir
# note: CAR data streamed to stdout will not have roots set in CAR header!
# specify the car file name.
$ ipfs-car pack path/to/files --output path/to/write/a.car
# by default, ipfs-car will wrap files in an IPFS directory.
# use --no-wrap to avoid this.
$ ipfs-car pack path/to/file --no-wrap --output path/to/write/a.car
Unpack files from a .car
# unpack files to a specific path.
$ ipfs-car unpack path/to/my.car --output /path/to/unpack/files/to
# unpack a specific root.
$ ipfs-car unpack path/to/my.car --root <cid1>
# unpack files from a .car on stdin.
$ cat path/to/my.car | ipfs-car unpack
Show the files and directories in a .car
# show the files and directories.
$ ipfs-car ls path/to/my.car
# show the files and directories, their CIDs and byte sizes.
$ ipfs-car ls path/to/my.car --verbose
Show the root CIDs in a .car
# show the CID roots found in the CAR header.
$ ipfs-car roots path/to/my.car
# show the CID roots found implicitly from the blocks in the file.
$ ipfs-car roots --implicit path/to/my.car
Show the block CIDs in a .car
# show the CIDs for all the blocks.
$ ipfs-car blocks path/to/my.car
Get other information about a CAR
# generate CID for a CAR.
$ ipfs-car hash path/to/my.car
To pack files into content-addressable archives, you can use the following:
createFileEncoderStream
a factory function for creating a ReadableStream
that encodes a single file into DAG Block
s.createDirectoryEncoderStream
a factory function for creating a ReadableStream
for encoding a directory of files into DAG Block
s.CAREncoderStream
a TransformStream
sub-class that you can write Block
s to and read Uint8Array
CAR file data from.To unpack content-addressable archives to files, you should use @ipld/car
and ipfs-unixfs-exporter
modules.
import { createFileEncoderStream, CAREncoderStream } from 'ipfs-car'
const file = new Blob(['Hello ipfs-car!'])
const carStream = createFileEncoderStream(file).pipeThrough(new CAREncoderStream())
// carStream.pipeTo(somewhereWritable)
import { Writable } from 'stream'
import { createDirectoryEncoderStream, CAREncoderStream } from 'ipfs-car'
import { filesFromPaths } from 'files-from-path'
const files = await filesFromPaths(process.argv.slice(2))
await createDirectoryEncoderStream(files)
.pipeThrough(new CAREncoderStream())
.pipeTo(Writable.toWeb(process.stdout))
Usage: node script.js file0 file1 dir0 > my.car
.
The root CID is the final block generated by the file/directory encoder stream. Use a transform stream to record the CID of the last block generated:
import { createFileEncoderStream, CAREncoderStream } from 'ipfs-car'
const file = new Blob(['Hello ipfs-car!'])
let rootCID
await createFileEncoderStream(file)
.pipeThrough(new TransformStream({
transform (block, controller) {
rootCID = block.cid
controller.enqueue(block)
}
}))
.pipeThrough(new CAREncoderStream())
.pipeTo(new WritableStream())
console.log(rootCID.toString())
If you need root CIDs in the CAR header, there are two approaches you can use:
import { createFileEncoderStream, CAREncoderStream } from 'ipfs-car'
const file = new Blob(['Hello ipfs-car!'])
const blocks = []
// buffer the output
await createFileEncoderStream(file)
.pipeTo(new WritableStream({ write: b => blocks.push(b) }))
const rootCID = blocks.at(-1).cid
const blockStream = new ReadableStream({
pull (controller) {
if (blocks.length) {
controller.enqueue(blocks.shift())
} else {
controller.close()
}
}
})
await blockStream
.pipeThrough(new CAREncoderStream([rootCID])) // pass root to CAR encoder
.pipeTo(new WritableStream())
import fs from 'fs'
import { Writable } from 'stream'
import { CarWriter } from '@ipld/car/writer'
import { CID } from 'multiformats/cid'
import { createFileEncoderStream, CAREncoderStream } from 'ipfs-car'
// Root CID written in CAR file header before it is updated with the real root CID.
const placeholderCID = CID.parse('bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi')
const file = new Blob(['Hello ipfs-car!'])
let rootCID
await createFileEncoderStream(file)
.pipeThrough(new TransformStream({
transform (block, controller) {
rootCID = block.cid
controller.enqueue(block)
}
}))
.pipeThrough(new CAREncoderStream(placeholderCID))
.pipeTo(Writable.toWeb(fs.createWriteStream('path/to/my.car')))
// update roots in CAR header
const fd = await fs.promises.open(opts.output, 'r+')
await CarWriter.updateRootsInFile(fd, [rootCID])
await fd.close()
This functionality is not provided by this library, but is easy to do with @ipld/car
and ipfs-unixfs-exporter
modules:
import { CarIndexedReader } from '@ipld/car/indexed-reader'
import { recursive as exporter } from 'ipfs-unixfs-exporter'
const reader = await CarIndexedReader.fromFile('path/to/my.car')
const roots = await reader.getRoots()
const entries = exporter(roots[0], {
async get (cid) {
const block = await reader.get(cid)
return block.bytes
}
})
for await (const entry of entries) {
if (entry.type === 'file' || entry.type === 'raw') {
console.log('file', entry.path, entry.content)
} else if (entry.type === 'directory') {
console.log('directory', entry.path)
}
}
Feel free to join in. All welcome. Open an issue!
Dual-licensed under MIT + Apache 2.0