or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

attachments.mdbulk-queries.mdchanges-events.mddatabase-operations.mdindex.mdreplication-sync.md

attachments.mddocs/

0

# Attachments

1

2

PouchDB provides comprehensive binary attachment management for storing and retrieving files associated with documents. Attachments support various content types including images, documents, audio, and any binary data.

3

4

## Capabilities

5

6

### Creating Attachments

7

8

#### db.putAttachment()

9

10

Attaches binary data to a document.

11

12

```javascript { .api }

13

/**

14

* Attach binary data to a document

15

* @param docId - Document ID to attach to

16

* @param attachmentId - Unique identifier for the attachment

17

* @param rev - Current document revision

18

* @param blob - Binary data (Blob, Buffer, ArrayBuffer, or base64 string)

19

* @param type - MIME type of the attachment

20

* @param callback - Optional callback function (err, result) => void

21

* @returns Promise resolving to attachment operation result

22

*/

23

db.putAttachment(docId, attachmentId, rev, blob, type, callback);

24

```

25

26

**Usage Examples:**

27

28

```javascript

29

// Attach a text file

30

const doc = await db.get('user_001');

31

const textContent = 'This is a text file content';

32

const textBlob = new Blob([textContent], { type: 'text/plain' });

33

34

const result = await db.putAttachment(

35

'user_001',

36

'notes.txt',

37

doc._rev,

38

textBlob,

39

'text/plain'

40

);

41

42

// Attach an image (browser)

43

const fileInput = document.getElementById('fileInput');

44

const file = fileInput.files[0];

45

46

if (file) {

47

const doc = await db.get('user_001');

48

const result = await db.putAttachment(

49

'user_001',

50

'profile-photo.jpg',

51

doc._rev,

52

file,

53

file.type

54

);

55

}

56

57

// Attach binary data (Node.js)

58

const fs = require('fs');

59

const imageBuffer = fs.readFileSync('./image.png');

60

61

const doc = await db.get('document_001');

62

const result = await db.putAttachment(

63

'document_001',

64

'image.png',

65

doc._rev,

66

imageBuffer,

67

'image/png'

68

);

69

70

// Attach base64 encoded data

71

const base64Data = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==';

72

const doc = await db.get('user_001');

73

74

const result = await db.putAttachment(

75

'user_001',

76

'pixel.png',

77

doc._rev,

78

base64Data,

79

'image/png'

80

);

81

```

82

83

### Retrieving Attachments

84

85

#### db.getAttachment()

86

87

Retrieves an attachment from a document.

88

89

```javascript { .api }

90

/**

91

* Retrieve an attachment

92

* @param docId - Document ID containing the attachment

93

* @param attachmentId - Attachment identifier

94

* @param options - Optional retrieval parameters

95

* @param callback - Optional callback function (err, blob) => void

96

* @returns Promise resolving to attachment data

97

*/

98

db.getAttachment(docId, attachmentId, options, callback);

99

```

100

101

**Usage Examples:**

102

103

```javascript

104

// Get attachment as Blob (browser)

105

const blob = await db.getAttachment('user_001', 'profile-photo.jpg');

106

const imageUrl = URL.createObjectURL(blob);

107

document.getElementById('profileImage').src = imageUrl;

108

109

// Get attachment as Buffer (Node.js)

110

const buffer = await db.getAttachment('document_001', 'image.png');

111

const fs = require('fs');

112

fs.writeFileSync('./downloaded-image.png', buffer);

113

114

// Get specific revision of attachment

115

const blob = await db.getAttachment('user_001', 'notes.txt', {

116

rev: '2-abc123def456'

117

});

118

119

// Get attachment as binary string

120

const binaryString = await db.getAttachment('user_001', 'data.bin', {

121

binary: true

122

});

123

```

124

125

### Removing Attachments

126

127

#### db.removeAttachment()

128

129

Removes an attachment from a document.

130

131

```javascript { .api }

132

/**

133

* Remove an attachment from a document

134

* @param docId - Document ID containing the attachment

135

* @param attachmentId - Attachment identifier to remove

136

* @param rev - Current document revision

137

* @param callback - Optional callback function (err, result) => void

138

* @returns Promise resolving to removal operation result

139

*/

140

db.removeAttachment(docId, attachmentId, rev, callback);

141

```

142

143

**Usage Examples:**

144

145

