many-level
many-level is a JavaScript library designed to share an abstract-level compatible database over network streams, acting as the spiritual successor to `multileveldown`. Currently at version 2.0.0, it allows a 'host' to expose a LevelDB-like database over any binary stream (e.g., TCP), while 'guests' can connect and interact with it as if it were a local `abstract-level` database instance. It leverages compact Protocol Buffers for efficient message encoding. The project follows a steady release cadence with significant updates marked by major version bumps addressing underlying stream mechanisms and protocol versions. A key differentiator is its optional seamless retry mechanism for guests, which helps maintain connectivity and resume operations, though it comes with a trade-off regarding snapshot guarantees. It ships with TypeScript types, providing a robust development experience.
Common errors
-
Error: Not found
cause A `get` operation was attempted for a key that does not exist in the underlying LevelDB instance, or the connection to the host was interrupted.fixEnsure the key exists before attempting to retrieve it, or implement error handling for `NotFound` errors. Verify network connectivity between guest and host. -
TypeError: db.createRpcStream is not a function
cause Attempting to call `createRpcStream()` on an object that is not a `ManyLevelHost` or `ManyLevelGuest` instance, or before the object is properly initialized.fixEnsure you are calling `createRpcStream()` on an instance created with `new ManyLevelHost(db)` or `new ManyLevelGuest()`. Double-check import paths and object instantiation. -
Error: write after end
cause Attempting to write data to a stream that has already been closed or ended, typically due to a broken or closed network connection.fixImplement robust error handling for stream pipelines, ensuring that operations are only attempted on active connections. The quickstart example demonstrates basic pipeline error handling.
Warnings
- breaking Version 2.0.0 replaced `duplexify` and related stream utilities with `readable-stream` v4. This is an internal change but could affect custom stream handling or integrations that relied on specific `duplexify` behaviors.
- breaking Upgrading from `multileveldown` to `many-level` (version 1.0.0+) requires significant changes as `many-level` is a complete successor, not a drop-in replacement. The API surface, particularly around stream creation and options, has changed.
- gotcha Using the `retry: true` option for `ManyLevelGuest` enables seamless reconnection but disables snapshot guarantees for iterators. New iterators will be created upon reconnect, meaning `db.supports.snapshots` will be `false`.
- breaking The internal `protocol-buffers` dependency was bumped from v4 to v5 in v2.0.0. While typically an internal concern, this could subtly affect wire compatibility or performance characteristics if you are interacting with the raw protocol buffer messages.
Install
-
npm install many-level -
yarn add many-level -
pnpm add many-level
Imports
- ManyLevelHost
const { ManyLevelHost } = require('many-level')import { ManyLevelHost } from 'many-level' - ManyLevelGuest
import ManyLevelGuest from 'many-level'
import { ManyLevelGuest } from 'many-level' - Level
import Level from 'level'
import { Level } from 'level'
Quickstart
import { ManyLevelHost, ManyLevelGuest } from 'many-level';
import { Level } from 'level';
import { pipeline } from 'readable-stream';
import { createServer, connect } from 'net';
import { rmSync } from 'fs';
// Clean up previous test database if it exists
try {
rmSync('./db', { recursive: true, force: true });
} catch (e) {
// ignore
}
const dbPath = './db';
const hostDb = new Level(dbPath);
const host = new ManyLevelHost(hostDb);
const PORT = 9001;
const server = createServer(function (socket) {
pipeline(socket, host.createRpcStream(), socket, (err) => {
if (err) console.error('Host pipeline error:', err.message);
console.log('Host: Client disconnected.');
});
});
server.listen(PORT, async () => {
console.log(`Host server listening on port ${PORT}`);
const guestDb = new ManyLevelGuest();
const guestSocket = connect(PORT);
pipeline(guestSocket, guestDb.createRpcStream(), guestSocket, (err) => {
if (err) console.error('Guest pipeline error:', err.message);
console.log('Guest: Disconnected from host.');
});
try {
await guestDb.put('hello', 'world');
console.log(`Guest: Successfully put 'hello': 'world'`);
const value = await guestDb.get('hello');
console.log(`Guest: Retrieved 'hello': '${value}'`);
await guestDb.del('hello');
console.log(`Guest: Successfully deleted 'hello'.`);
const hostValue = await hostDb.get('hello');
console.log(`Host: Value after guest operation: '${hostValue}'`);
} catch (error) {
console.error('Guest operation failed:', error.message);
} finally {
server.close(() => console.log('Host server closed.'));
await hostDb.close();
console.log('Host database closed.');
try {
rmSync(dbPath, { recursive: true, force: true });
console.log('Cleaned up database files.');
} catch (e) {
console.warn('Failed to clean up database files:', e.message);
}
}
});