0
# CAR Writing
1
2
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.
3
4
## Capabilities
5
6
### CarWriter Class
7
8
Provides streaming CAR archive creation with backpressure and channel-based output.
9
10
```typescript { .api }
11
/**
12
* Streaming CAR writer with backpressure support
13
* Uses channel architecture: writer for input, out for data stream
14
*/
15
class CarWriter {
16
/** Write a Block to the archive, resolves when data is written to output stream */
17
put(block: Block): Promise<void>;
18
19
/** Finalize the CAR archive and signal output stream completion */
20
close(): Promise<void>;
21
22
/** Create new writer channel with specified roots */
23
static create(roots?: CID[] | CID): WriterChannel;
24
25
/** Create appender channel (no header, for extending existing CARs) */
26
static createAppender(): WriterChannel;
27
28
/** Update roots in existing CAR byte array (must be same header length) */
29
static updateRootsInBytes(bytes: Uint8Array, roots: CID[]): Promise<Uint8Array>;
30
}
31
32
/**
33
* Writer channel containing writer instance and output stream
34
*/
35
interface WriterChannel {
36
/** Writer instance for putting blocks */
37
writer: CarWriter;
38
/** Output stream yielding CAR bytes */
39
out: AsyncIterable<Uint8Array>;
40
}
41
```
42
43
**Usage Examples:**
44
45
```typescript
46
import { CarWriter } from "@ipld/car/writer";
47
import fs from 'fs';
48
import { Readable } from 'stream';
49
import { CID } from 'multiformats/cid';
50
51
// Create CAR with single root
52
const { writer, out } = CarWriter.create(rootCid);
53
54
// Pipe output to file
55
Readable.from(out).pipe(fs.createWriteStream('output.car'));
56
57
// Write blocks
58
await writer.put({ cid: block1Cid, bytes: block1Data });
59
await writer.put({ cid: block2Cid, bytes: block2Data });
60
61
// Finalize
62
await writer.close();
63
64
// Create CAR with multiple roots
65
const { writer: multiWriter, out: multiOut } = CarWriter.create([root1, root2, root3]);
66
67
// Create CAR with no initial roots (can be set later)
68
const { writer: emptyWriter, out: emptyOut } = CarWriter.create();
69
```
70
71
### CAR Appending
72
73
Create appender for extending existing CAR archives without header.
74
75
```typescript { .api }
76
/**
77
* Create appender channel for extending existing CAR archives
78
* Does not write header - designed to append to existing CAR
79
*/
80
static createAppender(): WriterChannel;
81
```
82
83
**Usage Example:**
84
85
```typescript
86
import { CarWriter } from "@ipld/car/writer";
87
import fs from 'fs';
88
89
// Create appender (no header)
90
const { writer, out } = CarWriter.createAppender();
91
92
// Append to existing file
93
const appendStream = fs.createWriteStream('existing.car', { flags: 'a' });
94
Readable.from(out).pipe(appendStream);
95
96
// Add more blocks to existing CAR
97
await writer.put({ cid: newBlockCid, bytes: newBlockData });
98
await writer.close();
99
```
100
101
### Root Management
102
103
Update roots in existing CAR files with same-length header constraint.
104
105
```typescript { .api }
106
/**
107
* Update roots in existing CAR byte array
108
* New header must be exactly same length as existing header
109
* @param bytes - CAR byte array to modify (modified in place)
110
* @param roots - New roots (must encode to same byte length)
111
* @returns Modified byte array
112
*/
113
static updateRootsInBytes(bytes: Uint8Array, roots: CID[]): Promise<Uint8Array>;
114
```
115
116
**Usage Example:**
117
118
```typescript
119
import { CarWriter } from "@ipld/car/writer";
120
import fs from 'fs';
121
122
// Load existing CAR
123
const carBytes = fs.readFileSync('existing.car');
124
125
// Update roots (must be same encoded length)
126
const newRoots = [newRootCid]; // Must encode to same byte length as old roots
127
const updatedCar = await CarWriter.updateRootsInBytes(carBytes, newRoots);
128
129
// Write updated CAR
130
fs.writeFileSync('updated.car', updatedCar);
131
```
132
133
### File-Based Root Updates (Node.js Only)
134
135
Update roots directly in CAR files using file descriptors.
136
137
```typescript { .api }
138
/**
139
* Update roots in CAR file using file descriptor (Node.js only)
140
* File must be opened in read+write mode ('r+')
141
* @param fd - File descriptor (number or FileHandle)
142
* @param roots - New roots (must encode to same byte length as existing)
143
*/
144
static updateRootsInFile(
145
fd: fs.promises.FileHandle | number,
146
roots: CID[]
147
): Promise<void>;
148
```
149
150
**Usage Example:**
151
152
```typescript
153
import fs from 'fs';
154
import { CarWriter } from "@ipld/car/writer";
155
156
// Open file in read+write mode
157
const fd = await fs.promises.open('existing.car', 'r+');
158
159
try {
160
// Update roots in place
161
await CarWriter.updateRootsInFile(fd, [newRoot1, newRoot2]);
162
console.log('Roots updated successfully');
163
} catch (error) {
164
if (error.message.includes('same length')) {
165
console.log('New roots must encode to same byte length as existing roots');
166
}
167
} finally {
168
await fd.close();
169
}
170
```
171
172
### Backpressure Handling
173
174
CarWriter provides backpressure through Promise resolution timing.
175
176
```typescript
177
// Wait for each block to be written to output stream
178
await writer.put(block1); // Resolves when block1 data is consumed from 'out'
179
await writer.put(block2); // Resolves when block2 data is consumed from 'out'
180
181
// Alternative: Queue operations (memory will accumulate)
182
const promises = [
183
writer.put(block1),
184
writer.put(block2),
185
writer.put(block3)
186
];
187
// Data will queue in memory until 'out' stream is consumed
188
await Promise.all(promises);
189
```
190
191
### Error Handling
192
193
Common errors when writing CAR files:
194
195
- **TypeError**: Invalid block format (must have `cid` and `bytes` properties)
196
- **Error**: Writer already closed, invalid CID format
197
- **RangeError**: Header length mismatch when updating roots
198
199
```typescript
200
try {
201
await writer.put({ cid: invalidCid, bytes: data });
202
} catch (error) {
203
if (error instanceof TypeError) {
204
console.log('Invalid block format');
205
} else if (error.message.includes('closed')) {
206
console.log('Writer already closed');
207
}
208
}
209
210
try {
211
await CarWriter.updateRootsInBytes(carBytes, newRoots);
212
} catch (error) {
213
if (error.message.includes('same length')) {
214
console.log('New header must be same length as existing header');
215
}
216
}
217
```
218
219
## Platform Differences
220
221
### Browser Environment
222
- Uses `CarWriter` from `lib/writer-browser.js`
223
- Memory and stream-based operations only
224
- No file descriptor support
225
226
### Node.js Environment
227
- Uses `CarWriter` from `lib/writer.js` (extends browser version)
228
- Adds `updateRootsInFile()` static method
229
- Supports file descriptor operations