npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nekodb/client

v1.1.7

Published

NekoDB is a lightweight encrypted document database for Node.js, built with real-time WebSocket communication, simple JSON-based storage, and a developer-friendly API. Designed for speed, security, and ease of integration without external database depende

Readme

NekoDB

Secure, resilient document database with high availability and seamless real-time data access.

Install

npm install @nekodb/client

Quick Start

const NekoDB = require('@nekodb/client');

const db = new NekoDB({
    host: 'your-server.com',
    username: 'Username',
    password: 'Password',
});

const id = await db.insert('users', { name: 'Neko', age: 5 });
const doc = await db.get('users', id);
const results = await db.search('users', { name: 'Neko' });
await db.update('users', id, { age: 6 });
await db.delete('users', id);

db.close();

Why NekoDB?

| Feature | NekoDB | MongoDB | Firebase | SQLite | |---------|--------|---------|----------|--------| | AES-256 Encryption | ✅ Built-in | ❌ | ❌ | ❌ | | Multi-Node Auto-Failover | ✅ | Manual setup | N/A | ❌ | | Zero Disk Overflow | ✅ 99% auto-migrate | ❌ | N/A | ❌ | | WebSocket Real-time | ✅ | Change Streams | ✅ | ❌ | | Binary WebSocket Protocol | ✅ | ❌ | ❌ | ❌ | | No Schema Required | ✅ | ✅ | ✅ | ❌ | | Aggregation Pipeline | ✅ | ✅ | Limited | ❌ | | Bulk Operations | ✅ Parallel | ✅ | ✅ | ❌ | | Auto-Reconnect | ✅ Built-in | Manual | Built-in | N/A | | Data Encrypted at Rest | ✅ AES-256-GCM | Enterprise only | ✅ | ❌ |

Connection

const NekoDB = require('@nekodb/client');

const db = new NekoDB({
    host: 'your-server.com',
    username: 'your_username',
    password: 'your_password',
    // Options: 'none', 'debug', 'info', 'warn', 'error'
    // Or pass a custom logger object like winston/pino (must implement debug, info, warn, error)
    logging: 'info', 
});

// From environment variables (NEKODB_HOST, NEKODB_USERNAME, NEKODB_PASSWORD)
const db = NekoDB.fromEnv();

Events

db.on('connected', () => console.log('Connected!'));
db.on('disconnected', (info) => console.log('Disconnected:', info));
db.on('reconnected', () => console.log('Reconnected!'));
db.on('reconnect_failed', () => console.log('Could not reconnect'));
db.on('error', (err) => console.error('Error:', err));
db.on('closed', () => console.log('Connection closed'));

L1 Cache & Real-Time Invalidation

NekoDB Client has a built-in L1 Cache that speeds up read queries (get, list, count) by storing responses in local memory.

By default, L1 cache has a TTL of 10 seconds. You can configure it when initializing the client:

const db = new NekoDB({
    host: 'your-server.com',
    username: 'your_username',
    password: 'your_password',
    cache: {
        ttl: 30000,      // TTL in milliseconds (default: 10000)
        maxSize: 500     // Maximum cache size (default: 200)
    }
});

Real-Time Invalidation via Pub/Sub

When you subscribe to real-time events on a collection, NekoDB client automatically keeps your L1 cache updated. Any incoming insert, update, or delete event from the server will automatically invalidate the corresponding L1 cache entries.

// Local L1 cache is automatically invalidated when real-time updates occur
await db.subscribe('users', (event) => {
    console.log('Real-time database event:', event);
});

This guarantees 0 ms latency for read operations on cache hit while keeping data perfectly fresh!

TypeScript Support

NekoDB Client has built-in TypeScript definitions. Autocomplete and type checks work out-of-the-box in VS Code or any TypeScript project.

Collection API

Use db.collection() for a cleaner syntax:

const users = db.collection('users');

await users.insert({ name: 'Neko', role: 'admin' });
const doc = await users.get('doc-id');
const all = await users.list();
const found = await users.search({ role: 'admin' });
await users.update('doc-id', { role: 'superadmin' });
await users.delete('doc-id');
const total = await users.count();
await users.drop();

