PouchDB is a pocket-sized database inspired by Apache CouchDB that runs in the browser and Node.js.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Add functionality to PouchDB through plugins that extend the core API.
/**
* Add a plugin to PouchDB
* @param plugin - Plugin object or function
* @returns PouchDB constructor with plugin installed
*/
PouchDB.plugin(plugin: Plugin): typeof PouchDB;
// Plugin can be a function that receives PouchDB constructor
type PluginFunction = (PouchDB: typeof PouchDB) => void;
// Plugin can be an object with methods to add to prototype
interface PluginObject {
[methodName: string]: Function;
}
type Plugin = PluginFunction | PluginObject;Usage Examples:
// Function-based plugin
function myPlugin(PouchDB) {
// Add static method
PouchDB.customStaticMethod = function() {
console.log('Custom static method called');
};
// Add instance method
PouchDB.prototype.customMethod = function() {
console.log('Custom method called on', this.name);
return this.info();
};
}
// Install the plugin
PouchDB.plugin(myPlugin);
// Use the plugin
PouchDB.customStaticMethod();
const db = new PouchDB('test');
await db.customMethod();
// Object-based plugin
const objectPlugin = {
customQuery: function(selector) {
// Custom query implementation
return this.allDocs({ include_docs: true })
.then(result => result.rows.filter(row =>
Object.keys(selector).every(key =>
row.doc[key] === selector[key]
)
));
},
bulkUpdate: function(updates) {
// Bulk update implementation
return Promise.all(
Object.keys(updates).map(docId =>
this.get(docId).then(doc =>
this.put({ ...doc, ...updates[docId] })
)
)
);
}
};
PouchDB.plugin(objectPlugin);
// Use object plugin methods
const results = await db.customQuery({ type: 'user', active: true });
await db.bulkUpdate({
'user1': { lastLogin: new Date().toISOString() },
'user2': { status: 'inactive' }
});Register and configure different storage adapters for various environments.
/**
* Register a new adapter
* @param name - Adapter name
* @param adapter - Adapter implementation
* @param addToPreferred - Whether to add to preferred adapters list
*/
PouchDB.adapter(name: string, adapter: Adapter, addToPreferred?: boolean): void;
/**
* Object containing all registered adapters
*/
PouchDB.adapters: { [name: string]: Adapter };
/**
* Array of preferred adapters in order of preference
*/
PouchDB.preferredAdapters: string[];Usage Examples:
// Check available adapters
console.log('Available adapters:', Object.keys(PouchDB.adapters));
console.log('Preferred adapters:', PouchDB.preferredAdapters);
// Register a custom adapter
const customAdapter = {
valid() {
return true;
},
async _info(callback) {
// Implementation
},
async _get(id, opts, callback) {
// Implementation
},
async _put(doc, opts, callback) {
// Implementation
}
// ... other required methods
};
PouchDB.adapter('custom', customAdapter, true);
// Use the custom adapter
const db = new PouchDB('test', { adapter: 'custom' });PouchDB comes with several built-in adapters for different environments.
// Common built-in adapters:
// - 'idb': IndexedDB adapter for browsers
// - 'leveldb': LevelDB adapter for Node.js
// - 'http'/'https': HTTP adapter for remote CouchDB
// - 'memory': In-memory adapter (via plugin)
// - 'websql': WebSQL adapter for older browsers (deprecated)Usage Examples:
// Explicitly specify adapter
const idbDb = new PouchDB('browser-db', { adapter: 'idb' });
const levelDb = new PouchDB('node-db', { adapter: 'leveldb' });
const remoteDb = new PouchDB('http://localhost:5984/db', { adapter: 'http' });
// Let PouchDB choose best adapter
const autoDb = new PouchDB('auto-db'); // Uses first available from preferredAdapters
// Check which adapter was chosen
console.log('Using adapter:', autoDb.adapter);Create PouchDB constructors with predefined default options.
/**
* Create a PouchDB constructor with default options
* @param options - Default options to apply to all instances
* @returns New PouchDB constructor with defaults
*/
PouchDB.defaults(options: PouchDBOptions): typeof PouchDB;Usage Examples:
// Create constructor with memory adapter default
const MemoryPouchDB = PouchDB.defaults({
adapter: 'memory'
});
// All instances use memory adapter by default
const memDb1 = new MemoryPouchDB('db1');
const memDb2 = new MemoryPouchDB('db2');
// Can still override defaults
const levelDb = new MemoryPouchDB('db3', { adapter: 'leveldb' });
// Create constructor for remote databases
const RemotePouchDB = PouchDB.defaults({
adapter: 'http',
auth: {
username: 'user',
password: 'pass'
}
});
const remoteDb = new RemotePouchDB('http://localhost:5984/mydb');Examples of commonly used PouchDB plugins and their installation.
// PouchDB Find (Mango queries)
const PouchDBFind = require('pouchdb-find');
PouchDB.plugin(PouchDBFind);
// Usage
await db.createIndex({ index: { fields: ['name', 'age'] } });
const result = await db.find({
selector: { name: 'Alice', age: { $gt: 18 } }
});
// PouchDB Authentication
const PouchDBAuth = require('pouchdb-authentication');
PouchDB.plugin(PouchDBAuth);
// Usage
await db.signUp('username', 'password', {
metadata: { email: 'user@example.com' }
});
await db.logIn('username', 'password');
// PouchDB Load (bulk loading)
const PouchDBLoad = require('pouchdb-load');
PouchDB.plugin(PouchDBLoad);
// Usage
await db.load('http://example.com/dump.json');
// PouchDB Debug (debugging utilities)
const PouchDBDebug = require('pouchdb-debug');
PouchDB.plugin(PouchDBDebug);
// Enable debug mode
PouchDB.debug.enable('pouchdb:*');Create your own plugins to extend PouchDB functionality.
// Advanced plugin example with validation
function validationPlugin(PouchDB) {
const originalPut = PouchDB.prototype.put;
const originalPost = PouchDB.prototype.post;
const originalBulkDocs = PouchDB.prototype.bulkDocs;
// Schema storage
const schemas = new Map();
// Add schema registration method
PouchDB.prototype.setSchema = function(docType, schema) {
schemas.set(docType, schema);
return this;
};
// Validation function
function validateDoc(doc) {
if (!doc.type) return; // Skip docs without type
const schema = schemas.get(doc.type);
if (!schema) return; // Skip docs without schema
// Basic validation example
for (const [field, rules] of Object.entries(schema)) {
if (rules.required && !doc[field]) {
throw new Error(`Field '${field}' is required for type '${doc.type}'`);
}
if (rules.type && doc[field] && typeof doc[field] !== rules.type) {
throw new Error(`Field '${field}' must be of type '${rules.type}'`);
}
}
}
// Override put method with validation
PouchDB.prototype.put = function(doc, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
try {
validateDoc(doc);
return originalPut.call(this, doc, options, callback);
} catch (error) {
if (callback) {
return callback(error);
}
return Promise.reject(error);
}
};
// Override post method with validation
PouchDB.prototype.post = function(doc, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
try {
validateDoc(doc);
return originalPost.call(this, doc, options, callback);
} catch (error) {
if (callback) {
return callback(error);
}
return Promise.reject(error);
}
};
// Override bulkDocs with validation
PouchDB.prototype.bulkDocs = function(docs, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
try {
docs.forEach(validateDoc);
return originalBulkDocs.call(this, docs, options, callback);
} catch (error) {
if (callback) {
return callback(error);
}
return Promise.reject(error);
}
};
}
// Install and use the validation plugin
PouchDB.plugin(validationPlugin);
const db = new PouchDB('validated-db');
// Set schema for user documents
db.setSchema('user', {
name: { required: true, type: 'string' },
email: { required: true, type: 'string' },
age: { type: 'number' }
});
// This will pass validation
await db.put({
_id: 'user1',
type: 'user',
name: 'Alice',
email: 'alice@example.com',
age: 30
});
// This will fail validation (missing required field)
try {
await db.put({
_id: 'user2',
type: 'user',
email: 'bob@example.com'
});
} catch (error) {
console.error('Validation error:', error.message);
}Create custom adapters for specialized storage needs.
// Custom adapter example (simplified)
function createCustomAdapter() {
return {
// Indicates adapter is valid/available
valid() {
return typeof localStorage !== 'undefined';
},
// Whether adapter uses prefix for database names
use_prefix: false,
// Get database information
async _info(callback) {
const info = {
db_name: this.name,
doc_count: 0,
update_seq: 0
};
// Count documents in localStorage
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(this.name + '_')) {
info.doc_count++;
}
}
callback(null, info);
},
// Get a document
async _get(id, opts, callback) {
const key = this.name + '_' + id;
const docString = localStorage.getItem(key);
if (!docString) {
const error = new Error('Document not found');
error.status = 404;
error.name = 'not_found';
return callback(error);
}
const doc = JSON.parse(docString);
callback(null, doc);
},
// Store a document
async _put(doc, opts, callback) {
const key = this.name + '_' + doc._id;
// Generate revision ID (simplified)
if (!doc._rev) {
doc._rev = '1-' + Math.random().toString(36).substr(2);
} else {
// Increment revision
const revNum = parseInt(doc._rev.split('-')[0]) + 1;
doc._rev = revNum + '-' + Math.random().toString(36).substr(2);
}
localStorage.setItem(key, JSON.stringify(doc));
callback(null, {
ok: true,
id: doc._id,
rev: doc._rev
});
},
// Remove a document
async _remove(doc, opts, callback) {
const key = this.name + '_' + doc._id;
localStorage.removeItem(key);
callback(null, {
ok: true,
id: doc._id,
rev: doc._rev
});
},
// Get all documents
async _allDocs(opts, callback) {
const result = {
total_rows: 0,
offset: 0,
rows: []
};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(this.name + '_')) {
const docString = localStorage.getItem(key);
const doc = JSON.parse(docString);
result.rows.push({
id: doc._id,
key: doc._id,
value: { rev: doc._rev },
doc: opts.include_docs ? doc : undefined
});
}
}
result.total_rows = result.rows.length;
callback(null, result);
},
// Close database
async _close(callback) {
callback();
},
// Destroy database
async _destroy(opts, callback) {
// Remove all documents for this database
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(this.name + '_')) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
callback();
}
};
}
// Register the custom adapter
PouchDB.adapter('localstorage', createCustomAdapter);
// Use the custom adapter
const db = new PouchDB('my-db', { adapter: 'localstorage' });function robustPlugin(PouchDB) {
PouchDB.prototype.safeOperation = function(operation) {
return new Promise((resolve, reject) => {
try {
// Validate inputs
if (typeof operation !== 'function') {
throw new Error('Operation must be a function');
}
// Execute operation with error handling
Promise.resolve(operation(this))
.then(resolve)
.catch(reject);
} catch (error) {
reject(error);
}
});
};
}// Example plugin test
function testPlugin() {
const db = new PouchDB('test', { adapter: 'memory' });
// Test plugin functionality
return db.safeOperation(async (database) => {
await database.put({ _id: 'test', value: 'hello' });
const doc = await database.get('test');
return doc.value === 'hello';
}).then(result => {
console.log('Plugin test passed:', result);
}).catch(error => {
console.error('Plugin test failed:', error);
});
}PouchDB automatically selects the best available adapter:
// Check adapter selection logic
function checkAdapterPreference() {
console.log('Preferred adapters:', PouchDB.preferredAdapters);
console.log('Available adapters:', Object.keys(PouchDB.adapters));
// Create database and see which adapter was chosen
const db = new PouchDB('test');
console.log('Selected adapter:', db.adapter);
return db.destroy(); // Clean up
}
checkAdapterPreference();The adapter system enables PouchDB to work seamlessly across different environments while maintaining a consistent API surface.
Install with Tessl CLI
npx tessl i tessl/npm-pouchdb