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

@stoolap/node

v0.3.7

Published

High-performance Node.js driver for Stoolap embedded SQL database

Readme

@stoolap/node

High-performance JavaScript driver for Stoolap, a modern embedded SQL database with MVCC, time-travel queries, and full ACID compliance.

Built with a native N-API C addon for minimal overhead. Works with Node.js, Bun, and Deno. Provides both async and sync APIs.

License Node Bun Deno

Installation

npm install @stoolap/node

The stoolap engine shared library is pre-built for:

  • macOS (x64, ARM64)
  • Linux (x64, ARM64 GNU)
  • Windows (x64 MSVC)

A C compiler is required to build the thin N-API addon on install (compiled automatically via node-gyp):

  • macOS: xcode-select --install
  • Linux: sudo apt-get install build-essential (or equivalent)
  • Windows: Visual Studio Build Tools with "Desktop development with C++"

Quick Start

// ESM
import { Database } from '@stoolap/node';

// CommonJS
const { Database } = require('@stoolap/node');
const db = await Database.open(':memory:');

await db.exec(`
  CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT
  )
`);

// Insert with positional parameters ($1, $2, ...)
await db.execute(
  'INSERT INTO users (id, name, email) VALUES ($1, $2, $3)',
  [1, 'Alice', '[email protected]']
);

// Insert with named parameters (:key)
await db.execute(
  'INSERT INTO users (id, name, email) VALUES (:id, :name, :email)',
  { id: 2, name: 'Bob', email: '[email protected]' }
);

// Query rows as objects
const users = await db.query('SELECT * FROM users ORDER BY id');
// [{ id: 1, name: 'Alice', email: '[email protected]' }, ...]

// Query single row
const user = await db.queryOne('SELECT * FROM users WHERE id = $1', [1]);
// { id: 1, name: 'Alice', email: '[email protected]' }

// Query in raw columnar format (faster, no per-row object creation)
const raw = await db.queryRaw('SELECT id, name FROM users ORDER BY id');
// { columns: ['id', 'name'], rows: [[1, 'Alice'], [2, 'Bob']] }

await db.close();

API

Database

// In-memory
const db = await Database.open(':memory:');
const db = await Database.open('');
const db = await Database.open('memory://');

// File-based (data persists across restarts)
const db = await Database.open('./mydata');
const db = await Database.open('file:///absolute/path/to/db');

Async Methods

| Method | Returns | Description | |--------|---------|-------------| | Database.open(path) | Promise<Database> | Open a database | | execute(sql, params?) | Promise<RunResult> | Execute DML statement | | exec(sql) | Promise<void> | Execute a DDL statement | | query(sql, params?) | Promise<Object[]> | Query rows as objects | | queryOne(sql, params?) | Promise<Object \| null> | Query single row | | queryRaw(sql, params?) | Promise<{columns, rows}> | Query in columnar format | | begin() | Promise<Transaction> | Begin a transaction | | close() | Promise<void> | Close the database |

Sync Methods

Sync methods run on the main thread. Faster for simple operations but block the event loop.

| Method | Returns | Description | |--------|---------|-------------| | Database.openSync(path) | Database | Open a database | | clone() | Database | Clone handle (shared engine, own state) | | executeSync(sql, params?) | RunResult | Execute DML statement | | execSync(sql) | void | Execute a DDL statement | | querySync(sql, params?) | Object[] | Query rows as objects | | queryOneSync(sql, params?) | Object \| null | Query single row | | queryRawSync(sql, params?) | {columns, rows} | Query in columnar format | | executeBatchSync(sql, paramsArray) | RunResult | Execute with multiple param sets | | beginSync() | Transaction | Begin a transaction | | prepare(sql) | PreparedStatement | Create a prepared statement | | closeSync() | void | Close the database |

RunResult is { changes: number }. It can be imported as a type:

import { Database, RunResult } from '@stoolap/node';

Persistence

File-based databases persist data to disk using WAL (Write-Ahead Logging) and periodic snapshots. Data survives process restarts.

const db = await Database.open('./mydata');

await db.exec('CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT)');
await db.execute('INSERT INTO kv VALUES ($1, $2)', ['hello', 'world']);
await db.close();

// Reopen: data is still there
const db2 = await Database.open('./mydata');
const row = await db2.queryOne('SELECT * FROM kv WHERE key = $1', ['hello']);
// { key: 'hello', value: 'world' }
await db2.close();
Configuration

Pass configuration as query parameters in the path:

// Maximum durability: fsync on every WAL write
const db = await Database.open('./mydata?sync=full');

// High throughput: no fsync, larger buffers
const db = await Database.open('./mydata?sync=none&wal_buffer_size=131072');

// Custom snapshot interval with compression
const db = await Database.open('./mydata?snapshot_interval=60&compression=on');

// Multiple options
const db = await Database.open(
  './mydata?sync=full&snapshot_interval=120&keep_snapshots=10&wal_max_size=134217728'
);
Sync Modes