CRUD Operations

Insert

const docId = await db.insert('products', {
    name: 'Laptop',
    price: 999,
    tags: ['electronics', 'computers'],
});

// Insert document with TTL (Time-To-Live in seconds)
const sessionDocId = await db.insertTTL('sessions', { token: 'secret123' }, 3600);
// or using options object:
await db.insert('sessions', { token: 'secret123' }, { ttl: 3600 });

Get

const product = await db.get('products', docId);

List

const allIds = await db.list('products');

Search

// Exact match
await db.search('products', { name: 'Laptop' });

// Comparison operators: $gt, $gte, $lt, $lte, $eq, $ne, $in, $nin, $regex
await db.search('products', { price: { $gt: 500 } });
await db.search('products', { price: { $gte: 100, $lte: 1000 } });
await db.search('products', { category: { $in: ['electronics', 'software'] } });
await db.search('products', { name: { $regex: '^Laptop' } });

Vector Search

Search documents by vector/embedding similarity using Cosine Similarity or Euclidean (L2) Distance:

// From NekoDB instance
const results = await db.vectorSearch('products', {
    field: 'embedding',
    vector: [0.12, 0.43, -0.91],
    metric: 'cosine', // 'cosine' or 'l2' (euclidean)
    topK: 5
});

// From Collection instance
const products = db.collection('products');
const results = await products.vectorSearch({
    field: 'embedding',
    vector: [0.12, 0.43, -0.91],
    metric: 'l2',
    topK: 3
});

Full-Text Search (FTS)

Search documents by textual relevance using the BM25 scoring algorithm:

// From NekoDB instance
const results = await db.ftsSearch('articles', {
    field: 'content',
    query: 'belajar go database',
    limit: 10
});

// From Collection instance
const articles = db.collection('articles');
const results = await articles.ftsSearch({
    field: 'content',
    query: 'belajar go database',
    limit: 5
});

Update

await db.update('products', docId, { price: 899 });

Delete

await db.delete('products', docId);

Count

const result = await db.count('products');

List Collections

const collections = await db.listCollections();

Delete Collection

await db.deleteCollection('temp_data');

Query Builder

Build queries with a chainable, fluent API:

const users = db.collection('users');

// Simple query
const results = await users.query()
    .where('age').gt(18)
    .where('role').eq('admin')
    .exec();

// With sorting and pagination
const page = await users.query()
    .where('status').eq('active')
    .sort('name', 'asc')
    .limit(20)
    .page(1)
    .exec();

// Range query
const products = await db.query('products')
    .between('price', 100, 500)
    .sortDesc('price')
    .limit(10)
    .exec();

// Select specific fields (projection)
const names = await users.query()
    .where('role').eq('admin')
    .select('name', 'email')
    .exec();

// Exclude specific fields
const safe = await users.query()
    .exclude('password', 'secret')
    .exec();

// Get first match only
const first = await users.query()
    .where('email').eq('[email protected]')
    .first();

// In / Not In
const results = await users.query()
    .where('role').in(['admin', 'moderator'])
    .where('status').ne('banned')
    .exec();

// Regex / Wildcard matching
const matches = await users.query()
    .where('email').regex('@gmail\\.com$')
    .exec();

Collection Helpers

High-level helper methods for common operations:

const users = db.collection('users').helper();

// Find single document matching query
const admin = await users.findOne({ role: 'admin' });

// Find by ID (returns doc with _id)
const user = await users.findById('doc-id');

// Check if document exists
const exists = await users.exists('doc-id');

// Find all matching documents (returns full docs, not just IDs)
// ⚡ Optimized: Fetches all matching documents in exactly 1 WebSocket roundtrip!
const admins = await users.findMany({ role: 'admin' });

// Get all documents in collection
// ⚡ Optimized: Fetches all documents in exactly 1 WebSocket roundtrip!
const allUsers = await users.getAll();

