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

albedo-node

v0.10.1

Published

High-performance embedded database for Node.js with native bindings, BSON support, indexing, and replication

Readme

albedo-node

Native Node.js/Bun bindings for the Albedo embedded document database.

This package wraps the core database engine with a concise TypeScript API, ships prebuilt binaries for common platforms, and exposes BSON helpers plus replication utilities so you can embed Albedo buckets directly in your applications.

Features

  • 🧱 Access to the full Albedo bucket API from Node.js or Bun
  • 📦 Precompiled native modules for Linux (glibc & musl), macOS, and arm64/x64
  • 🔁 Generator-based iterators for queries, transforms, and live subscriptions
  • 🔄 WAL-based replication helpers with explicit cursors and batches
  • 🧪 TypeScript typings and Bun-based test suite

Installation

Install with your preferred package manager (Node.js 18+ or Bun 1.0+ recommended):

npm install albedo-node
pnpm add albedo-node
bun add albedo-node

The published package includes native binaries under native/. When running on an unsupported platform, or if you prefer to build locally, install Zig 0.15.x and run bun run build to compile the binding.

Quick start

import albedo, { BSON, Bucket, where } from "albedo-node";

// Open or create a bucket on disk
const bucket = Bucket.open("./example.bucket");

// Insert JavaScript objects or pre-serialized BSON buffers
bucket.insert({ name: "Ada", skills: ["math", "computing"] });

const serialized = BSON.serialize({ name: "Alan", skills: ["logic", "cryptanalysis"] });
bucket.insert(serialized);

// List documents using a generator with the fluent `where` helper
for (const doc of bucket.list(where("name", { $eq: "Ada" }))) {
  console.log(doc);
}

bucket.close();

Fluent query helpers

Use the fluent Query builder when you need more than a single-field filter:

import { Bucket, Query } from "albedo-node";

const bucket = Bucket.open("./example.bucket");

const adults = new Query()
  .where("age", { $gte: 21 })
  .sortBy("_id", "asc")
  .sector(0, 50);

for (const doc of bucket.list(adults)) {
  console.log(doc);
}

bucket.close();

You can chain multiple where calls or combine them with sortBy and sector. For quick one-off predicates, the standalone where(field, filter) helper returns a Query instance that slots into any method accepting a query.

Logical groups are available as both instance methods and static constructors:

import { Query, where } from "albedo-node";

const visibleUsers = Query.or(
  where("role", "admin"),
  Query.and(
    where("role", "user"),
    where("verified", true),
  ),
).nor(
  where("deleted", true),
);

for (const doc of bucket.list(visibleUsers)) {
  console.log(doc);
}

This produces a query equivalent to:

{
  query: {
    $or: [
      { role: "admin" },
      {
        $and: [
          { role: "user" },
          { verified: true },
        ],
      },
    ],
    $nor: [
      { deleted: true },
    ],
  },
}

Transforming documents in place

const iter = bucket.transformIterator({ query: { name: "Ada" } });
let step = iter.next();
while (!step.done) {
  const doc = step.value as { name: string; skills: string[] };
  step = iter.next({ ...doc, skills: [...doc.skills, "analytics"] });
}

Declarative updates with transfigurate

transfigurate applies a declarative update program to every matching document entirely inside the native layer — documents never cross into JavaScript, which makes it substantially faster than transform for bulk edits. It runs in a single implicit transaction and returns the number of documents updated.

const updated = bucket.transfigurate(where("name", { $eq: "stark" }), {
  $set: {
    age: { $plus: ["$.age", 1] }, // read the current value and increment
    seenAt: "$$now", // current timestamp
  },
  $unset: "marriage", // remove a path
});

Pass an array to run a pipeline of stages in order:

bucket.transfigurate(undefined, [
  { $set: { fullName: { $concat: ["$.first", " ", "$.last"] } } },
  { $unset: ["first", "last"] },
]);

The same method exists on transactions, so updates roll back with the surrounding transaction:

bucket.tx((tx) => {
  tx.transfigurate(where("active", { $eq: true }), { $set: { tier: "pro" } });
});

Update program syntax

| Form | Meaning | | --- | --- | | { $set: { path: expr } } | Assign evaluated expressions to paths | | { $unset: "path" \| ["a", "b"] } | Remove paths from the document | | { path: expr } | Shorthand for $set | | "$.path" | Reference the current value at path | | "$$now" | Current time at apply time | | { $plus: [a, b, ...] } | Numeric addition | | { $minus: [a, b, ...] } | Numeric subtraction | | { $concat: [a, b, ...] } | String concatenation | | { $isoDateTime: expr } | Parse/format as ISO date-time |

Subscribing to change events

Bucket.subscribe() exposes the new oplog-backed subscription API as an async generator. It yields individual change events rather than re-scanned documents.

const bucket = Bucket.open("./example.bucket", {
  wal: true,
  write_durability: "all",
});

for await (const event of bucket.subscribe<{ name: string }>(
  where("name", { $eq: "Ada" }),
  { pollingTimeout: 50, batchSize: 64 },
)) {
  console.log(event.op, event.seqno, event.doc);

  if (event.op === "delete") {
    console.log("deleted", event.doc_id.toString());
  }
}

Each yielded event has this shape:

type SubscriptionEvent<T = unknown> = {
  seqno: bigint;
  op: "insert" | "update" | "delete";
  doc_id: ObjectId;
  ts: bigint;
  doc?: T;
};

Notes:

  • Subscriptions require WAL mode to be active.
  • doc is present for inline insert and update payloads.
  • Delete events still include doc_id, even when doc is absent.
  • The generator closes the native subscription automatically when iteration stops.

Replication

