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

@vijayee/wavedb

v0.10.2

Published

Node.js bindings for WaveDB - Hierarchical B+Trie Database

Readme

WaveDB Node.js Bindings

Node.js bindings for WaveDB — a hierarchical key-value database with MVCC, WAL durability, and schema layer access.

Installation

npm install wavedb

Quick Start

const { WaveDB } = require('wavedb');

const db = new WaveDB('/path/to/db', {
  delimiter: '/',
  lruMemoryMb: 50,
  wal: { syncMode: 'debounced' }
});

// Async (Promise)
await db.put('users/alice/name', 'Alice');
const name = await db.get('users/alice/name');

// Sync (blocking)
db.putSync('users/bob/name', 'Bob');
const name2 = db.getSync('users/bob/name');

// Callback style
db.putCb('users/charlie/name', 'Charlie', (err) => { /* ... */ });
db.getCb('users/charlie/name', (err, val) => { /* ... */ });

// Object operations (nested data → flattened paths)
await db.putObject('users/alice', { name: 'Alice', age: 30 });
const user = await db.getObject('users/alice'); // { name: 'Alice', age: 30 }

// Batch
await db.batch([
  { type: 'put', key: 'users/alice/name', value: 'Alice' },
  { type: 'del', key: 'users/bob/name' }
]);

// Iterator
const iter = new Iterator(db, { start: 'users/alice', end: 'users/alice~' });
let entry;
while ((entry = iter.read())) {
  console.log(entry.key, entry.value);
}
iter.end();

// Stream
db.createReadStream({ start: 'users/', end: 'users/~' })
  .on('data', ({ key, value }) => console.log(key, '=', value))
  .on('end', () => console.log('Done'));

db.close();

Configuration

const db = new WaveDB(path, options);

| Option | Default | Description | |--------|---------|-------------| | delimiter | '/' | Key path delimiter | | chunkSize | 4 | HBTrie chunk size (immutable, set at creation) | | btreeNodeSize | 4096 | B+tree node size (immutable) | | enablePersist | true | Persist data to disk (immutable) | | lruMemoryMb | 50 | LRU cache size in MB | | lruShards | 64 | LRU shard count (0 = auto-scale) | | workerThreads | 4 | Worker pool size | | wal.syncMode | 'debounced' | 'immediate', 'debounced', or 'async' | | wal.debounceMs | 250 | fsync debounce window (ms) | | wal.maxFileSize | 131072 | Max WAL file size before sealing | | encryption | — | Encryption configuration (see below) |

WAL Sync Modes

| Mode | Behavior | Durability | Throughput | |------|----------|------------|------------| | 'immediate' | fsync after every write | Highest | ~1K ops/sec | | 'debounced' | batched fsync (default 250ms) | High | ~300K ops/sec | | 'async' | buffered write, idle drain every 250ms | Process crash only | ~400K ops/sec |

Encryption

WaveDB supports AES-256-GCM encryption at rest for WAL files and persisted pages. Encryption is set at database creation and cannot be changed on an existing database. Key material is never persisted — only a salt and verification check are stored.

| Mode | Key | Use case | |------|-----|----------| | 'symmetric' | 32-byte Buffer | Simple setups, embedded devices | | 'asymmetric' | DER-encoded RSA key pair | Key rotation, write-only nodes |

Symmetric Encryption

const { WaveDB, EncryptionError } = require('wavedb');

const key = Buffer.alloc(32); // your 32-byte AES-256 key
// ... fill key with crypto.randomBytes(32) or a derived key ...

const db = new WaveDB('/path/to/db', {
  enablePersist: true,
  encryption: {
    type: 'symmetric',
    key: key            // required, exactly 32 bytes
  }
});

// All operations work transparently — encryption is internal
await db.put('users/alice/name', 'Alice');
const name = await db.get('users/alice/name');

// Reopen with the same key
const db2 = new WaveDB('/path/to/db', {
  enablePersist: true,
  encryption: { type: 'symmetric', key }
});

Asymmetric Encryption

const { WaveDB } = require('wavedb');
const fs = require('fs');

const publicKey = fs.readFileSync('public_key.der');
const privateKey = fs.readFileSync('private_key.der');

const db = new WaveDB('/path/to/db', {
  enablePersist: true,
  encryption: {
    type: 'asymmetric',
    publicKey: publicKey,    // required, DER-encoded
    privateKey: privateKey   // optional — omit for write-only mode
  }
});

In asymmetric mode, a random data encryption key (DEK) is generated per session and wrapped with the RSA public key. The private key is required for decryption; a write-only node can omit privateKey.

Encryption Errors

| Condition | Error | Message | |-----------|-------|---------| | Opening encrypted DB without a key | EncryptionError | "Encryption required: this database was created with encryption" | | Wrong key on re-open | EncryptionError | "Invalid encryption key" | | Adding encryption to unencrypted DB | EncryptionError | "Encryption unsupported" | | Symmetric key not 32 bytes | RangeError | "encryption.key must be exactly 32 bytes for AES-256" | | Missing required key material | TypeError | "encryption.key is required for symmetric encryption..." |

