NebulaDB's plugin system allows you to extend its functionality with custom behaviors. Plugins can intercept and modify database operations at various points in the lifecycle.
NebulaDB comes with several built-in plugins:
The Validation Plugin uses Zod to validate documents before they're inserted or updated.
import { createDb } from '@nebula/core';
import { MemoryAdapter } from '@nebula/adapter-memory';
import { createValidationPlugin } from '@nebula/plugin-validation';
import { z } from 'zod';
const userSchema = z.object({
id: z.string(),
name: z.string().min(2).max(50),
email: z.string().email().optional(),
age: z.number().int().positive().optional()
});
const validationPlugin = createValidationPlugin({
schemas: { users: userSchema },
strict: false
});
const db = createDb({
adapter: new MemoryAdapter(),
plugins: [validationPlugin]
});
const users = db.collection('users');
await users.insert({ name: 'Alice', email: 'alice@example.com' }); // Valid
await users.insert({ name: 'B', email: 'not-an-email' }); // Error: Validation failed
The Encryption Plugin encrypts sensitive data before saving it and decrypts it when loading.
import { createDb } from '@nebula/core';
import { MemoryAdapter } from '@nebula/adapter-memory';
import { createEncryptionPlugin } from '@nebula/plugin-encryption';
const encryptionPlugin = createEncryptionPlugin({
encryptionKey: 'your-secret-key',
fields: { users: ['email', 'password', 'ssn'] },
encryptAll: false
});
const db = createDb({
adapter: new MemoryAdapter(),
plugins: [encryptionPlugin]
});
const users = db.collection('users');
await users.insert({
name: 'Alice',
email: 'alice@example.com', // Will be encrypted
password: 'secret123' // Will be encrypted
});
The Versioning Plugin tracks document versions and maintains a history of changes.
import { createDb } from '@nebula/core';
import { MemoryAdapter } from '@nebula/adapter-memory';
import { createVersioningPlugin } from '@nebula/plugin-versioning';
const versioningPlugin = createVersioningPlugin({
versionField: '_version',
timestampField: '_updatedAt',
historyCollectionSuffix: '_history',
maxVersions: 10
});
const db = createDb({
adapter: new MemoryAdapter(),
plugins: [versioningPlugin]
});
const users = db.collection('users');
const user = await users.insert({ name: 'Alice' }); // _version: 1
await users.update({ id: user.id }, { $set: { name: 'Alicia' } }); // _version: 2
const userHistory = db.collection('users_history');
const versions = await userHistory.find({ _originalId: user.id });
console.log(versions); // All versions of the document
You can create your own plugins by implementing the Plugin interface:
import { Plugin, Document, Query, UpdateOperation } from '@nebula/core';
const loggingPlugin: Plugin = {
name: 'logging',
onInit(db) { console.log('Database initialized'); },
onCollectionCreate(collection) { console.log(`Collection created: ${collection.name}`); },
async onBeforeInsert(collection, doc) {
console.log(`Inserting into ${collection}:`, doc);
return doc;
},
onAfterInsert(collection, doc) { console.log(`Inserted into ${collection}:`, doc); },
async onBeforeUpdate(collection, query, update) {
console.log(`Updating in ${collection}:`, { query, update });
return [query, update];
},
onAfterUpdate(collection, query, update, affectedDocs) {
console.log(`Updated in ${collection}:`, { query, update, affectedDocs });
},
async onBeforeDelete(collection, query) {
console.log(`Deleting from ${collection}:`, query);
return query;
},
onAfterDelete(collection, query, deletedDocs) {
console.log(`Deleted from ${collection}:`, { query, deletedDocs });
},
async onBeforeQuery(collection, query) {
console.log(`Querying ${collection}:`, query);
return query;
},
async onAfterQuery(collection, query, results) {
console.log(`Query results from ${collection}:`, { query, count: results.length });
return results;
}
};
const db = createDb({ adapter: new MemoryAdapter(), plugins: [loggingPlugin] });
Here's an example of a custom plugin that adds creation and update timestamps to documents:
import { Plugin, Document } from '@nebula/core';
export function createTimestampsPlugin(options = {}): Plugin {
const { createdAtField = 'createdAt', updatedAtField = 'updatedAt' } = options;
return {
name: 'timestamps',
async onBeforeInsert(collection, doc) {
const now = new Date().toISOString();
return { ...doc, [createdAtField]: now, [updatedAtField]: now };
},
async onBeforeUpdate(collection, query, update) {
const now = new Date().toISOString();
const newUpdate = { ...update };
if (!newUpdate.$set) newUpdate.$set = {};
newUpdate.$set[updatedAtField] = now;
return [query, newUpdate];
}
};
}
You can use multiple plugins together. The plugins are executed in the order they are provided:
const db = createDb({
adapter: new MemoryAdapter(),
plugins: [
createTimestampsPlugin(),
createValidationPlugin({ schemas }),
createEncryptionPlugin({ encryptionKey }),
loggingPlugin
]
});
The order of plugins matters. For example:
Plugins should handle errors gracefully and not break the application:
async onBeforeInsert(collection, doc) {
try {
return processedDoc;
} catch (error) {
console.error('Plugin error:', error);
return doc; // Return original to continue
}
}