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

batenbase

v2.9.7

Published

Official BatenBase SDK — causal storage for builders

Downloads

580

Readme

batenbase

Official JavaScript / TypeScript SDK for BatenBase

BatenBase is a causal key-value database — every block automatically records when it was created, when it was last written, who can see it, and whether it has been redacted. You get a full audit log for free, with no schema to define and no migrations to run.

If you have ever built a system where you needed to answer "what happened to this record and when?" — BatenBase was designed for exactly that.

npm install batenbase

How it works

Every piece of data you store is called a block. A block has a key, a payload (data), and four causal fields that BatenBase manages for you:

| Field | Type | Meaning | |-------|------|---------| | f | number | First write — creation timestamp in Unix ms. Immutable. | | l | number | Last write — updated automatically on every set(). | | v | number | Visibility scope — controls who can read this block. | | h | number | Hidden flag — 0 = visible, 1 = redacted. |

You write { name: "Alice" }. You get back { key, f, l, v, h, data: { name: "Alice" } }. No configuration required.

Quick start

import { BatenBase } from "batenbase"

const db = new BatenBase({ token: "YOUR_TOKEN" })

// Store anything — users, invoices, sessions, contracts
await db.set("user:[email protected]", {
  name: "Alice",
  role: "admin",
  plan: "pro"
})

// Read it back — f and l are set automatically
const block = await db.get("user:[email protected]")
console.log(block.data.name) // "Alice"
console.log(block.f)         // e.g. 1748700000000  ← created at
console.log(block.l)         // e.g. 1748700000000  ← last modified

// Scan by prefix — all users, all invoices, all sessions
const users = await db.list({ prefix: "user:", limit: 200 })

// Append an immutable audit event
await db.log("user.login", { email: "[email protected]", ip: "1.2.3.4" })

// Query the audit trail
const recent = await db.events({ since: "24h", limit: 100 })

Get your token from dashboard.batenbase.com.

Constructor

const db = new BatenBase({
  token:    "YOUR_TOKEN",                      // required
  baseUrl:  "https://api.batenbase.com"        // optional override
})

Methods

set(key, data){ ok, key }

Upsert a block. On the first write, f is set and never changes. On every subsequent write, l is updated.

await db.set("invoice:2026-001", {
  client: "Acme Corp",
  amount: 9500,
  status: "pending"
})

get(key)Block

Read one block. Returns the full causal envelope.

const inv = await db.get("invoice:2026-001")
// inv.f  → creation timestamp
// inv.l  → last modified
// inv.data.status → "pending"

delete(key){ ok }

Permanently delete a block.

await db.delete("session:expired-token")

list({ prefix?, limit? })Block[]

Prefix scan. Max 1000 results per call. Use type: prefixes to scope your scans efficiently.

const invoices = await db.list({ prefix: "invoice:", limit: 500 })
const sessions = await db.list({ prefix: "session:" })

log(kind, details?){ ok }

Append an immutable event to the audit log. kind is a dot-separated string that describes the event type. Events cannot be deleted or modified.

await db.log("invoice.paid",   { id: "2026-001", amount: 9500 })
await db.log("user.suspended", { email: "[email protected]", reason: "abuse" })
await db.log("contract.signed",{ parties: ["alice", "bob"], hash: "abc123" })

events({ since?, limit? })Event[]

Query the audit log. since accepts "1h", "24h", "7d", "30d", or a Unix timestamp in ms.

const today = await db.events({ since: "24h" })
const week  = await db.events({ since: "7d", limit: 1000 })
const from  = await db.events({ since: 1748700000000 })

me()Tenant

Returns your tenant info: plan, blocks used, and quota.

const info = await db.me()
// { plan: "pro", blocks_used: 4821, quota: 1000000 }

health(){ status }

Liveness check. No token required. Useful for monitoring.

const { status } = await db.health()
// { status: "ok" }

Key convention

Keys are arbitrary UTF-8 strings. The recommended pattern is type:identifier — this makes prefix scans fast and your data self-organizing.

user:[email protected]
invoice:2026-001
session:a1b2c3d4e5f6
contract:client-42:v3
log:2026-01-15:server-01

You can nest prefixes (contract:client-42:v3) for hierarchical data without any special configuration.

Error handling

All API errors throw a BatenBaseError with a status code.

import { BatenBase, BatenBaseError } from "batenbase"

try {
  const block = await db.get("user:unknown")
} catch (e) {
  if (e instanceof BatenBaseError) {
    console.log(e.status)  // 404
    console.log(e.message) // "block not found"
  }
}

| Status | Code | Meaning | |--------|------|---------| | 401 | unauthorized | Invalid or missing token | | 404 | not_found | Block does not exist | | 429 | rate_limited | Too many requests | | 507 | quota_exceeded | Block limit reached — upgrade your plan | | 500 | internal_error | Server error |

TypeScript

The SDK is written in TypeScript and ships with full type definitions. No @types package needed.

import { BatenBase, Block, BatenBaseError } from "batenbase"

const db = new BatenBase({ token: process.env.BATENBASE_TOKEN! })

async function getUser(email: string): Promise<Block> {
  return db.get(`user:${email}`)
}

Links