Universal abstract-level database for Node.js and browsers
npx @tessl/cli install tessl/npm-level@10.0.0Level is a universal database abstraction layer that automatically selects the appropriate implementation based on the runtime environment. It provides a consistent API for creating lexicographically sorted key-value databases that work seamlessly across Node.js (using LevelDB) and browsers (using IndexedDB).
npm install levelconst { Level } = require('level');For ES modules:
import { Level } from 'level';TypeScript with generics:
import { Level } from 'level';const { Level } = require('level');
// Create a database
const db = new Level('example', { valueEncoding: 'json' });
// Add an entry with key 'a' and value 1
await db.put('a', 1);
// Add multiple entries
await db.batch([{ type: 'put', key: 'b', value: 2 }]);
// Get value of key 'a': 1
const value = await db.get('a');
// Iterate entries with keys that are greater than 'a'
for await (const [key, value] of db.iterator({ gt: 'a' })) {
console.log(value); // 2
}TypeScript usage with generic type parameters:
import { Level } from 'level';
// Specify types of keys and values (any, in the case of json).
// The generic type parameters default to Level<string, string>.
const db = new Level<string, any>('./db', { valueEncoding: 'json' });
// All relevant methods then use those types
await db.put('a', { x: 123 });
// Specify different types when overriding encoding per operation
await db.get<string, string>('a', { valueEncoding: 'utf8' });
// It works the same for sublevels
const abc = db.sublevel('abc');
const xyz = db.sublevel<string, any>('xyz', { valueEncoding: 'json' });Level implements a unified interface that automatically chooses the appropriate backend:
classic-level which provides LevelDB-based persistent storagebrowser-level which provides IndexedDB-based storageabstract-level providing consistent methodsThe Level class extends AbstractLevel<string | Buffer | Uint8Array, KDefault, VDefault> and provides all the functionality from the abstract-level specification.
Creates a new database instance or opens an existing one.
/**
* Database constructor.
* @param location Directory path (relative or absolute) where LevelDB will store its
* files, or in browsers, the name of the IDBDatabase to be opened.
* @param options Options, of which some will be forwarded to open.
*/
constructor(location: string, options?: DatabaseOptions<KDefault, VDefault>);
type DatabaseOptions<K, V> = {
// Encoding options
keyEncoding?: string;
valueEncoding?: string;
// Database-specific options (varies by implementation)
createIfMissing?: boolean;
errorIfExists?: boolean;
compression?: boolean;
cacheSize?: number;
// Additional options from classic-level and browser-level
[key: string]: any;
};Access to database metadata and configuration.
/**
* Location that was passed to the constructor.
*/
get location(): string;Control database opening and closing.
/**
* Open the database. Called automatically by other methods if not already open.
*/
open(): Promise<void>;
open(options: OpenOptions): Promise<void>;
/**
* Close the database. This is an async operation.
*/
close(): Promise<void>;
/**
* Database status property.
*/
get status(): string;
type OpenOptions = {
createIfMissing?: boolean;
errorIfExists?: boolean;
[key: string]: any;
};Store, retrieve, and delete individual key-value pairs.
/**
* Get the value of a key from the database.
* @param key The key to retrieve
* @returns Promise resolving to the value
*/
get(key: KDefault): Promise<VDefault>;
get<K = KDefault, V = VDefault>(key: K, options: GetOptions<K, V>): Promise<V>;
/**
* Check if a key exists in the database.
* @param key The key to check
* @returns Promise resolving to boolean indicating existence
*/
has(key: KDefault): Promise<boolean>;
has<K = KDefault>(key: K, options: HasOptions<K>): Promise<boolean>;
/**
* Put a key-value pair to the database.
* @param key The key to store
* @param value The value to store
*/
put(key: KDefault, value: VDefault): Promise<void>;
put<K = KDefault, V = VDefault>(key: K, value: V, options: PutOptions<K, V>): Promise<void>;
/**
* Delete a key-value pair from the database.
* @param key The key to delete
*/
del(key: KDefault): Promise<void>;
del<K = KDefault>(key: K, options: DelOptions<K>): Promise<void>;
type GetOptions<K, V> = {
keyEncoding?: string;
valueEncoding?: string;
[key: string]: any;
};
type HasOptions<K> = {
keyEncoding?: string;
[key: string]: any;
};
type PutOptions<K, V> = {
keyEncoding?: string;
valueEncoding?: string;
[key: string]: any;
};
type DelOptions<K> = {
keyEncoding?: string;
[key: string]: any;
};Retrieve multiple values or perform multiple operations atomically.
/**
* Get the values of multiple keys from the database.
* @param keys Array of keys to retrieve
* @returns Promise resolving to array of values
*/
getMany(keys: KDefault[]): Promise<VDefault[]>;
getMany<K = KDefault, V = VDefault>(keys: K[], options: GetManyOptions<K, V>): Promise<V[]>;
/**
* Check if multiple keys exist in the database.
* @param keys Array of keys to check
* @returns Promise resolving to array of booleans indicating existence
*/
hasMany(keys: KDefault[]): Promise<boolean[]>;
hasMany<K = KDefault>(keys: K[], options: HasManyOptions<K>): Promise<boolean[]>;
/**
* Perform multiple operations atomically.
* @param operations Array of operations to perform
*/
batch(operations: Array<BatchOperation<typeof this, KDefault, VDefault>>): Promise<void>;
batch<K = KDefault, V = VDefault>(operations: Array<BatchOperation<typeof this, K, V>>, options: BatchOptions<K, V>): Promise<void>;
/**
* Create a chained batch for building multiple operations.
* @returns ChainedBatch instance for adding operations
*/
batch(): ChainedBatch<typeof this, KDefault, VDefault>;
type GetManyOptions<K, V> = {
keyEncoding?: string;
valueEncoding?: string;
[key: string]: any;
};
type HasManyOptions<K> = {
keyEncoding?: string;
[key: string]: any;
};
type BatchOptions<K, V> = {
keyEncoding?: string;
valueEncoding?: string;
[key: string]: any;
};
type BatchOperation<TDatabase, K, V> = {
type: 'put' | 'del';
key: K;
value?: V; // Required for 'put', ignored for 'del'
};
interface ChainedBatch<TDatabase, K, V> {
put(key: K, value: V): ChainedBatch<TDatabase, K, V>;
del(key: K): ChainedBatch<TDatabase, K, V>;
clear(): ChainedBatch<TDatabase, K, V>;
write(): Promise<void>;
close(): Promise<void>;
}Create isolated database sections that share the same underlying storage but have separate key spaces.
/**
* Create a sublevel that acts like a separate database with its own key space.
* @param name Sublevel name/prefix
* @param options Optional configuration for the sublevel
* @returns New Level instance scoped to the sublevel
*/
sublevel<SK = KDefault, SV = VDefault>(name: string, options?: DatabaseOptions<SK, SV>): Level<SK, SV>;Create iterators for traversing database entries with range queries and filtering.
/**
* Create an iterator for key-value pairs.
* @param options Iterator configuration options
* @returns Iterator instance
*/
iterator(): Iterator<typeof this, KDefault, VDefault>;
iterator<K = KDefault, V = VDefault>(options: IteratorOptions<K, V>): Iterator<typeof this, K, V>;
/**
* Create an iterator for keys only.
* @param options Iterator configuration options
* @returns KeyIterator instance
*/
keys(): KeyIterator<typeof this, KDefault>;
keys<K = KDefault>(options: KeyIteratorOptions<K>): KeyIterator<typeof this, K>;
/**
* Create an iterator for values only.
* @param options Iterator configuration options
* @returns ValueIterator instance
*/
values(): ValueIterator<typeof this, KDefault, VDefault>;
values<K = KDefault, V = VDefault>(options: ValueIteratorOptions<K, V>): ValueIterator<typeof this, K, V>;
type IteratorOptions<K, V> = {
gt?: K; // Greater than
gte?: K; // Greater than or equal
lt?: K; // Less than
lte?: K; // Less than or equal
reverse?: boolean;
limit?: number;
keyEncoding?: string;
valueEncoding?: string;
[key: string]: any;
};
type KeyIteratorOptions<K> = {
gt?: K;
gte?: K;
lt?: K;
lte?: K;
reverse?: boolean;
limit?: number;
keyEncoding?: string;
[key: string]: any;
};
type ValueIteratorOptions<K, V> = {
gt?: K;
gte?: K;
lt?: K;
lte?: K;
reverse?: boolean;
limit?: number;
keyEncoding?: string;
valueEncoding?: string;
[key: string]: any;
};
interface Iterator<TDatabase, K, V> {
next(): Promise<[K, V] | undefined>;
all(): Promise<Array<[K, V]>>;
close(): Promise<void>;
[Symbol.asyncIterator](): AsyncIterableIterator<[K, V]>;
}
interface KeyIterator<TDatabase, K> {
next(): Promise<K | undefined>;
all(): Promise<K[]>;
close(): Promise<void>;
[Symbol.asyncIterator](): AsyncIterableIterator<K>;
}
interface ValueIterator<TDatabase, K, V> {
next(): Promise<V | undefined>;
all(): Promise<V[]>;
close(): Promise<void>;
[Symbol.asyncIterator](): AsyncIterableIterator<V>;
}Delete all entries or a range of entries from the database.
/**
* Delete all entries or a range of entries from the database.
* @param options Range and encoding options
*/
clear(): Promise<void>;
clear<K = KDefault>(options: ClearOptions<K>): Promise<void>;
type ClearOptions<K> = {
gt?: K; // Greater than
gte?: K; // Greater than or equal
lt?: K; // Less than
lte?: K; // Less than or equal
reverse?: boolean;
limit?: number;
keyEncoding?: string;
[key: string]: any;
};Attach and detach resources that should be managed with the database lifecycle.
/**
* Attach a resource to be managed by the database lifecycle.
* @param resource Resource to attach (must have close method)
*/
attachResource(resource: any): void;
/**
* Detach a previously attached resource.
* @param resource Resource to detach
*/
detachResource(resource: any): void;Access to database capabilities and supported features.
/**
* Database features and capabilities.
*/
get supports(): {
[key: string]: boolean;
};Utility methods for working with encodings.
/**
* Get or normalize key encoding.
* @param encoding Encoding name or options
* @returns Normalized encoding
*/
keyEncoding(): any;
keyEncoding(encoding: string | any): any;
/**
* Get or normalize value encoding.
* @param encoding Encoding name or options
* @returns Normalized encoding
*/
valueEncoding(): any;
valueEncoding(encoding: string | any): any;Advanced database operations and deferred execution.
/**
* Defer function execution until database is in the correct state.
* @param fn Function to defer
* @param options Defer options
*/
defer(fn: () => void): void;
defer(fn: () => void, options: DeferOptions): void;
type DeferOptions = {
[key: string]: any;
};The Level class extends EventEmitter and emits various events during database operations.
/**
* Add event listener for database events.
* @param event Event name
* @param listener Event handler function
*/
on(event: 'open' | 'opening' | 'close' | 'closing' | 'put' | 'del' | 'batch' | 'clear', listener: (...args: any[]) => void): this;
/**
* Remove event listener.
* @param event Event name
* @param listener Event handler function
*/
off(event: 'open' | 'opening' | 'close' | 'closing' | 'put' | 'del' | 'batch' | 'clear', listener: (...args: any[]) => void): this;
/**
* Add one-time event listener.
* @param event Event name
* @param listener Event handler function
*/
once(event: 'open' | 'opening' | 'close' | 'closing' | 'put' | 'del' | 'batch' | 'clear', listener: (...args: any[]) => void): this;
/**
* Emit an event.
* @param event Event name
* @param args Event arguments
*/
emit(event: string, ...args: any[]): boolean;const db = new Level('my-db', { valueEncoding: 'json' });
await db.put('user:1', { name: 'Alice', age: 30 });
const user = await db.get('user:1'); // { name: 'Alice', age: 30 }// Atomic batch operations
await db.batch([
{ type: 'put', key: 'user:1', value: { name: 'Alice' } },
{ type: 'put', key: 'user:2', value: { name: 'Bob' } },
{ type: 'del', key: 'user:3' }
]);
// Chained batch operations
const batch = db.batch();
batch.put('key1', 'value1');
batch.put('key2', 'value2');
batch.del('key3');
await batch.write();// Iterate over a range of keys
for await (const [key, value] of db.iterator({ gte: 'user:1', lt: 'user:9' })) {
console.log(`${key}: ${JSON.stringify(value)}`);
}
// Get all keys starting with 'user:'
const userKeys = await db.keys({ gte: 'user:', lt: 'user;\xFF' }).all();
// Get values in reverse order
const recentValues = await db.values({ reverse: true, limit: 10 }).all();interface User {
name: string;
email: string;
age: number;
}
// Create typed database
const userDb = new Level<string, User>('users', { valueEncoding: 'json' });
// Type-safe operations
await userDb.put('alice', { name: 'Alice', email: 'alice@example.com', age: 30 });
const alice: User = await userDb.get('alice');
// Override types for specific operations
const rawData = await userDb.get<string, string>('alice', { valueEncoding: 'utf8' });const db = new Level('my-db', { valueEncoding: 'json' });
// Create sublevels for different data types
const users = db.sublevel('users');
const posts = db.sublevel('posts');
// Each sublevel acts like a separate database
await users.put('alice', { name: 'Alice', age: 30 });
await posts.put('post1', { title: 'Hello World', author: 'alice' });
// TypeScript with sublevel typing
const typedUsers = db.sublevel<string, User>('users', { valueEncoding: 'json' });// Check if a single key exists
const exists = await db.has('user:1');
console.log(exists); // true or false
// Check multiple keys at once
const keysExist = await db.hasMany(['user:1', 'user:2', 'user:3']);
console.log(keysExist); // [true, false, true]// Clear all entries
await db.clear();
// Clear a range of entries
await db.clear({ gte: 'user:', lt: 'user;\xFF' });
// Clear with limit
await db.clear({ limit: 100 });const db = new Level('my-db', { valueEncoding: 'json' });
// Listen for database events
db.on('open', () => console.log('Database opened'));
db.on('put', (key, value) => console.log(`Put ${key}: ${JSON.stringify(value)}`));
db.on('del', (key) => console.log(`Deleted ${key}`));
// One-time listeners
db.once('close', () => console.log('Database closed'));
// Operations will emit events
await db.put('test', { data: 'example' }); // Emits 'put' event
await db.del('test'); // Emits 'del' event
await db.close(); // Emits 'close' eventconst db = new Level('my-db');
// Create a resource that needs cleanup
const customResource = {
close: async () => console.log('Custom resource closed')
};
// Attach resource to database lifecycle
db.attachResource(customResource);
// When database closes, attached resources are also closed
await db.close(); // Also closes customResourceLevel operations may throw various errors:
get() when a key doesn't existtry {
const value = await db.get('nonexistent-key');
} catch (error) {
if (error.code === 'LEVEL_NOT_FOUND') {
console.log('Key not found');
}
}