0
# Plugins and Adapters
1
2
Extensible architecture for adding functionality and storage backends to PouchDB. The plugin system allows extending PouchDB with additional features, while adapters provide different storage mechanisms for various environments.
3
4
## Capabilities
5
6
### Plugin System
7
8
Add functionality to PouchDB through plugins that extend the core API.
9
10
```javascript { .api }
11
/**
12
* Add a plugin to PouchDB
13
* @param plugin - Plugin object or function
14
* @returns PouchDB constructor with plugin installed
15
*/
16
PouchDB.plugin(plugin: Plugin): typeof PouchDB;
17
18
// Plugin can be a function that receives PouchDB constructor
19
type PluginFunction = (PouchDB: typeof PouchDB) => void;
20
21
// Plugin can be an object with methods to add to prototype
22
interface PluginObject {
23
[methodName: string]: Function;
24
}
25
26
type Plugin = PluginFunction | PluginObject;
27
```
28
29
**Usage Examples:**
30
31
```javascript
32
// Function-based plugin
33
function myPlugin(PouchDB) {
34
// Add static method
35
PouchDB.customStaticMethod = function() {
36
console.log('Custom static method called');
37
};
38
39
// Add instance method
40
PouchDB.prototype.customMethod = function() {
41
console.log('Custom method called on', this.name);
42
return this.info();
43
};
44
}
45
46
// Install the plugin
47
PouchDB.plugin(myPlugin);
48
49
// Use the plugin
50
PouchDB.customStaticMethod();
51
const db = new PouchDB('test');
52
await db.customMethod();
53
54
// Object-based plugin
55
const objectPlugin = {
56
customQuery: function(selector) {
57
// Custom query implementation
58
return this.allDocs({ include_docs: true })
59
.then(result => result.rows.filter(row =>
60
Object.keys(selector).every(key =>
61
row.doc[key] === selector[key]
62
)
63
));
64
},
65
66
bulkUpdate: function(updates) {
67
// Bulk update implementation
68
return Promise.all(
69
Object.keys(updates).map(docId =>
70
this.get(docId).then(doc =>
71
this.put({ ...doc, ...updates[docId] })
72
)
73
)
74
);
75
}
76
};
77
78
PouchDB.plugin(objectPlugin);
79
80
// Use object plugin methods
81
const results = await db.customQuery({ type: 'user', active: true });
82
await db.bulkUpdate({
83
'user1': { lastLogin: new Date().toISOString() },
84
'user2': { status: 'inactive' }
85
});
86
```
87
88
### Adapter Management
89
90
Register and configure different storage adapters for various environments.
91
92
```javascript { .api }
93
/**
94
* Register a new adapter
95
* @param name - Adapter name
96
* @param adapter - Adapter implementation
97
* @param addToPreferred - Whether to add to preferred adapters list
98
*/
99
PouchDB.adapter(name: string, adapter: Adapter, addToPreferred?: boolean): void;
100
101
/**
102
* Object containing all registered adapters
103
*/
104
PouchDB.adapters: { [name: string]: Adapter };
105
106
/**
107
* Array of preferred adapters in order of preference
108
*/
109
PouchDB.preferredAdapters: string[];
110
```
111
112
**Usage Examples:**
113
114
```javascript
115
// Check available adapters
116
console.log('Available adapters:', Object.keys(PouchDB.adapters));
117
console.log('Preferred adapters:', PouchDB.preferredAdapters);
118
119
// Register a custom adapter
120
const customAdapter = {
121
valid() {
122
return true;
123
},
124
125
async _info(callback) {
126
// Implementation
127
},
128
129
async _get(id, opts, callback) {
130
// Implementation
131
},
132
133
async _put(doc, opts, callback) {
134
// Implementation
135
}
136
137
// ... other required methods
138
};
139
140
PouchDB.adapter('custom', customAdapter, true);
141
142
// Use the custom adapter
143
const db = new PouchDB('test', { adapter: 'custom' });
144
```
145
146
### Built-in Adapters
147
148
PouchDB comes with several built-in adapters for different environments.
149
150
```javascript { .api }
151
// Common built-in adapters:
152
// - 'idb': IndexedDB adapter for browsers
153
// - 'leveldb': LevelDB adapter for Node.js
154
// - 'http'/'https': HTTP adapter for remote CouchDB
155
// - 'memory': In-memory adapter (via plugin)
156
// - 'websql': WebSQL adapter for older browsers (deprecated)
157
```
158
159
**Usage Examples:**
160
161
```javascript
162
// Explicitly specify adapter
163
const idbDb = new PouchDB('browser-db', { adapter: 'idb' });
164
const levelDb = new PouchDB('node-db', { adapter: 'leveldb' });
165
const remoteDb = new PouchDB('http://localhost:5984/db', { adapter: 'http' });
166
167
// Let PouchDB choose best adapter
168
const autoDb = new PouchDB('auto-db'); // Uses first available from preferredAdapters
169
170
// Check which adapter was chosen
171
console.log('Using adapter:', autoDb.adapter);
172
```
173
174
### Default Configuration
175
176
Create PouchDB constructors with predefined default options.
177
178
```javascript { .api }
179
/**
180
* Create a PouchDB constructor with default options
181
* @param options - Default options to apply to all instances
182
* @returns New PouchDB constructor with defaults
183
*/
184
PouchDB.defaults(options: PouchDBOptions): typeof PouchDB;
185
```
186
187
**Usage Examples:**
188
189
```javascript
190
// Create constructor with memory adapter default
191
const MemoryPouchDB = PouchDB.defaults({
192
adapter: 'memory'
193
});
194
195
// All instances use memory adapter by default
196
const memDb1 = new MemoryPouchDB('db1');
197
const memDb2 = new MemoryPouchDB('db2');
198
199
// Can still override defaults
200
const levelDb = new MemoryPouchDB('db3', { adapter: 'leveldb' });
201
202
// Create constructor for remote databases
203
const RemotePouchDB = PouchDB.defaults({
204
adapter: 'http',
205
auth: {
206
username: 'user',
207
password: 'pass'
208
}
209
});
210
211
const remoteDb = new RemotePouchDB('http://localhost:5984/mydb');
212
```
213
214
### Popular Third-Party Plugins
215
216
Examples of commonly used PouchDB plugins and their installation.
217
218
```javascript
219
// PouchDB Find (Mango queries)
220
const PouchDBFind = require('pouchdb-find');
221
PouchDB.plugin(PouchDBFind);
222
223
// Usage
224
await db.createIndex({ index: { fields: ['name', 'age'] } });
225
const result = await db.find({
226
selector: { name: 'Alice', age: { $gt: 18 } }
227
});
228
229
// PouchDB Authentication
230
const PouchDBAuth = require('pouchdb-authentication');
231
PouchDB.plugin(PouchDBAuth);
232
233
// Usage
234
await db.signUp('username', 'password', {
235
metadata: { email: 'user@example.com' }
236
});
237
await db.logIn('username', 'password');
238
239
// PouchDB Load (bulk loading)
240
const PouchDBLoad = require('pouchdb-load');
241
PouchDB.plugin(PouchDBLoad);
242
243
// Usage
244
await db.load('http://example.com/dump.json');
245
246
// PouchDB Debug (debugging utilities)
247
const PouchDBDebug = require('pouchdb-debug');
248
PouchDB.plugin(PouchDBDebug);
249
250
// Enable debug mode
251
PouchDB.debug.enable('pouchdb:*');
252
```
253
254
### Custom Plugin Development
255
256
Create your own plugins to extend PouchDB functionality.
257
258
```javascript
259
// Advanced plugin example with validation
260
function validationPlugin(PouchDB) {
261
const originalPut = PouchDB.prototype.put;
262
const originalPost = PouchDB.prototype.post;
263
const originalBulkDocs = PouchDB.prototype.bulkDocs;
264
265
// Schema storage
266
const schemas = new Map();
267
268
// Add schema registration method
269
PouchDB.prototype.setSchema = function(docType, schema) {
270
schemas.set(docType, schema);
271
return this;
272
};
273
274
// Validation function
275
function validateDoc(doc) {
276
if (!doc.type) return; // Skip docs without type
277
278
const schema = schemas.get(doc.type);
279
if (!schema) return; // Skip docs without schema
280
281
// Basic validation example
282
for (const [field, rules] of Object.entries(schema)) {
283
if (rules.required && !doc[field]) {
284
throw new Error(`Field '${field}' is required for type '${doc.type}'`);
285
}
286
287
if (rules.type && doc[field] && typeof doc[field] !== rules.type) {
288
throw new Error(`Field '${field}' must be of type '${rules.type}'`);
289
}
290
}
291
}
292
293
// Override put method with validation
294
PouchDB.prototype.put = function(doc, options, callback) {
295
if (typeof options === 'function') {
296
callback = options;
297
options = {};
298
}
299
300
try {
301
validateDoc(doc);
302
return originalPut.call(this, doc, options, callback);
303
} catch (error) {
304
if (callback) {
305
return callback(error);
306
}
307
return Promise.reject(error);
308
}
309
};
310
311
// Override post method with validation
312
PouchDB.prototype.post = function(doc, options, callback) {
313
if (typeof options === 'function') {
314
callback = options;
315
options = {};
316
}
317
318
try {
319
validateDoc(doc);
320
return originalPost.call(this, doc, options, callback);
321
} catch (error) {
322
if (callback) {
323
return callback(error);
324
}
325
return Promise.reject(error);
326
}
327
};
328
329
// Override bulkDocs with validation
330
PouchDB.prototype.bulkDocs = function(docs, options, callback) {
331
if (typeof options === 'function') {
332
callback = options;
333
options = {};
334
}
335
336
try {
337
docs.forEach(validateDoc);
338
return originalBulkDocs.call(this, docs, options, callback);
339
} catch (error) {
340
if (callback) {
341
return callback(error);
342
}
343
return Promise.reject(error);
344
}
345
};
346
}
347
348
// Install and use the validation plugin
349
PouchDB.plugin(validationPlugin);
350
351
const db = new PouchDB('validated-db');
352
353
// Set schema for user documents
354
db.setSchema('user', {
355
name: { required: true, type: 'string' },
356
email: { required: true, type: 'string' },
357
age: { type: 'number' }
358
});
359
360
// This will pass validation
361
await db.put({
362
_id: 'user1',
363
type: 'user',
364
name: 'Alice',
365
email: 'alice@example.com',
366
age: 30
367
});
368
369
// This will fail validation (missing required field)
370
try {
371
await db.put({
372
_id: 'user2',
373
type: 'user',
374
email: 'bob@example.com'
375
});
376
} catch (error) {
377
console.error('Validation error:', error.message);
378
}
379
```
380
381
### Adapter Development
382
383
Create custom adapters for specialized storage needs.
384
385
```javascript
386
// Custom adapter example (simplified)
387
function createCustomAdapter() {
388
return {
389
// Indicates adapter is valid/available
390
valid() {
391
return typeof localStorage !== 'undefined';
392
},
393
394
// Whether adapter uses prefix for database names
395
use_prefix: false,
396
397
// Get database information
398
async _info(callback) {
399
const info = {
400
db_name: this.name,
401
doc_count: 0,
402
update_seq: 0
403
};
404
405
// Count documents in localStorage
406
for (let i = 0; i < localStorage.length; i++) {
407
const key = localStorage.key(i);
408
if (key.startsWith(this.name + '_')) {
409
info.doc_count++;
410
}
411
}
412
413
callback(null, info);
414
},
415
416
// Get a document
417
async _get(id, opts, callback) {
418
const key = this.name + '_' + id;
419
const docString = localStorage.getItem(key);
420
421
if (!docString) {
422
const error = new Error('Document not found');
423
error.status = 404;
424
error.name = 'not_found';
425
return callback(error);
426
}
427
428
const doc = JSON.parse(docString);
429
callback(null, doc);
430
},
431
432
// Store a document
433
async _put(doc, opts, callback) {
434
const key = this.name + '_' + doc._id;
435
436
// Generate revision ID (simplified)
437
if (!doc._rev) {
438
doc._rev = '1-' + Math.random().toString(36).substr(2);
439
} else {
440
// Increment revision
441
const revNum = parseInt(doc._rev.split('-')[0]) + 1;
442
doc._rev = revNum + '-' + Math.random().toString(36).substr(2);
443
}
444
445
localStorage.setItem(key, JSON.stringify(doc));
446
447
callback(null, {
448
ok: true,
449
id: doc._id,
450
rev: doc._rev
451
});
452
},
453
454
// Remove a document
455
async _remove(doc, opts, callback) {
456
const key = this.name + '_' + doc._id;
457
localStorage.removeItem(key);
458
459
callback(null, {
460
ok: true,
461
id: doc._id,
462
rev: doc._rev
463
});
464
},
465
466
// Get all documents
467
async _allDocs(opts, callback) {
468
const result = {
469
total_rows: 0,
470
offset: 0,
471
rows: []
472
};
473
474
for (let i = 0; i < localStorage.length; i++) {
475
const key = localStorage.key(i);
476
if (key.startsWith(this.name + '_')) {
477
const docString = localStorage.getItem(key);
478
const doc = JSON.parse(docString);
479
480
result.rows.push({
481
id: doc._id,
482
key: doc._id,
483
value: { rev: doc._rev },
484
doc: opts.include_docs ? doc : undefined
485
});
486
}
487
}
488
489
result.total_rows = result.rows.length;
490
callback(null, result);
491
},
492
493
// Close database
494
async _close(callback) {
495
callback();
496
},
497
498
// Destroy database
499
async _destroy(opts, callback) {
500
// Remove all documents for this database
501
const keysToRemove = [];
502
for (let i = 0; i < localStorage.length; i++) {
503
const key = localStorage.key(i);
504
if (key.startsWith(this.name + '_')) {
505
keysToRemove.push(key);
506
}
507
}
508
509
keysToRemove.forEach(key => localStorage.removeItem(key));
510
callback();
511
}
512
};
513
}
514
515
// Register the custom adapter
516
PouchDB.adapter('localstorage', createCustomAdapter);
517
518
// Use the custom adapter
519
const db = new PouchDB('my-db', { adapter: 'localstorage' });
520
```
521
522
## Plugin Best Practices
523
524
### Error Handling in Plugins
525
526
```javascript
527
function robustPlugin(PouchDB) {
528
PouchDB.prototype.safeOperation = function(operation) {
529
return new Promise((resolve, reject) => {
530
try {
531
// Validate inputs
532
if (typeof operation !== 'function') {
533
throw new Error('Operation must be a function');
534
}
535
536
// Execute operation with error handling
537
Promise.resolve(operation(this))
538
.then(resolve)
539
.catch(reject);
540
541
} catch (error) {
542
reject(error);
543
}
544
});
545
};
546
}
547
```
548
549
### Plugin Testing
550
551
```javascript
552
// Example plugin test
553
function testPlugin() {
554
const db = new PouchDB('test', { adapter: 'memory' });
555
556
// Test plugin functionality
557
return db.safeOperation(async (database) => {
558
await database.put({ _id: 'test', value: 'hello' });
559
const doc = await database.get('test');
560
return doc.value === 'hello';
561
}).then(result => {
562
console.log('Plugin test passed:', result);
563
}).catch(error => {
564
console.error('Plugin test failed:', error);
565
});
566
}
567
```
568
569
## Adapter Selection Strategy
570
571
PouchDB automatically selects the best available adapter:
572
573
```javascript
574
// Check adapter selection logic
575
function checkAdapterPreference() {
576
console.log('Preferred adapters:', PouchDB.preferredAdapters);
577
console.log('Available adapters:', Object.keys(PouchDB.adapters));
578
579
// Create database and see which adapter was chosen
580
const db = new PouchDB('test');
581
console.log('Selected adapter:', db.adapter);
582
583
return db.destroy(); // Clean up
584
}
585
586
checkAdapterPreference();
587
```
588
589
The adapter system enables PouchDB to work seamlessly across different environments while maintaining a consistent API surface.