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

@cqlite/node

v0.15.0

Published

Node.js bindings for CQLite - read Apache Cassandra 5.0 SSTables without cluster dependencies

Readme

@cqlite/node

Node.js bindings for CQLite - a high-performance library for reading Apache Cassandra 5.0 SSTable files locally, without requiring a running Cassandra cluster.

Installation

npm install @cqlite/node

Quick Start

import { Database } from '@cqlite/node';

// Open a database with schema
const db = await Database.open('path/to/sstables', { schema: 'schema.cql' });

// Execute queries (executeNative() returns native JS types with full precision)
const result = await db.executeNative('SELECT * FROM keyspace.table LIMIT 10');
for (const row of result.rows) {
  console.log(row.name);
}

await db.close();

⚠️ Warning: execute() is deprecated and will be removed in the next major. Prefer executeNative(). execute() returns lossy legacy JSON encodings:

  • blob → base64 string (not a Buffer)
  • timestamp → ISO-8601 string (not a Date)
  • varint"0x{hex}" string
  • decimal"decimal:{scale}:0x{hex}" string
  • date/timenumber (days-since-epoch / nanoseconds-since-midnight)

It is also slower (JSON off-loop, then JS on-loop — a double conversion). Calling execute() emits a one-time DeprecationWarning. Use executeNative() for native types (BigInt, Buffer, Date, Set, Map) with full fidelity. (bigint/counter currently come back as an exact BigInt on this napi build, so they are not presently rounded — but execute() is unsupported regardless.)

Features

  • Zero cluster dependency - Read SSTable files directly from disk
  • Full CQL type support - All primitive types, collections, UDTs, and frozen types
  • Native JavaScript types - BigInt, Date, Buffer, Set, Map via executeNative()
  • Memory-efficient streaming - Configure buffer sizes for large datasets
  • Thread-safe - Safe concurrent access from multiple workers
  • Cross-platform - Linux (x86_64, ARM64), macOS (Intel, Apple Silicon), Windows

Supported Platforms

| Platform | Architecture | Status | |----------|--------------|--------| | Linux | x86_64 | ✅ | | Linux | ARM64 | ✅ | | macOS | Intel (x86_64) | ✅ | | macOS | Apple Silicon | ✅ | | Windows | x64 | ✅ |

Requirements

  • Node.js 18+
  • Cassandra 5.0 SSTable files

API Reference

Opening a Database

import { Database } from '@cqlite/node';

// With schema file
const db = await Database.open('/path/to/sstables', {
  schema: '/path/to/schema.cql',
});

// Always close when done
await db.close();

The close() method is idempotent - safe to call multiple times.

Executing Queries

// Simple query - executeNative() returns native JS types with full precision
const result = await db.executeNative('SELECT * FROM keyspace.table');
for (const row of result.rows) {
  console.log(row);
}

// With LIMIT
const limited = await db.executeNative('SELECT name, age FROM users LIMIT 100');

// Access query metadata
console.log(`Rows returned: ${result.rowCount}`);
console.log(`Execution time: ${result.executionTimeMs}ms`);
console.log(`Columns: ${result.columns.map(c => c.name).join(', ')}`);

Prefer executeNative() over the deprecated execute() — see the warning at the top of this README for the precision/encoding hazards of execute().

Native Types with executeNative()

Use executeNative() to get native JavaScript types instead of JSON-serializable values:

const result = await db.executeNative('SELECT * FROM keyspace.table');
for (const row of result.rows) {
  // BigInt for CQL bigint/varint
  const balance: bigint = row.balance;

  // Date for CQL timestamp
  const created: Date = row.created;

  // Buffer for CQL blob
  const data: Buffer = row.blob_data;

  // Set for CQL set
  const tags: Set<string> = row.tags;

  // Map for CQL map
  const metadata: Map<string, string> = row.metadata;
}

JSON Encoding (deprecated execute() method)

⚠️ Deprecated — removed in the next major. execute() returns lossy legacy JSON encodings and is slower than executeNative(). In particular blob comes back as a base64 string, timestamp as an ISO-8601 string, and varint/decimal as bespoke non-round-trippable strings. This section documents the encoding for the few callers that still depend on it; new code should use executeNative().

The execute() method returns JSON-serializable values. For most types this works intuitively, but varint and decimal types use a hex-based encoding to preserve arbitrary precision:

// Using execute() - hex encoding (deprecated)
const result = await db.execute('SELECT amount FROM transactions');
console.log(result.rows[0].amount);
// Varint: "0x7f" (127), "0xff" (-1), "0x0100" (256)
// Decimal: "decimal:2:0x7b" (1.23), "decimal:2:0xee29" (-45.67)