```javascript

146

// Remove an attachment

147

const doc = await db.get('user_001');

148

const result = await db.removeAttachment(

149

'user_001',

150

'old-photo.jpg',

151

doc._rev

152

);

153

154

console.log('Attachment removed:', result.ok);

155

156

// Remove with error handling

157

try {

158

const doc = await db.get('user_001');

159

await db.removeAttachment('user_001', 'notes.txt', doc._rev);

160

console.log('Attachment removed successfully');

161

} catch (err) {

162

console.error('Failed to remove attachment:', err);

163

}

164

```

165

166

## Attachment Metadata

167

168

### Retrieving Attachment Information

169

170

```javascript

171

// Get document with attachment metadata

172

const doc = await db.get('user_001', {

173

attachments: true,

174

binary: false

175

});

176

177

console.log(doc._attachments);

178

// Output example:

179

// {

180

// "profile-photo.jpg": {

181

// "content_type": "image/jpeg",

182

// "revpos": 2,

183

// "digest": "md5-abc123def456",

184

// "length": 45123,

185

// "stub": true

186

// }

187

// }

188

189

// Get document with attachment data included

190

const docWithAttachments = await db.get('user_001', {

191

attachments: true,

192

binary: true

193

});

194

195

// Attachment data will be included as base64 strings

196

console.log(docWithAttachments._attachments['profile-photo.jpg'].data);

197

```

198

199

### Attachment Metadata Structure

200

201

```javascript { .api }

202

interface AttachmentMetadata {

203

/** MIME type of the attachment */

204

content_type: string;

205

206

/** Revision position when attachment was added */

207

revpos: number;

208

209

/** MD5 digest of the attachment content */

210

digest: string;

211

212

/** Size of the attachment in bytes */

213

length: number;

214

215

/** Indicates if attachment data is included */

216

stub: boolean;

217

218

/** Base64 encoded attachment data (when stub is false) */

219

data?: string;

220

}

221

```

222

223

## Configuration Options

224

225

### GetAttachment Options

226

227

```javascript { .api }

228

interface GetAttachmentOptions {

229

/** Specific document revision to retrieve attachment from */

230

rev?: string;

231

232

/** Return attachment as binary data */

233

binary?: boolean;

234

235

/** Additional retrieval options */

236

[key: string]: any;

237

}

238

```

239

240

## Advanced Usage Examples

241

242

### Bulk Attachment Operations

243

244

```javascript

245

// Upload multiple attachments to a document

246

async function uploadMultipleAttachments(docId, attachments) {

247

let doc = await db.get(docId);

248

249

for (const attachment of attachments) {

250

try {

251

const result = await db.putAttachment(

252

docId,

253

attachment.id,

254

doc._rev,

255

attachment.data,

256

attachment.type

257

);

258

259

// Update revision for next attachment

260

doc._rev = result.rev;

261

console.log(`Uploaded attachment: ${attachment.id}`);

262

} catch (err) {

263

console.error(`Failed to upload ${attachment.id}:`, err);

264

}

265

}

266

267

return doc;

268

}

269

270

// Usage

271

const attachments = [

272

{ id: 'photo1.jpg', data: imageBlob1, type: 'image/jpeg' },

273

{ id: 'photo2.jpg', data: imageBlob2, type: 'image/jpeg' },

274

{ id: 'notes.txt', data: textBlob, type: 'text/plain' }

275

];

276

277

await uploadMultipleAttachments('user_001', attachments);

278

```

279

280

### Attachment Synchronization

281

282

```javascript

283

// Download all attachments from a document

284

async function downloadAllAttachments(docId, downloadPath) {

285

const doc = await db.get(docId, { attachments: false });

286

287

if (!doc._attachments) {

288

console.log('No attachments found');

289

return;

290

}

291

292

const attachmentIds = Object.keys(doc._attachments);

293

const downloads = [];

294

295

for (const attachmentId of attachmentIds) {

296

try {

297

const blob = await db.getAttachment(docId, attachmentId);

298

downloads.push({

299

id: attachmentId,

300

data: blob,

301

metadata: doc._attachments[attachmentId]

302

});

303

} catch (err) {

304

console.error(`Failed to download ${attachmentId}:`, err);

305

}

306

}

307

308

return downloads;

309

}

310

311

// Sync attachments between databases

312

async function syncAttachments(sourceDB, targetDB, docId) {

313

// Get source document with attachment metadata

314

const sourceDoc = await sourceDB.get(docId, { attachments: false });

315

316

if (!sourceDoc._attachments) {

317

return;

318

}

319

320

// Get or create target document

321

let targetDoc;

322

try {

323

targetDoc = await targetDB.get(docId);

324

} catch (err) {

325

if (err.status === 404) {

326

// Create document without attachments first

327

const { _attachments, ...docWithoutAttachments } = sourceDoc;

328

targetDoc = await targetDB.put(docWithoutAttachments);

329

} else {

330

throw err;

331

}

332

}

333

334

// Sync each attachment

335

for (const attachmentId of Object.keys(sourceDoc._attachments)) {

336

try {

337

const attachmentData = await sourceDB.getAttachment(docId, attachmentId);

338

const metadata = sourceDoc._attachments[attachmentId];

339

340

await targetDB.putAttachment(

341

docId,

342

attachmentId,

343

targetDoc._rev,

344

attachmentData,

345

metadata.content_type

346

);

347

348

// Update target document revision

349

targetDoc = await targetDB.get(docId);

350

} catch (err) {

351

console.error(`Failed to sync attachment ${attachmentId}:`, err);

352

}

353

}

354

}

355

```

