Plugins Guide

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.

Available Plugins

NebulaDB comes with several built-in plugins:

Validation Plugin

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

Encryption Plugin

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
});

Versioning Plugin

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

Creating Custom Plugins

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] });

Example: Timestamps Plugin

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];
    }
  };
}

Combining Plugins

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
  ]
});

Best Practices

Plugin Order

The order of plugins matters. For example:

  1. Put validation plugins early to reject invalid documents before processing
  2. Put transformation plugins (like timestamps) before validation
  3. Put encryption plugins after validation but before saving
  4. Put logging plugins at the beginning or end depending on whether you want to log raw or processed data

Error Handling

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
  }
}

Performance Considerations