This guide covers advanced usage patterns and techniques for NebulaDB.
NebulaDB now includes a powerful built-in indexing system with B-tree implementation for efficient queries:
const users = db.collection('users', {
indexes: [
{ name: 'email_idx', fields: ['email'], type: 'unique' },
{ name: 'age_idx', fields: ['age'], type: 'single' },
{ name: 'name_age_idx', fields: ['name', 'age'], type: 'compound' },
{
name: 'active_users_idx',
fields: ['lastActive'], type: 'single',
options: { partial: { filter: { active: true } } }
},
{
name: 'session_expiry_idx',
fields: ['createdAt'], type: 'single',
options: { expireAfterSeconds: 3600 }
}
]
});
// Instead of: for (const item of items) { await collection.insert(item); }
// Use built-in batch operations:
await collection.insertBatch(items);
// Batch updates
await collection.updateBatch(
[{ id: '1' }, { id: '2' }, { id: '3' }],
[{ $set: { processed: true } }, { $set: { processed: false } }, { $inc: { count: 1 } }]
);
// Batch deletes
await collection.deleteBatch([{ id: '1' }, { id: '2' }]);
const users = db.collection('users');
const posts = db.collection('posts');
const user = await users.insert({ name: 'Alice', email: 'alice@example.com' });
await posts.insert({ title: 'First Post', content: 'Hello world!', userId: user.id });
await posts.insert({ title: 'Second Post', content: 'Another post', userId: user.id });
const userPosts = await posts.find({ userId: user.id });
const users = db.collection('users');
const tags = db.collection('tags');
const userTags = db.collection('userTags');
const alice = await users.insert({ name: 'Alice' });
const bob = await users.insert({ name: 'Bob' });
const tagDev = await tags.insert({ name: 'developer' });
const tagAdmin = await tags.insert({ name: 'admin' });
await userTags.insert({ userId: alice.id, tagId: tagDev.id });
await userTags.insert({ userId: alice.id, tagId: tagAdmin.id });
await userTags.insert({ userId: bob.id, tagId: tagDev.id });
async function getUsersByTag(tagId) {
const relationships = await userTags.find({ tagId });
const userIds = relationships.map(rel => rel.userId);
return await users.find({ id: { $in: userIds } });
}
async function transaction(operations) {
const snapshot = {};
for (const [collectionName, collection] of db.collections.entries()) {
snapshot[collectionName] = collection.getAll();
}
try {
const result = await operations();
await db.save();
return result;
} catch (error) {
for (const [collectionName, docs] of Object.entries(snapshot)) {
db.collection(collectionName).setAll(docs);
}
throw error;
}
}
function createMigrationPlugin(migrations) {
return {
name: 'migration',
async onInit(db) {
const migrationsCollection = db.collection('_migrations');
const applied = await migrationsCollection.find();
const appliedVersions = new Set(applied.map(m => m.version));
const pendingMigrations = migrations
.filter(m => !appliedVersions.has(m.version))
.sort((a, b) => a.version - b.version);
for (const migration of pendingMigrations) {
console.log(`Applying migration: ${migration.name}`);
await migration.up(db);
await migrationsCollection.insert({
id: `migration-${migration.version}`,
version: migration.version, name: migration.name,
appliedAt: new Date().toISOString()
});
}
}
};
}
const db = createDb({
adapter: new MemoryAdapter(),
compression: { enabled: true, threshold: 1024, level: 6, fields: ['content', 'description'] }
});
await users.processInChunks(async (docs) => {
for (const doc of docs) { console.log(doc.name); }
return docs;
}, 500);
async function getPage(collection, query, page, pageSize) {
const allDocs = await collection.find(query);
const start = (page - 1) * pageSize;
return allDocs.slice(start, start + pageSize);
}
async function* streamCollection(collection, query, batchSize = 100) {
let page = 1, hasMore = true;
while (hasMore) {
const batch = await getPage(collection, query, page, batchSize);
if (batch.length === 0) { hasMore = false; }
else { for (const doc of batch) { yield doc; } page++; }
}
}
import { createDb } from '@nebula/core';
import { FileSystemAdapter } from '@nebula/adapter-filesystem';
import crypto from 'crypto';
class EncryptedFileSystemAdapter extends FileSystemAdapter {
private encryptionKey: Buffer;
constructor(filePath: string, encryptionKey: string) {
super(filePath);
this.encryptionKey = crypto.scryptSync(encryptionKey, 'salt', 32);
}
private encrypt(data: string): string {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted;
}
private decrypt(data: string): string {
const [ivHex, encryptedData] = data.split(':');
const iv = Buffer.from(ivHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, iv);
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
async save(data: Record<string, any[]>): Promise<void> {
const encryptedData = this.encrypt(JSON.stringify(data));
await super.save({ data: encryptedData } as any);
}
async load(): Promise<Record<string, any[]>> {
try {
const encryptedData = await super.load();
if (!encryptedData.data) return {};
return JSON.parse(this.decrypt(encryptedData.data));
} catch (error) { return {}; }
}
}
const db = createDb({
adapter: new EncryptedFileSystemAdapter('data.json', 'your-secret-password')
});