// Upsert: update if exists, insert if not
const result = await users.upsert({ email: '[email protected]' }, {
    email: '[email protected]',
    name: 'Neko',
    role: 'user',
});
// result: { action: 'inserted', id: '...' } or { action: 'updated', id: '...' }

// Find or insert
const { doc, created } = await users.findOrInsert(
    { email: '[email protected]' },
    { email: '[email protected]', name: 'Neko' }
);

// Update all documents matching query
await users.updateWhere({ role: 'user' }, { verified: true });

// Delete all documents matching query
await users.deleteWhere({ status: 'inactive' });

// Async paginate iterator
for await (const page of users.paginate({ limit: 10 })) {
    console.log(`Page ${page.page}:`, page.data);
    // page.pageInfo.has_next tells if more pages exist
}

// Functional helpers
await users.forEach({ role: 'admin' }, (user, i) => {
    console.log(`Admin ${i}:`, user.name);
});

const names = await users.map({ role: 'admin' }, u => u.name);
const seniors = await users.filter({}, u => u.age > 50);

// Update nested properties (dot-notation)
await users.updatePath('doc-id', 'profile.avatar', 'new-avatar.png');

// Push item to nested array
await users.pushToArray('doc-id', 'profile.tags', 'developer');

// Pull item from nested array
await users.pullFromArray('doc-id', 'profile.tags', 'developer');

Schema Validation

Client-side validation before sending data to the server:

const { Schema } = require('@nekodb/client');

const userSchema = new Schema({
    name: { type: 'string', required: true, minLength: 1, maxLength: 100 },
    email: { type: 'string', required: true, match: /^[^@]+@[^@]+\.[^@]+$/ },
    age: { type: 'number', min: 0, max: 200 },
    role: { type: 'string', enum: ['admin', 'user', 'moderator'] },
    tags: 'array',
}, { strict: true }); // strict: reject unknown fields

// Validate
const { valid, errors } = userSchema.validate({
    name: 'Neko',
    email: '[email protected]',
    age: 5,
    role: 'admin',
});

if (!valid) {
    console.error('Validation errors:', errors);
    // [{ field: 'name', rule: 'required', message: '"name" is required' }]
}

// Apply defaults
const withDefaults = userSchema.applyDefaults({ name: 'Neko' });

// Pick/Omit fields
const partial = userSchema.pick(doc, 'name', 'email');
const safe = userSchema.omit(doc, 'password', 'secret');

// Inspect schema
console.log(userSchema.getFieldNames());     // ['name', 'email', 'age', ...]
console.log(userSchema.getRequiredFields());  // ['name', 'email']

Pagination & Sorting

// Offset-based
const page1 = await db.listPaginated('products', { limit: 10, offset: 0 });

// Page-based
const page2 = await db.listPaginated('products', { limit: 10, page: 2 });

// With sorting
const sorted = await db.listPaginated('products', {
    limit: 10,
    sort: [{ field: 'price', order: 'desc' }],
});

// Cursor-based
const next = await db.listPaginated('products', {
    limit: 10,
    cursor: page1.page_info.next_cursor,
});

// Search with pagination
const results = await db.searchPaginated('products', { price: { $gt: 100 } }, { limit: 5 });

Aggregation Pipeline

const result = await db.aggregate('orders', [
    { type: '$match', params: { status: 'completed' } },
    { type: '$group', params: {
        _id: '$category',
        total: { $sum: '$amount' },
        avg: { $avg: '$amount' },
        count: { $sum: 1 },
    }},
    { type: '$sort', params: { total: -1 } },
    { type: '$limit', params: { value: 5 } },
]);

Stages: $match, $group, $sort, $limit, $skip, $project, $count, $unwind

Accumulators: $sum, $avg, $min, $max, $count, $first, $last, $push

Bulk Operations

// Bulk insert
await db.bulkInsert('products', [
    { name: 'Mouse', price: 25 },
    { name: 'Keyboard', price: 75 },
]);

// Bulk update
await db.bulkUpdate('products', {
    'doc-id-1': { price: 30 },
    'doc-id-2': { price: 80 },
});

// Bulk delete
await db.bulkDelete('products', ['doc-id-1', 'doc-id-2']);

