Content Addressable aRchive format reader and writer for IPLD data structures.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Streaming writer interface for creating CAR archives with backpressure support, flexible root management, and efficient data output. CarWriter provides a channel-based architecture separating the writing interface from data output.
Provides streaming CAR archive creation with backpressure and channel-based output.
/**
* Streaming CAR writer with backpressure support
* Uses channel architecture: writer for input, out for data stream
*/
class CarWriter {
/** Write a Block to the archive, resolves when data is written to output stream */
put(block: Block): Promise<void>;
/** Finalize the CAR archive and signal output stream completion */
close(): Promise<void>;
/** Create new writer channel with specified roots */
static create(roots?: CID[] | CID): WriterChannel;
/** Create appender channel (no header, for extending existing CARs) */
static createAppender(): WriterChannel;
/** Update roots in existing CAR byte array (must be same header length) */
static updateRootsInBytes(bytes: Uint8Array, roots: CID[]): Promise<Uint8Array>;
}
/**
* Writer channel containing writer instance and output stream
*/
interface WriterChannel {
/** Writer instance for putting blocks */
writer: CarWriter;
/** Output stream yielding CAR bytes */
out: AsyncIterable<Uint8Array>;
}Usage Examples:
import { CarWriter } from "@ipld/car/writer";
import fs from 'fs';
import { Readable } from 'stream';
import { CID } from 'multiformats/cid';
// Create CAR with single root
const { writer, out } = CarWriter.create(rootCid);
// Pipe output to file
Readable.from(out).pipe(fs.createWriteStream('output.car'));
// Write blocks
await writer.put({ cid: block1Cid, bytes: block1Data });
await writer.put({ cid: block2Cid, bytes: block2Data });
// Finalize
await writer.close();
// Create CAR with multiple roots
const { writer: multiWriter, out: multiOut } = CarWriter.create([root1, root2, root3]);
// Create CAR with no initial roots (can be set later)
const { writer: emptyWriter, out: emptyOut } = CarWriter.create();Create appender for extending existing CAR archives without header.
/**
* Create appender channel for extending existing CAR archives
* Does not write header - designed to append to existing CAR
*/
static createAppender(): WriterChannel;Usage Example:
import { CarWriter } from "@ipld/car/writer";
import fs from 'fs';
// Create appender (no header)
const { writer, out } = CarWriter.createAppender();
// Append to existing file
const appendStream = fs.createWriteStream('existing.car', { flags: 'a' });
Readable.from(out).pipe(appendStream);
// Add more blocks to existing CAR
await writer.put({ cid: newBlockCid, bytes: newBlockData });
await writer.close();Update roots in existing CAR files with same-length header constraint.
/**
* Update roots in existing CAR byte array
* New header must be exactly same length as existing header
* @param bytes - CAR byte array to modify (modified in place)
* @param roots - New roots (must encode to same byte length)
* @returns Modified byte array
*/
static updateRootsInBytes(bytes: Uint8Array, roots: CID[]): Promise<Uint8Array>;Usage Example:
import { CarWriter } from "@ipld/car/writer";
import fs from 'fs';
// Load existing CAR
const carBytes = fs.readFileSync('existing.car');
// Update roots (must be same encoded length)
const newRoots = [newRootCid]; // Must encode to same byte length as old roots
const updatedCar = await CarWriter.updateRootsInBytes(carBytes, newRoots);
// Write updated CAR
fs.writeFileSync('updated.car', updatedCar);Update roots directly in CAR files using file descriptors.
/**
* Update roots in CAR file using file descriptor (Node.js only)
* File must be opened in read+write mode ('r+')
* @param fd - File descriptor (number or FileHandle)
* @param roots - New roots (must encode to same byte length as existing)
*/
static updateRootsInFile(
fd: fs.promises.FileHandle | number,
roots: CID[]
): Promise<void>;Usage Example:
import fs from 'fs';
import { CarWriter } from "@ipld/car/writer";
// Open file in read+write mode
const fd = await fs.promises.open('existing.car', 'r+');
try {
// Update roots in place
await CarWriter.updateRootsInFile(fd, [newRoot1, newRoot2]);
console.log('Roots updated successfully');
} catch (error) {
if (error.message.includes('same length')) {
console.log('New roots must encode to same byte length as existing roots');
}
} finally {
await fd.close();
}CarWriter provides backpressure through Promise resolution timing.
// Wait for each block to be written to output stream
await writer.put(block1); // Resolves when block1 data is consumed from 'out'
await writer.put(block2); // Resolves when block2 data is consumed from 'out'
// Alternative: Queue operations (memory will accumulate)
const promises = [
writer.put(block1),
writer.put(block2),
writer.put(block3)
];
// Data will queue in memory until 'out' stream is consumed
await Promise.all(promises);Common errors when writing CAR files:
cid and bytes properties)try {
await writer.put({ cid: invalidCid, bytes: data });
} catch (error) {
if (error instanceof TypeError) {
console.log('Invalid block format');
} else if (error.message.includes('closed')) {
console.log('Writer already closed');
}
}
try {
await CarWriter.updateRootsInBytes(carBytes, newRoots);
} catch (error) {
if (error.message.includes('same length')) {
console.log('New header must be same length as existing header');
}
}CarWriter from lib/writer-browser.jsCarWriter from lib/writer.js (extends browser version)updateRootsInFile() static method