xitdb Immutable Database

0.13.0 · active · verified Wed Apr 22

xitdb (current version 0.13.0) is an embedded, immutable database library for TypeScript and JavaScript, designed for storing and managing structured data in a versioned manner. It excels by efficiently creating a new "copy" of the database with each transaction, allowing past states to be read from or reverted to. Unlike traditional databases, xitdb operates without a query engine, instead providing direct APIs to manipulate core data structures like ArrayList and HashMap, which can be nested arbitrarily. It supports both single-file and in-memory storage, incrementally reading and writing to handle datasets larger than available memory. A key differentiator is its entirely synchronous API, eliminating the need for `async/await`, and its pure TypeScript implementation with no external dependencies beyond the JavaScript standard library. Reads never block writes, and multiple threads/processes can access the database concurrently without locks. This makes it a powerful alternative to SQL databases like SQLite for applications requiring simplicity, immutability, and tight integration with native TypeScript data structures, especially in scenarios akin to version control systems. The project is under active development, with an irregular, feature-driven release cadence, reflecting its 0.x version status.

Common errors

Warnings

Install

Imports

Quickstart

This example initializes a file-backed xitdb, performs a transaction to write structured data, and then reads the latest state.

import { CoreBufferedFile, Database, Hasher, WriteArrayList, WriteHashMap, Bytes, Uint } from 'xitdb';
import * as path from 'path';
import * as fs from 'fs';

const dbFilePath = path.join(process.cwd(), 'main.db');

// Ensure the database file doesn't exist from a previous run
if (fs.existsSync(dbFilePath)) {
  fs.unlinkSync(dbFilePath);
}

// init the db
using core = new CoreBufferedFile(dbFilePath);
const hasher = new Hasher('SHA-1');
const db = new Database(core, hasher);

// to get the benefits of immutability, the top-level data structure
// must be an ArrayList, so each transaction is stored as an item in it
const history = new WriteArrayList(db.rootCursor());

// Execute a transaction to add data
history.appendContext(history.getSlot(-1), (cursor) => {
  const moment = new WriteHashMap(cursor);

  moment.put('foo', new Bytes('foo'));
  moment.put('bar', new Bytes('bar'));

  const fruitsCursor = moment.putCursor('fruits');
  const fruits = new WriteArrayList(fruitsCursor);
  fruits.append(new Bytes('apple'));
  fruits.append(new Bytes('pear'));
  fruits.append(new Bytes('grape'));

  const peopleCursor = moment.putCursor('people');
  const people = new WriteArrayList(peopleCursor);

  const aliceCursor = people.appendCursor();
  const alice = new WriteHashMap(aliceCursor);
  alice.put('name', new Bytes('Alice'));
  alice.put('age', new Uint(25));

  const bobCursor = people.appendCursor();
  const bob = new WriteHashMap(bobCursor);
  bob.put('name', new Bytes('Bob'));
  bob.put('age', new Uint(42));
});

// Read the most recent state of the database
const latestMomentCursor = history.getSlot(-1);
if (latestMomentCursor) {
  const latestMoment = new WriteHashMap(latestMomentCursor);
  console.log('Database state after transaction:');
  // You would typically iterate or access specific keys here
  // For demonstration, let's just confirm an item exists
  console.log('Has "foo" key:', latestMoment.get('foo')?.toString());
  console.log('Has "fruits" key:', latestMoment.get('fruits') !== undefined);
} else {
  console.log('No data found in the database history.');
}

// Clean up the database file (optional, for repeated runs)
// fs.unlinkSync(dbFilePath);

view raw JSON →