// Mixed operations
await db.bulkExecute([
    { type: 'insert', collection: 'logs', document: { event: 'login' } },
    { type: 'update', collection: 'users', document_id: 'abc', document: { active: true } },
    { type: 'delete', collection: 'sessions', document_id: 'old-session' },
]);

Projection

// Include only specific fields
const doc = await db.getProjected('users', 'doc-id', { name: 1, email: 1 });

// Exclude specific fields
const doc2 = await db.getProjected('users', 'doc-id', { password: 0 });

// Search with projection
const results = await db.searchProjected('users', { role: 'admin' }, { name: 1, email: 1 });

Indexing

await db.createIndex('users', 'email', 'hash');
await db.createIndex('products', 'price', 'sorted');
const indexes = await db.listIndexes('users');

Server-Side Schema & Snapshot Management

Expose server-side validation schemas and compressed backup snapshots:

// Register schema to be validated by the server before insert/update
await db.registerSchema('users', {
    fields: {
        name: 'string',
        age: 'number',
        verified: 'boolean',
    },
    required: ['name', 'verified'],
});

// Trigger a compressed .nkdb snapshot backup to local disk
const filename = await db.createSnapshot();
// returns: "backup_Username_1779752707.nkdb"

// Restore the database state from a specified .nkdb archive
await db.restoreSnapshot(filename);

Data Export and File Download

Export documents into formatted CSV or JSON files, and download the resulting archives. Both collection and doc_id are required parameters. Exported files expire after 5 minutes.

// Export a document to CSV (both collection and doc_id are required)
const result = await db.exportCSV('users', 'c4752fbd433b9daae49ea09a4b490f7f');
// returns: { format: 'csv', filename: 'users_c4752fbd..._1779752707.csv',
//            document_count: 1, file_size: 572, exported_at: '...', expires_at: '2026-06-25T15:05:00Z' }

// Export a document to JSON
const resultJson = await db.exportJSON('users', 'c4752fbd433b9daae49ea09a4b490f7f');

// Download the exported file using the filename (must download within 5 minutes)
const filename = result.filename;
await db.downloadExport(filename, './downloaded_users.csv');
console.log('File successfully downloaded!');

You can also export directly from a Collection instance:

const users = db.collection('users');

// Export document to CSV (doc_id is required)
const csvRes = await users.exportCSV('c4752fbd433b9daae49ea09a4b490f7f');

// Export document to JSON (doc_id is required)
const jsonRes = await users.exportJSON('c4752fbd433b9daae49ea09a4b490f7f');

Pub/Sub Reactive Channels

Subscribe to real-time change events on collections:

// Subscribe to real-time events on 'messages' collection
await db.subscribe('messages', (event) => {
    console.log(`Real-time change: ${event.event} on document ${event.document_id}`);
    if (event.document) {
        console.log('Document content:', event.document);
    }
});

// Broadcast events: Any other client inserting/updating/deleting in 'messages' 
// will trigger the above callback in sub-millisecond real-time!

Multi-Document ACID Transactions

Perform atomic multi-document writes with staging and rollbacks:

// Start a new ACID transaction session
const txId = await db.beginTransaction();

try {
    // Stage insert operations inside the transaction
    const id1 = await db.insert('users', { name: 'Alice' }, txId);
    const id2 = await db.insert('users', { name: 'Bob' }, txId);
    
    // Stage updates inside the transaction
    await db.update('users', id1, { verified: true }, txId);
    
    // Atomically commit all staged changes to the database
    await db.commitTransaction(txId);
    console.log('Transaction committed successfully!');
} catch (err) {
    // Roll back the entire transaction if any error occurs
    await db.rollbackTransaction(txId);
    console.error('Transaction failed and rolled back:', err.message);
}

Error Handling

Typed error classes for structured error handling:

const { errors } = require('@nekodb/client');