// Using executeNative() - proper types (recommended)
const native = await db.executeNative('SELECT amount FROM transactions');
console.log(native.rows[0].amount);
// Varint: 127n (BigInt)
// Decimal: "1.23" (human-readable string)

Hex Format Details:

  • varint: "0x{hex}" - Two's complement big-endian hex encoding
  • decimal: "decimal:{scale}:0x{hex}" - Scale (decimal places) + hex-encoded unscaled value

Recommendation: Use executeNative(). The execute() method is deprecated (removed in the next major); its JSON encoding is lossy (blob/timestamp/varint/ decimal come back as bespoke strings) and it is slower than executeNative().

Column Metadata

Each query result includes column information:

const result = await db.executeNative('SELECT * FROM keyspace.table');

for (const col of result.columns) {
  console.log(`${col.name}: ${col.dataType}`);
  console.log(`  nullable: ${col.nullable}`);
  console.log(`  position: ${col.position}`);
}

Database Statistics

const stats = await db.getStats();
console.log(`SSTables: ${stats.totalSstables}`);
console.log(`Total rows: ${stats.totalRows}`);
console.log(`Memory: ${stats.memoryUsedBytes} bytes`);

Refreshing SSTables (v0.13)

If Cassandra (or another process) writes new SSTables while your Database handle is open, call refresh() to re-discover them. Refresh is explicit-only (CQLite never rescans behind your back) and atomic / fail-closed: if any newly found generation fails to open, the swap is rolled back and the handle keeps serving the prior, consistent set of readers.

// ... time passes; Cassandra flushes/compacts new SSTables to disk ...

const report = await db.refresh();
console.log(`Tables scanned:  ${report.tablesScanned}`);
console.log(`Readers added:   ${report.readersAdded}`);
console.log(`Readers removed: ${report.readersRemoved}`);

// Subsequent queries see the newly discovered data
const result = await db.executeNative('SELECT * FROM keyspace.table');

refresh(): Promise<RefreshReport> resolves to a RefreshReport with the numeric fields tablesScanned, readersAdded, and readersRemoved.

Result Byte Budget (v0.13)

Non-streaming queries are bounded by a result-size budget of 64 MiB by default. When the materialized result's running byte estimate exceeds the budget, the query rejects with a CqliteError whose code === 'QUERY', directing you to add a LIMIT clause or use executeStreaming(). Streaming queries are not subject to this budget.

try {
  const result = await db.executeNative('SELECT * FROM keyspace.big_table');
  for (const row of result.rows) {
    process(row);
  }
} catch (e) {
  if (e.code === 'QUERY') {
    // Result exceeded the 64 MiB byte budget — add a LIMIT or stream instead
    for await (const row of db.executeStreaming('SELECT * FROM keyspace.big_table')) {
      process(row);
    }
  } else {
    throw e;
  }
}

OpenTelemetry Tracing (v0.13)

CQLite can emit OpenTelemetry traces when built with the observability Cargo feature; without that feature the configuration is accepted but is a no-op. Pass an otel option to Database.open():

const db = await Database.open('path/to/sstables', {
  schema: 'schema.cql',
  otel: {
    enabled: true,                       // default false
    endpoint: 'http://localhost:4317',   // default 'http://localhost:4317'
    protocol: 'grpc',                    // 'grpc' (default) or 'http'
    serviceName: 'cqlite',               // default 'cqlite'
    serviceVersion: '0.13.0',            // default: package version
    samplingRatio: 1.0,                  // default 1.0
    timeoutMs: 10000,                    // default 10000
  },
});

Options are layered over the CQLITE_OTEL_* environment variables.

Error Handling

All errors include structured metadata for programmatic handling:

import { Database } from '@cqlite/node';

try {
  const db = await Database.open('/path/to/data');
  const result = await db.executeNative('SELECT * FROM keyspace.table');
} catch (e) {
  // Error code for programmatic handling
  console.log(`Code: ${e.code}`);        // 'IO', 'SCHEMA', 'QUERY', 'PARSE', etc.

  // Error category
  console.log(`Category: ${e.category}`); // 'System', 'Schema', 'Query', etc.

  // Whether the operation can be retried
  console.log(`Recoverable: ${e.isRecoverable}`);

  // Original error message
  console.log(`Message: ${e.message}`);
}

Error Codes:

| Code | Category | Description | Recoverable | |------|----------|-------------|-------------| | IO | System | File system errors | Yes | | SCHEMA | Schema | Schema parsing/validation | No | | QUERY | Query | Query execution failures | No | | PARSE | Data | CQL syntax errors | No | | CONFIG | Configuration | Invalid configuration | No | | STORAGE | Storage | Storage engine errors | No | | NOT_FOUND | NotFound | Table/resource not found | No | | INVALID_INPUT | Logic | Invalid operation (e.g., closed db) | No |

Type Conversions

CQL types are automatically converted to JavaScript types:

| CQL Type | JavaScript Type | Notes | |----------|-----------------|-------| | text, varchar, ascii | string | | | int, smallint, tinyint | number | | | bigint, varint, counter | bigint | via executeNative() | | float, double | number | | | decimal | string | Preserves precision | | boolean | boolean | | | blob | Buffer | via executeNative() | | timestamp | Date | via executeNative() | | date | Date | via executeNative() | | time | bigint | Nanoseconds since midnight, via executeNative() | | duration | object | { months, days, nanos } | | uuid, timeuuid | string | Lowercase formatted | | inet | string | IP address string | | list<T> | T[] | | | set<T> | Set<T> | via executeNative() | | map<K,V> | Map<K,V> | via executeNative() | | tuple<...> | [...] | Array | | frozen<T> | Inner type | Unwrapped | | UDT | object | With _type and _keyspace fields |

* Note: With execute(), varint returns "0x{hex}" and decimal returns "decimal:{scale}:0x{hex}". Use executeNative() for human-readable formats.

Write Operations

CQLite v0.9.0 adds write support to the Node.js bindings. Open the database with writable: true and a writeDir to enable write operations.

const { Database } = require('@cqlite/node');

const db = await Database.open('path/to/sstables', {
  schema: 'schema.cql',
  writable: true,
  writeDir: '/tmp/my-writes',
});

// Write rows via CQL INSERT, UPDATE, or DELETE
await db.executeNative(
  "INSERT INTO test_basic.simple_table (id, name, age) " +
  "VALUES (22222222-2222-2222-2222-222222222222, 'Bob', 25)"
);
await db.executeNative(
  "UPDATE test_basic.simple_table SET age = 26 " +
  "WHERE id = 22222222-2222-2222-2222-222222222222"
);

// Flush the in-memory write buffer (memtable) to an SSTable on disk.
// Returns the path to the flushed Data.db file, or "" if memtable was empty.
const path = await db.flushRun();
console.log('Flushed to:', path);

// Run background compaction within a time budget
const report = await db.maintenanceStep({ budgetMs: 100 });
console.log(`Merged ${report.rowsMerged} rows in ${report.timeSpentMs}ms`);
if (report.pendingCompaction) {
  console.log('More compaction work available');
}

// Inspect write statistics (synchronous getter)
const stats = db.writeStats;
console.log('Memtable size:', stats.memtableSizeBytes, 'bytes');
console.log('Total flushed:', stats.totalWrittenBytes, 'bytes');

await db.close();

Write API

| Method / Property | Description | |-------------------|-------------| | db.executeNative(cql) | Execute a CQL INSERT, UPDATE, or DELETE statement (recommended) | | db.execute(cql) | Deprecated (removed next major; emits a DeprecationWarning). Same DML behavior as executeNative(), but lossy for SELECT — use executeNative() | | db.flushRun() | Flush memtable to SSTable; returns the Data.db path or "" if memtable was empty | | db.maintenanceStep(options?) | Run STCS compaction for up to options.budgetMs ms (default: 100); returns MaintenanceReport | | db.writeStats | Synchronous getter: memtableSizeBytes, memtableRowCount, totalWrittenBytes, l0SstableCount |

Known Limitations

  • Counter columns cannot be written — execute() throws CqliteError for counter mutations.
  • BTI-format index files are not produced; the writer emits BIG format.

See docs/write-support-limitations.md for the full limitations reference.

Examples

See the examples/ directory for complete working examples:

Streaming concurrency caveat: executeStreaming() fetches each batch of K = bufferSize rows on a libuv threadpool thread, so N concurrent streams can each occupy a libuv threadpool thread for the duration of a batch fetch. Heavy concurrent fs/crypto work in the same process may see added latency until the follow-up (#1901) moves streaming off the libuv pool onto the tokio runtime. If an error occurs mid-stream, rows already read in the in-flight batch (up to bufferSize) are not delivered — the iterator rejects with the error (errors are terminal).

Resources

License

MIT OR Apache-2.0

Links