Controls the durability vs. performance trade-off:

| Mode | Value | Description | |------|-------|-------------| | none | sync=none | No fsync. Fastest, data may be lost on crash | | normal | sync=normal | Fsync on commit batches. Good balance (default) | | full | sync=full | Fsync on every WAL write. Slowest, maximum durability |

All Configuration Parameters

| Parameter | Default | Description | |-----------|---------|-------------| | sync | normal | Sync mode: none, normal, or full | | snapshot_interval | 300 | Seconds between automatic snapshots (5 min) | | keep_snapshots | 5 | Number of snapshot files to retain | | wal_flush_trigger | 32768 | WAL flush trigger size in bytes (32 KB) | | wal_buffer_size | 65536 | WAL buffer size in bytes (64 KB) | | wal_max_size | 67108864 | Max WAL file size before rotation (64 MB) | | commit_batch_size | 100 | Commits to batch before syncing (normal mode) | | sync_interval_ms | 10 | Minimum ms between syncs (normal mode) | | wal_compression | on | LZ4 compression for WAL entries | | snapshot_compression | on | LZ4 compression for snapshots | | compression | | Set both wal_compression and snapshot_compression | | compression_threshold | 64 | Minimum bytes before compressing an entry |

Cloning

clone() creates a new Database handle that shares the same underlying engine (data, indexes, transactions) but has its own executor and error state. Useful for concurrent access patterns such as worker threads.

const db = await Database.open('./mydata');
const db2 = db.clone();

// Both see the same data
await db.execute('INSERT INTO users VALUES ($1, $2)', [1, 'Alice']);
const row = db2.queryOneSync('SELECT * FROM users WHERE id = $1', [1]);
// { id: 1, name: 'Alice' }

// Each clone must be closed independently
await db2.close();
await db.close();

Raw Query Format

queryRaw / queryRawSync return { columns: string[], rows: any[][] } instead of an array of objects. Faster when you don't need named keys.

const raw = db.queryRawSync('SELECT id, name, email FROM users ORDER BY id');
console.log(raw.columns); // ['id', 'name', 'email']
console.log(raw.rows);    // [[1, 'Alice', '[email protected]'], [2, 'Bob', '[email protected]']]

Batch Execution

Execute the same SQL with multiple parameter sets in a single call. Automatically wraps in a transaction.

const result = db.executeBatchSync(
  'INSERT INTO users VALUES ($1, $2, $3)',
  [
    [1, 'Alice', '[email protected]'],
    [2, 'Bob', '[email protected]'],
    [3, 'Charlie', '[email protected]'],
  ]
);
console.log(result.changes); // 3

PreparedStatement

Prepared statements parse SQL once and reuse the cached execution plan on every call. No parsing or cache lookup overhead per execution.

const insert = db.prepare('INSERT INTO users VALUES ($1, $2, $3)');
insert.executeSync([1, 'Alice', '[email protected]']);
insert.executeSync([2, 'Bob', '[email protected]']);

const lookup = db.prepare('SELECT * FROM users WHERE id = $1');
const user = lookup.queryOneSync([1]);
// { id: 1, name: 'Alice', email: '[email protected]' }

Methods