try {
    const doc = await db.get('users', 'non-existent');
} catch (err) {
    if (err instanceof errors.NotFoundError) {
        console.log('Document not found:', err.collection, err.documentId);
    } else if (err instanceof errors.AuthError) {
        console.log('Authentication failed');
    } else if (err instanceof errors.TimeoutError) {
        console.log('Request timed out after', err.durationMs, 'ms');
    } else if (err instanceof errors.ConnectionError) {
        console.log('Connection error to', err.host);
    }
}

// Classify raw server response into typed error
const error = errors.classify('invalid-credentials'); // returns AuthError
const error2 = errors.classify('not-found');           // returns NotFoundError

Available error classes: NekoError, ConnectionError, TimeoutError, AuthError, NotFoundError, ValidationError, BulkOperationError, CollectionError

Event Bus

Enhanced event emitter with wildcards and promise support:

const { EventBus } = require('@nekodb/client');

const bus = new EventBus();

// Standard events
bus.on('user:created', (user) => console.log('Created:', user));
bus.on('user:deleted', (user) => console.log('Deleted:', user));

// Wildcard — listen to ALL events
bus.on('*', (eventName, data) => console.log('Event:', eventName, data));

// Once — fire only once
bus.once('db:ready', () => console.log('Database ready'));

// Remove listener
const handler = (data) => console.log(data);
bus.on('test', handler);
bus.off('test', handler);

Connection Manager

Manage multiple NekoDB connections with built-in auto-failover query routing (Proxy):

const { ConnectionManager } = require('@nekodb/client');

const manager = new ConnectionManager();

// Add connections
manager.add('primary', new NekoDB({ host: 'server1.com', ... }));
manager.add('secondary', new NekoDB({ host: 'server2.com', ... }));

// 🌟 Smart Auto-Failover Proxy Routing:
// You can invoke database queries and helper methods directly on the manager.
// If the active node fails or goes offline, the manager automatically performs
// a failover (switches active node to a healthy one) and retries the operation transparently!
const id = await manager.insert('users', { name: 'Neko' });

// Works seamlessly with collection helper API too:
const users = manager.collection('users');
await users.insert({ name: 'Alice' });

// Switch active connection manually
manager.setActive('secondary');

// Health management
const healthy = manager.getHealthy();     // ['primary']
const unhealthy = manager.getUnhealthy(); // ['secondary']
manager.switchToHealthy();                // auto-switch to healthy node

// Stats & info
console.log(manager.list());
// [{ id: 'primary', connected: true, isActive: true, ... }, ...]

console.log(manager.getStats());
// { total: 2, connected: 1, disconnected: 1, activeId: 'primary' }

// Cleanup
await manager.closeAll();

Full Example

const NekoDB = require('@nekodb/client');
const { Schema } = require('@nekodb/client');

async function main() {
    const db = new NekoDB({
        host: 'your-server.com',
        username: 'Username',
        password: 'Password',
    });

    db.on('connected', () => console.log('✅ Connected'));
    db.on('error', (err) => console.error('❌', err));

    // Define schema
    const userSchema = new Schema({
        name: { type: 'string', required: true },
        age: { type: 'number', min: 0 },
        role: { type: 'string', enum: ['admin', 'user'] },
    });

    // Validate before insert
    const data = { name: 'Alice', age: 30, role: 'admin' };
    const { valid, errors } = userSchema.validate(data);
    if (!valid) return console.error('Invalid:', errors);

    const users = db.collection('users');
    const id = await users.insert(data);

    // Query builder
    const admins = await users.query()
        .where('role').eq('admin')
        .where('age').gte(18)
        .sortDesc('age')
        .limit(10)
        .exec();
    console.log('Admins:', admins);

    // Collection helpers
    const helper = users.helper();
    const alice = await helper.findOne({ name: 'Alice' });
    console.log('Found:', alice);

    // Aggregation
    const stats = await users.aggregate([
        { type: '$group', params: { _id: '$role', count: { $sum: 1 } } },
    ]);
    console.log('Stats:', stats);

    // Paginate
    for await (const page of helper.paginate({ limit: 5 })) {
        console.log(`Page ${page.page}:`, page.data.length, 'docs');
    }

    db.close();
}

main().catch(console.error);

License

MIT