try {
  const db = new WaveDB('/path/to/db', { encryption: { type: 'symmetric', key } });
} catch (err) {
  if (err instanceof EncryptionError) {
    console.error('Encryption failed:', err.message);
  }
}

API Reference

Async Operations

All async methods return Promises and dispatch to the C worker pool (non-blocking).

await db.put(key, value)           // Store a value
const val = await db.get(key)      // Retrieve (null if not found)
await db.del(key)                   // Delete
await db.batch(operations)          // Atomic batch of put/del
await db.putObject(key, obj)        // Store nested object as flattened paths
const obj = await db.getObject(key) // Reconstruct nested object

Callback Operations

Same as async, but with Node.js-style error-first callbacks. Also returns a Promise.

db.putCb(key, value, (err) => { /* ... */ })
db.getCb(key, (err, value) => { /* ... */ })
db.delCb(key, (err) => { /* ... */ })
db.batchCb(operations, (err) => { /* ... */ })

Sync Operations

Block the event loop. Use for initialization/migration scripts, not hot paths.

db.putSync(key, value)
const val = db.getSync(key)      // null if not found
db.delSync(key)
db.batchSync(operations)
db.putObjectSync(key, obj)
const obj = db.getObjectSync(key)

Keys and Values

// String keys with delimiter
await db.put('users/alice/name', 'Alice');

// Array keys
await db.put(['users', 'bob', 'name'], 'Bob');

// Custom delimiter
const db = new WaveDB('/path/to/db', { delimiter: ':' });
await db.put('users:charlie:name', 'Charlie');

// String or Buffer values
await db.put('key', 'text value');
await db.put('binary/key', Buffer.from([0x01, 0x02]));

Streams

db.createReadStream({ start, end, reverse, keys, values, keyAsArray })
  .on('data', ({ key, value }) => { /* ... */ })
  .on('end', () => { /* ... */ });

| Option | Default | Description | |--------|---------|-------------| | start | — | Start path (inclusive) | | end | — | End path (exclusive) | | reverse | false | Reverse order | | keys | true | Include keys | | values | true | Include values | | keyAsArray | false | Return keys as arrays |

GraphQL Schema Layer

const { GraphQLLayer } = require('wavedb');

const layer = new GraphQLLayer('/path/to/db', {
  enablePersist: true,
  chunkSize: 4,
  workerThreads: 4
});

// Define schema
layer.parseSchema(`
  type User {
    name: String
    age: Int
  }
`);

// Query
const result = layer.querySync('{ User { name } }');
// { success: true, data: { User: { name: null } }, errors: [] }

// Mutation
const created = layer.mutateSync('mutation { createUser(name: "Alice") { id name } }');
// { success: true, data: { createUser: { id: "abc123", name: "Alice" } } }

// Async variants
const result = await layer.query('{ User { name } }');
const created = await layer.mutate('mutation { createUser(name: "Bob") { id } }');

layer.close();

Introspection

const schema = await layer.query('{ __schema { types { name kind } } }');
const userType = await layer.query('{ __type(name: "User") { name kind fields { name } } }');

Required Fields

Fields marked ! in the schema are required for create mutations:

layer.parseSchema('type User { name: String! age: Int }');

// Missing required field — fails
layer.mutateSync('mutation { createUser(age: "30") { id } }');
// { success: false, errors: ['Missing required fields: name'] }

// Providing required field — succeeds
layer.mutateSync('mutation { createUser(name: "Alice") { id name } }');
// { success: true, data: { createUser: { id: '1', name: 'Alice' } } }

Error Handling

try {
  await db.put('key', 'value');
} catch (err) {
  console.error('Database error:', err.message);
}

Error codes: NOT_FOUND, INVALID_PATH, IO_ERROR, DATABASE_CLOSED, INVALID_ARGUMENT, ENCRYPTION

Performance

Benchmarks on Linux x86_64, Node.js v24, 50MB LRU cache, 32 worker threads, async WAL.

Node.js Sync (ASYNC WAL, 32 worker threads)

| Operation | Throughput | |-----------|------------| | putSync | 1.3K ops/sec | | getSync | 304K ops/sec |

Node.js Concurrent Throughput (ASYNC WAL, 32 worker threads)

| Concurrency | put | get | |-------------|-------|-------| | 1 | 1.2K ops/sec | 45K ops/sec | | 2 | 1.9K ops/sec | 47K ops/sec | | 4 | 2.6K ops/sec | 106K ops/sec | | 8 | 2.6K ops/sec | 102K ops/sec | | 16 | 2.4K ops/sec | 80K ops/sec | | 32 | 2.2K ops/sec | 114K ops/sec |

Tips: Use async operations in production. Scale concurrency to 4-8 for best throughput. Sync puts are WAL-bound; sync gets are memory-speed.

Building from Source

# Build C library
cd WaveDB && mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)

# Build Node.js addon
cd ../bindings/nodejs
npm install
npm run build

# Run tests
npm test

License

MIT