const primary = Bucket.open("./primary.bucket", {
  wal: true,
  write_durability: "all",
});
const replica = Bucket.open("./replica.bucket", {
  wal: true,
  write_durability: "all",
});

const cursor = primary.replicationCursor();

primary.insert({ name: "Replica", version: 1 });

const batch = primary.readReplicationBatch(cursor);
if (batch) {
  const nextCursor = replica.applyReplicationBatch(batch);
  console.log(nextCursor.next_frame_index);
}

primary.close();
replica.close();

Notes:

  • Replication requires WAL mode to be enabled on both buckets.
  • replicationCursor() snapshots the current position in the WAL stream.
  • readReplicationBatch(cursor, maxBytes?) returns null when there is nothing new to send.
  • applyReplicationBatch(batch) applies the batch and returns the replica's new cursor.

Query objects and BSON payloads

Most bucket operations accept either:

  • Plain JavaScript objects that will be converted to BSON automatically, or
  • Raw Uint8Array buffers containing BSON documents that you have serialized yourself, or
  • Instances of the fluent Query builder (including values produced by the where() helper).

Regardless of which form you use, the structure mirrors the underlying Albedo query language. Common patterns include:

  • { query: { field: value } } — equality filters
  • { query: { age: { "$gt": 40 } } } — comparison operators
  • { query: { $or: [{ role: "admin" }, { public: true }], deleted: false } } — logical groups mixed with leaf filters
  • { query: { _id: someId }, sector: { limit: 10, offset: 0 } } — pagination controls

Supported field operators in this binding include:

  • $eq, $ne, $lt, $lte, $gt, $gte
  • $in, $between
  • $startsWith, $endsWith
  • $exists, $notExists

Logical operators supported inside query are:

  • $or
  • $and
  • $nor

For the exhaustive list of operators and document shapes, consult the Albedo Query reference. Whatever BSON document the core engine accepts can be provided here either as a plain object or as prebuilt BSON bytes.

ObjectId support

The module exposes albedo.ObjectId, compatible with the BSON 12-byte identifier:

const id = new albedo.ObjectId();
const hex = id.toString();
const parsed = albedo.ObjectId.fromString(hex);

Serialized documents that contain _id fields with a BSON ObjectId will be revived as ObjectId instances when deserialized.

API reference

  • default export — raw Albedo native module

    • ObjectId, serialize, deserialize, open, open_with_options, close, insert, checkpoint, delete, list, transform, transfigurate, replicationCursor, readReplicationBatch, applyReplicationBatch, etc.
  • BSON.serialize(value) / BSON.deserialize(bytes) — helper wrappers

  • Bucket

    • static open(path: string, options?: OpenBucketOptions): Bucket
    • insert(doc: object | Uint8Array)
    • checkpoint()
    • delete(query?: object | Query)
    • list<T>(query?: object | Query): Generator<T>
    • subscribe<T>(query?: object | Query, options?: { pollingTimeout?: number; batchSize?: number }): AsyncGenerator<SubscriptionEvent<T>>
    • transformIterator<T>(query?: object | Query): Generator<T, undefined, object | null>
    • transform<T>(query, fn) / update<T>(query, fn)
    • transfigurate(query: object | Query | undefined, program: UpdateStage | UpdateStage[] | Uint8Array): number
    • all<T>(query?) / one<T>(query?)
    • beginTransaction() / tx(fn)
    • ensureIndex(name: string, options: { unique: boolean; sparse: boolean; reverse: boolean })
    • dropIndex(name: string)
    • indexes: Record<string, { name: string; unique: boolean; sparse: boolean; reverse: boolean }>
    • replicationCursor(): { generation: bigint; next_frame_index: bigint }
    • readReplicationBatch(cursor, maxBytes?): Uint8Array | null
    • applyReplicationBatch(batch: Uint8Array): { generation: bigint; next_frame_index: bigint }
    • accepts raw BSON Uint8Array payloads anywhere a query or document object is expected
  • OpenBucketOptions

    • buildIdIndex?: boolean
    • mode?: string
    • auto_vaccuum?: boolean
    • page_cache_capacity?: number
    • wal?: boolean
    • write_durability?: "all" | "manual" | { periodic: number }
    • read_durability?: "shared" | "process"
    • wal_auto_checkpoint?: (defaults to 1000 pages)
  • SubscriptionEvent<T>

    • seqno: bigint
    • op: "insert" | "update" | "delete"
    • doc_id: ObjectId
    • ts: bigint
    • doc?: T
  • UpdateStage — one stage of an update program

    • $set?: Record<string, UpdateExpression>
    • $unset?: string | string[]
    • [path: string]: UpdateExpression — shorthand assignment
  • UpdateExpression — a literal, "$.path" field reference, "$$now", or one of { $plus }, { $minus }, { $concat }, { $isoDateTime }

  • Query

    • where(field, filter) chains field predicates (e.g. { $eq: value }, { $gt: 10 })
    • or(...clauses) adds an $or group
    • and(...clauses) adds an $and group
    • nor(...clauses) adds a $nor group
    • sortBy(field, direction?) sets sort order
    • sector(offset?, limit?) applies pagination window
    • static or(...clauses): Query creates a query with an $or group
    • static and(...clauses): Query creates a query with an $and group
    • static nor(...clauses): Query creates a query with a $nor group
  • where(field, filter): Query — convenience helper that creates a single-field Query

Building from source

bun install
bun run build # zig build + tsc emit

The build step compiles the Zig binding for the current host and writes the .node artifact into native/, then emits the TypeScript declaration files to dist/.

Running tests

bun test

The test suite exercises insertion, querying, indexing, transforms, replication, and BSON round-trips using Bun’s test runner.

License

MIT. See the accompanying LICENSE file (or the upstream Albedo repository) for details.