356

357

### File Upload with Progress Tracking

358

359

```javascript

360

// Upload file with progress tracking (browser)

361

async function uploadFileWithProgress(docId, file, onProgress) {

362

const chunkSize = 64 * 1024; // 64KB chunks

363

const totalChunks = Math.ceil(file.size / chunkSize);

364

let uploadedChunks = 0;

365

366

// Read file in chunks and upload

367

const reader = new FileReader();

368

const chunks = [];

369

370

for (let i = 0; i < totalChunks; i++) {

371

const start = i * chunkSize;

372

const end = Math.min(start + chunkSize, file.size);

373

const chunk = file.slice(start, end);

374

375

const chunkData = await new Promise((resolve) => {

376

reader.onload = (e) => resolve(e.target.result);

377

reader.readAsArrayBuffer(chunk);

378

});

379

380

chunks.push(new Uint8Array(chunkData));

381

uploadedChunks++;

382

383

// Report progress

384

if (onProgress) {

385

onProgress({

386

loaded: uploadedChunks * chunkSize,

387

total: file.size,

388

percentage: (uploadedChunks / totalChunks) * 100

389

});

390

}

391

}

392

393

// Combine chunks and upload

394

const combinedData = new Uint8Array(file.size);

395

let offset = 0;

396

397

for (const chunk of chunks) {

398

combinedData.set(chunk, offset);

399

offset += chunk.length;

400

}

401

402

const blob = new Blob([combinedData], { type: file.type });

403

const doc = await db.get(docId);

404

405

return await db.putAttachment(

406

docId,

407

file.name,

408

doc._rev,

409

blob,

410

file.type

411

);

412

}

413

414

// Usage

415

const fileInput = document.getElementById('fileInput');

416

const file = fileInput.files[0];

417

418

await uploadFileWithProgress('user_001', file, (progress) => {

419

console.log(`Upload progress: ${progress.percentage.toFixed(2)}%`);

420

});

421

```

422

423

### Attachment Caching

424

425

```javascript

426

// Attachment caching system

427

class AttachmentCache {

428

constructor(maxSize = 50 * 1024 * 1024) { // 50MB default

429

this.cache = new Map();

430

this.maxSize = maxSize;

431

this.currentSize = 0;

432

}

433

434

async getAttachment(db, docId, attachmentId) {

435

const cacheKey = `${docId}/${attachmentId}`;

436

437

// Check cache first

438

if (this.cache.has(cacheKey)) {

439

const cached = this.cache.get(cacheKey);

440

// Move to end (LRU)

441

this.cache.delete(cacheKey);

442

this.cache.set(cacheKey, cached);

443

return cached.data;

444

}

445

446

// Fetch from database

447

const data = await db.getAttachment(docId, attachmentId);

448

const size = this._getDataSize(data);

449

450

// Add to cache if there's room

451

if (size <= this.maxSize) {

452

this._ensureSpace(size);

453

this.cache.set(cacheKey, { data, size });

454

this.currentSize += size;

455

}

456

457

return data;

458

}

459

460

_getDataSize(data) {

461

if (data instanceof Blob) {

462

return data.size;

463

} else if (data instanceof ArrayBuffer) {

464

return data.byteLength;

465

} else if (Buffer.isBuffer(data)) {

466

return data.length;

467

}

468

return 0;

469

}

470

471

_ensureSpace(neededSize) {

472

while (this.currentSize + neededSize > this.maxSize && this.cache.size > 0) {

473

const firstKey = this.cache.keys().next().value;

474

const removed = this.cache.get(firstKey);

475

this.cache.delete(firstKey);

476

this.currentSize -= removed.size;

477

}

478

}

479

480

clear() {

481

this.cache.clear();

482

this.currentSize = 0;

483

}

484

}

485

486

// Usage

487

const cache = new AttachmentCache();

488

489

// Get cached attachment

490

const imageData = await cache.getAttachment(db, 'user_001', 'profile-photo.jpg');

491

```