All methods mirror Database but without the sql parameter (it's bound at prepare time).

| Async | Sync | Description | |-------|------|-------------| | execute(params?) | executeSync(params?) | Execute DML statement | | query(params?) | querySync(params?) | Query rows as objects | | queryOne(params?) | queryOneSync(params?) | Query single row | | queryRaw(params?) | queryRawSync(params?) | Query in columnar format | | | executeBatchSync(paramsArray) | Execute with multiple param sets | | | finalize() | Release the prepared statement |

Property: sql returns the SQL text of this prepared statement.

Async Prepared Statement

const stmt = db.prepare('SELECT * FROM users WHERE id = $1');

const rows = await stmt.query([1]);
const one = await stmt.queryOne([1]);
const raw = await stmt.queryRaw([1]);
const result = await stmt.execute([1]); // for DML

Sync Prepared Statement

const stmt = db.prepare('SELECT * FROM users WHERE id = $1');

const rows = stmt.querySync([1]);
const one = stmt.queryOneSync([1]);
const raw = stmt.queryRawSync([1]);
const result = stmt.executeSync([1]); // for DML

Batch with Prepared Statement

const insert = db.prepare('INSERT INTO users VALUES ($1, $2, $3)');
const result = insert.executeBatchSync([
  [1, 'Alice', '[email protected]'],
  [2, 'Bob', '[email protected]'],
  [3, 'Charlie', '[email protected]'],
]);
console.log(result.changes); // 3

Transaction

Methods

| Async | Sync | Description | |-------|------|-------------| | execute(sql, params?) | executeSync(sql, params?) | Execute DML statement | | query(sql, params?) | querySync(sql, params?) | Query rows as objects | | queryOne(sql, params?) | queryOneSync(sql, params?) | Query single row | | queryRaw(sql, params?) | queryRawSync(sql, params?) | Query in columnar format | | commit() | commitSync() | Commit the transaction | | rollback() | rollbackSync() | Rollback the transaction | | | executeBatchSync(sql, paramsArray) | Execute with multiple param sets |

Async Transaction

const tx = await db.begin();
try {
  await tx.execute('INSERT INTO users VALUES ($1, $2, $3)', [1, 'Alice', '[email protected]']);
  await tx.execute('INSERT INTO users VALUES ($1, $2, $3)', [2, 'Bob', '[email protected]']);

  // Read within the transaction (sees uncommitted changes)
  const rows = await tx.query('SELECT * FROM users');
  const one = await tx.queryOne('SELECT * FROM users WHERE id = $1', [1]);
  const raw = await tx.queryRaw('SELECT id, name FROM users');

  await tx.commit();
} catch (e) {
  await tx.rollback();
  throw e;
}

Sync Transaction

const tx = db.beginSync();
try {
  tx.executeSync('INSERT INTO users VALUES ($1, $2, $3)', [1, 'Alice', '[email protected]']);
  tx.executeSync('INSERT INTO users VALUES ($1, $2, $3)', [2, 'Bob', '[email protected]']);

  const rows = tx.querySync('SELECT * FROM users');
  const one = tx.queryOneSync('SELECT * FROM users WHERE id = $1', [1]);
  const raw = tx.queryRawSync('SELECT id, name FROM users');

  tx.commitSync();
} catch (e) {
  tx.rollbackSync();
  throw e;
}

Batch in Transaction

const tx = db.beginSync();
const result = tx.executeBatchSync(
  'INSERT INTO users VALUES ($1, $2, $3)',
  [
    [1, 'Alice', '[email protected]'],
    [2, 'Bob', '[email protected]'],
  ]
);
tx.commitSync();
console.log(result.changes); // 2

Parameters

Both positional and named parameters are supported across all methods:

// Positional ($1, $2, ...)
db.querySync('SELECT * FROM users WHERE id = $1 AND name = $2', [1, 'Alice']);

// Named (:key)
db.querySync(
  'SELECT * FROM users WHERE id = :id AND name = :name',
  { id: 1, name: 'Alice' }
);

Error Handling

All methods throw on errors (invalid SQL, constraint violations, etc.):

// Async
try {
  await db.execute('INSERT INTO users VALUES ($1, $2)', [1, null]); // NOT NULL violation
} catch (err) {
  console.error(err.message);
}

// Sync
try {
  db.executeSync('SELECTX * FROM users'); // syntax error
} catch (err) {
  console.error(err.message);
}

Supported Types

| JavaScript | Stoolap | Notes | |-----------|---------|-------| | number (integer) | INTEGER | | | number (float) | FLOAT | | | string | TEXT | | | boolean | BOOLEAN | | | null / undefined | NULL | | | BigInt | INTEGER | | | Date | TIMESTAMP | | | Float32Array | VECTOR(N) | Returned as Float32Array | | Buffer | TEXT (UTF-8) | | | Object / Array | JSON (stringified) | |

Vector Support

Stoolap supports native vector storage and similarity search. Vectors are returned as Float32Array and can be passed as Float32Array bind parameters.

// Create a table with a vector column
await db.exec('CREATE TABLE embeddings (id INTEGER PRIMARY KEY, vec VECTOR(3))');

// Insert vectors via SQL string literals
await db.execute("INSERT INTO embeddings VALUES (1, '[0.1, 0.2, 0.3]')");

// Query: vectors are returned as Float32Array
const row = await db.queryOne('SELECT vec FROM embeddings WHERE id = 1');
console.log(row.vec);              // Float32Array(3) [0.1, 0.2, 0.3]
console.log(row.vec instanceof Float32Array); // true

// k-NN search with distance functions
const nearest = await db.query(`
  SELECT id, VEC_DISTANCE_L2(vec, '[0.15, 0.25, 0.35]') AS dist
  FROM embeddings ORDER BY dist LIMIT 5
`);

// HNSW index for fast approximate nearest neighbor search
await db.exec('CREATE INDEX idx ON embeddings(vec) USING HNSW');

Available distance functions: VEC_DISTANCE_L2, VEC_DISTANCE_COSINE, VEC_DISTANCE_IP.

See the Stoolap Vector Search docs for full details on HNSW indexes, distance metrics, and configuration.

Building from Source

Requires:

  • Node.js >= 18
  • C compiler (gcc, clang, or MSVC)
  • node-gyp and its prerequisites

The stoolap shared library (libstoolap.dylib / libstoolap.so / stoolap.dll) must be available, either via a platform package or built from the Stoolap repository.

git clone https://github.com/stoolap/stoolap-node.git
cd stoolap-node
npm install
npm test

License

Apache 2.0 - see LICENSE for details.