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

@tuckertucker/koji-db

v0.5.1

Published

Kōji - Hybrid SQL + Recursive + Vector Database

Readme

Koji Node.js Bindings

Node.js bindings for Koji, the hybrid SQL + Recursive + Vector database.

Installation

npm install @tuckertucker/koji-db

Quick Start

import { openMemory } from '@tuckertucker/koji-db';

// Create in-memory database
const db = await openMemory();

// Create table
await db.syncSchema({
  users: { columns: { id: { type: 'integer' }, name: { type: 'text' } } }
});

// Insert data
await db.insert('users', [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);

// Query
const result = await db.query('SELECT * FROM users');
console.log(result.rows);

await db.close();

Features

  • Relational queries via SQL
  • Recursive CTEs for graph traversal
  • Vector similarity search with automatic indexing
  • JSON serialization for seamless JavaScript integration

Configuration

Auto-Sync (Durability by Default)

Koji automatically syncs data to disk after each mutation operation by default. This ensures your data is durable and survives process crashes.

import { open } from '@tuckertucker/koji-db';

// Default behavior: autoSync enabled
const db = await open('./data');

await db.syncSchema({
  users: { columns: { id: { type: 'integer' }, name: { type: 'text' } } }
});
await db.insert('users', [{ id: 1, name: 'Alice' }]);
// ^ Data is immediately durable - survives crash

await db.update('users', { name: 'Bob' }, 'id = 1');
// ^ Update is immediately durable

await db.delete('users', 'id = 1');
// ^ Delete is immediately durable

Disabling Auto-Sync for Batch Operations

For high-throughput batch operations, disable auto-sync and manually sync at the end:

import { open } from '@tuckertucker/koji-db';

// Disable auto-sync for batch insert
const db = await open('./data', { autoSync: false });

await db.syncSchema({
  events: { columns: { id: { type: 'integer' }, data: { type: 'text' } } }
});

// Insert 10,000 rows without per-row sync overhead
for (const batch of dataBatches) {
  await db.insert('events', batch);
}

// Single sync at the end
await db.sync();

Performance Considerations

| Mode | Use Case | Tradeoff | |------|----------|----------| | autoSync: true (default) | OLTP, multi-process apps | Durable but slower for bulk ops | | autoSync: false | Batch ingestion, ETL | Faster but requires manual sync |

When to disable auto-sync:

  • Bulk data imports (1000+ rows)
  • ETL pipelines
  • Single-writer scenarios with explicit checkpoints

When to keep auto-sync enabled (default):

  • Multi-process applications
  • User-facing operations
  • Any operation where data loss is unacceptable

In-Memory Databases

Auto-sync also applies to in-memory databases, though it's less critical since there's no disk persistence:

import { openMemory } from '@tuckertucker/koji-db';

// Auto-sync enabled (default)
const db = await openMemory();

// Auto-sync disabled
const db2 = await openMemory({ autoSync: false });

API Reference

open(path, options?)

Opens a database at the specified path.

Parameters:

  • path: string - Path to database directory
  • options?: OpenOptions - Configuration options

Returns: Promise<Database>

Options: | Option | Type | Default | Description | |--------|------|---------|-------------| | autoSync | boolean | true | Sync to disk after mutations |

Example:

// Default (durable)
const db = await open('./data');

// Batch mode (manual sync)
const db2 = await open('./data', { autoSync: false });

openMemory(options?)

Opens an in-memory database.

Parameters:

  • options?: OpenOptions - Configuration options

Returns: Promise<Database>

Example:

const db = await openMemory();
const db2 = await openMemory({ autoSync: false });

OpenOptions

Configuration options for database opening.

interface OpenOptions {
  /**
   * Automatically sync after mutations.
   * @default true
   */
  autoSync?: boolean;
}

Database Methods

| Method | Description | |--------|-------------| | syncSchema(schema, options?) | Sync database schema to match target definition | | query(sql, params?) | Execute query and return results | | insert(table, data) | Insert JSON objects into table | | scan(table) | Read all rows from table | | delete(table, filter?) | Delete rows matching filter | | update(table, assignments, filter?) | Update rows matching filter | | sync() | Force sync to disk | | close() | Close database connection | | listTables() | List all table names | | getSchema(table) | Get table schema | | version(table) | Get table version number | | compact(table, options?) | Compact table fragments | | cleanupVersions(table, options?) | Remove old versions | | vacuum(table, options?) | Full table maintenance | | vectorSearch(table, column, query, k, filter?) | k-NN similarity search | | hybridSearch(table, column, query, k, filter) | Vector + SQL hybrid search | | batchVectorSearch(table, column, queries, k, filter?) | Batch k-NN searches | | createVectorIndex(table, column, options) | Create HNSW/IVF-PQ vector index | | listIndexes(table) | List vector indexes on table | | indexExists(table, indexName) | Check if index exists | | dropIndex(table, indexName) | Drop vector index (no-op in Lance 0.22) |

Migration Guide

Migrating from pre-autoSync Versions

If you're upgrading from a version without autoSync, your existing code will continue to work without changes.

Before (manual sync required):

await db.insert('users', [{ id: 1 }]);
await db.sync();  // Required for durability

After (auto-sync by default):

await db.insert('users', [{ id: 1 }]);
// sync() is called automatically - data is durable

Your existing sync() calls are now harmless no-ops. You can:

  1. Leave them in place (safest, no code changes)
  2. Remove them gradually (cleaner code)
  3. Add { autoSync: false } to batch operations that have sync() at the end

If you have batch operations with explicit sync:

// Before: works but inefficient with autoSync=true
for (const row of data) {
  await db.insert('table', [row]);
}
await db.sync();

// After: explicitly disable autoSync for batches
const db = await open('./data', { autoSync: false });
for (const row of data) {
  await db.insert('table', [row]);
}
await db.sync();  // Single sync at end

Documentation

See the full documentation.