492

493

### Attachment Validation

494

495

```javascript

496

// Validate attachments before upload

497

class AttachmentValidator {

498

constructor(options = {}) {

499

this.maxSize = options.maxSize || 10 * 1024 * 1024; // 10MB

500

this.allowedTypes = options.allowedTypes || [

501

'image/jpeg',

502

'image/png',

503

'image/gif',

504

'text/plain',

505

'application/pdf'

506

];

507

this.maxFilenameLength = options.maxFilenameLength || 255;

508

}

509

510

validate(attachmentId, data, contentType) {

511

const errors = [];

512

513

// Validate filename

514

if (!attachmentId || attachmentId.length === 0) {

515

errors.push('Attachment ID is required');

516

}

517

518

if (attachmentId.length > this.maxFilenameLength) {

519

errors.push(`Filename too long (max ${this.maxFilenameLength} characters)`);

520

}

521

522

// Validate content type

523

if (!this.allowedTypes.includes(contentType)) {

524

errors.push(`Content type ${contentType} not allowed`);

525

}

526

527

// Validate size

528

const size = this._getDataSize(data);

529

if (size > this.maxSize) {

530

errors.push(`File too large (max ${this.maxSize} bytes)`);

531

}

532

533

if (size === 0) {

534

errors.push('File is empty');

535

}

536

537

return {

538

valid: errors.length === 0,

539

errors

540

};

541

}

542

543

_getDataSize(data) {

544

if (data instanceof Blob) return data.size;

545

if (data instanceof ArrayBuffer) return data.byteLength;

546

if (Buffer.isBuffer(data)) return data.length;

547

if (typeof data === 'string') return data.length;

548

return 0;

549

}

550

}

551

552

// Usage

553

const validator = new AttachmentValidator({

554

maxSize: 5 * 1024 * 1024, // 5MB

555

allowedTypes: ['image/jpeg', 'image/png']

556

});

557

558

async function uploadWithValidation(docId, attachmentId, data, contentType) {

559

const validation = validator.validate(attachmentId, data, contentType);

560

561

if (!validation.valid) {

562

throw new Error(`Validation failed: ${validation.errors.join(', ')}`);

563

}

564

565

const doc = await db.get(docId);

566

return await db.putAttachment(docId, attachmentId, doc._rev, data, contentType);

567

}

568

```

569

570

## Performance Considerations

571

572

### Optimizing Attachment Performance

573

574

```javascript

575

// Efficient attachment handling for large files

576

async function handleLargeAttachment(docId, attachmentId, file) {

577

// Check if attachment already exists

578

try {

579

const doc = await db.get(docId, { attachments: false });

580

const existingAttachment = doc._attachments?.[attachmentId];

581

582

if (existingAttachment) {

583

// Calculate file hash to check if upload is necessary

584

const fileHash = await calculateFileHash(file);

585

if (existingAttachment.digest === `md5-${fileHash}`) {

586

console.log('Attachment unchanged, skipping upload');

587

return { ok: true, unchanged: true };

588

}

589

}

590

} catch (err) {

591

// Document doesn't exist, continue with upload

592

}

593

594

// Upload with compression for text files

595

let dataToUpload = file;

596

if (file.type.startsWith('text/')) {

597

dataToUpload = await compressData(file);

598

}

599

600

const doc = await db.get(docId).catch(() => ({ _id: docId }));

601

return await db.putAttachment(

602

docId,

603

attachmentId,

604

doc._rev,

605

dataToUpload,

606

file.type

607

);

608

}

609

610

// Memory-efficient attachment streaming (Node.js)

611

const stream = require('stream');

612

613

function createAttachmentStream(db, docId, attachmentId) {

614

return new stream.Readable({

615

async read() {

616

try {

617

const data = await db.getAttachment(docId, attachmentId);

618

this.push(data);

619

this.push(null); // End of stream

620

} catch (err) {

621

this.emit('error', err);

622

}

623

}

624

});

625

}

626

```