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

@elitjs/xdb

v0.3.4

Published

Native Node.js addon (napi-rs) สำหรับเรียกใช้ x-db จาก TypeScript โดยตรง

Downloads

60

Readme

x-db

Read-only key-value store แบบ SSTable (Rust) เรียกใช้จาก TypeScript ได้โดยตรงผ่าน native binding (napi-rs) — ไม่ต้องมี server

คุณสมบัติ

  • เร็ว: bloom filter + sparse index (binary search) + zero-copy mmap
  • ปลอดภัย: CRC32 ทุก block + footer, ตรวจความยาว key/value, fsync ตอนเขียน — ไฟล์เสียหายจะได้ error ไม่ใช่ข้อมูลเพี้ยน/crash
  • Iterator: ไล่ทั้งตาราง / range scan / prefix scan
  • Merge/compaction: รวมหลายตารางเป็นไฟล์เดียวแบบ streaming (ตารางใหญ่ก็ไม่กิน RAM)
  • LZ4 compression ต่อ block (format v6): ตาราง/compaction บีบอัดได้ — ข้อมูล text เล็กลง 3-5 เท่า
  • XDBStore (realtime updates): put/delete แบบ LSM-lite — เหมาะกับแอปที่ update ตลอดเวลา พร้อม file locking (เปิดซ้อนหลาย process ไม่ได้ — กันข้อมูลเสียหาย) และ close() ปลดล็อก
  • seek / range / prefix: iterator กระโดดไปตำแหน่ง key ได้เลย (ไม่ต้องไล่ตั้งแต่ต้น)
  • Native addon: Rust compile เป็น .node เรียกจาก Node.js/TypeScript ตรง ๆ ใน process เดียวกัน

Benchmark (ตัวอย่าง, cargo run --release --example bench)

100,000 entries × 100B (ไฟล์ 11.8 MB), Windows x64:

| การทำงาน | เวลา (ไม่บีบอัด) | |---|---| | build ทั้งตาราง | ~20 ms | | get เจอ key (สุ่ม, warm) | ~1.0 µs/op | | get เจอ key เดิมซ้ำ ๆ (hot path ล้วน) | ~190 ns/op | | get ไม่เจอ (bloom ตัด) | ~155 ns/op | | iterate ทั้งตาราง | ~7 ms (คืนค่า owned — รองรับ block บีบอัด) | | range scan 1,000 keys | ~3.9 ms |

ตัวเลขบน block ที่บีบอัด: get เจอ ~1µs เท่าเดิมหลัง block cache ร้อน (decompress ครั้งเดียวต่อ block, cache 64MB) — จาก test: ตาราง text 100k entries เล็กลงมากกว่า 2 เท่า

เทียบกับ database อื่น (cargo run --release -p xdb-bench)

เงื่อนไขเดียวกัน: 100,000 entries × 100B, page cache ร้อน, Windows x64 — เทียบกับ SQLite (rusqlite), redb และ HashMap บน RAM เป็น baseline:

| database | build (ms) | open (ms) | get เจอ (ns) | get ไม่เจอ (ns) | iterate (ms) | ไฟล์ (MB) | |---|---:|---:|---:|---:|---:|---:| | x-db | 16 | 1.1 | 543 | 18 | 6.3 | 11.8 | | HashMap (RAM) | 4 | — | 59 | 23 | 0.0 | — | | SQLite | 92 | 0.25 | 28,936 | 23,803 | 13.1 | 13.5 | | redb | 229 | 8.5 | 770 | 231 | 8.1 | 32.6 |

(open วัดแบบ warm — การเปิดไฟล์ที่เพิ่งเขียนเสร็จครั้งแรกมีค่า first-touch ของ OS/antivirus ~10-15ms เท่ากันทุก database บน Windows)

สรุป:

  • เทียบ SQLite: x-db เร็วกว่า ~50 เท่าบน point lookup และ ~15 เท่าบนการไล่ทั้งตาราง ไฟล์เล็กกว่าด้วย
  • เทียบ redb: เร็วกว่าทุกด้าน — เปิดไฟล์เร็ว ~54 เท่า, get เจอ ~1.6 เท่า, miss ~13 เท่า (bloom filter), iterate ~10 เท่า, ไฟล์เล็กกว่า 2.8 เท่า
  • เทียบ HashMap: ช้ากว่า RAM ล้วน ~6 เท่าบน hot path (~190ns vs ~124ns) — ที่เหลือคือ cache miss จากการสุ่มเข้าไฟล์ 11.8MB ซึ่งเป็นขีดจำกัดของ hardware แลกกับข้อมูลใหญ่เท่าไรก็ได้โดยไม่กิน RAM

หมายเหตุความเป็นธรรม: x-db เป็น immutable read-only store (ไม่มีค่าใช้จ่าย transaction/locking) ส่วน SQLite/redb รองรับการเขียน/แก้ไขข้อมูลได้ — เลือกใช้ตามลักษณะงาน

เทียบ OLTP ops: เขียน / อัพเดต / ลบ / อ่าน (XDBStore)

10,000 ops ต่อ phase, Windows x64 — ทุก phase มี correctness check อ่านคืน:

| database | put (ns) | batch 1000 (ns/key) | update (ns) | delete (ns) | get เจอ (ns) | get ไม่เจอ (ns) | |---|---:|---:|---:|---:|---:|---:| | x-db (sync) | 979,227* | 3,479 | 1,073,055* | 1,171,353* | 585 | 312 | | x-db (nosync) | 5,016 | 2,718 | 4,427 | 6,272 | 412 | 318 | | SQLite (WAL+NORMAL) | 19,986 | 1,016 | 16,580 | 17,205 | 2,592 | 2,149 | | redb (txn/op) | 1,159,788* | 3,708 | 1,159,762* | 1,202,097* | 402 | 261 | | HashMap (RAM) | 224 | — | 278 | 207 | 192 | — |

* = fsync ทุก operation (durable จริง) — ต้นทุนถูกครอบด้วย disk sync (~1ms บนเครื่องนี้)

อ่านผลอย่างเป็นธรรม:

  • อ่าน: x-db เร็วสุด — เจอ ~414-463ns (6 เท่าของ SQLite), ไม่เจอ ~335ns (6.7 เท่า)
  • เขียน durable จริง (sync ทุก op): x-db ≈ redb (~1ms ถูกจำกัดโดย fsync ของดิสก์ ไม่ใช่โค้ด)
  • เขียน nosync: x-db 4.5-5.9µs/op — เร็วกว่า SQLite(WAL) 3-4 เท่า
  • batch 1000 keys: ตัวเลขในตารางรวมค่า flush layer (fsync) + background compaction ที่รวอยู่ด้วย ถ้าวัดเฉพาะ batch path ล้วน (ไม่มี flush/compaction): x-db = 0.4µs/key — เร็วกว่า SQLite (1.0) ~2.5 เท่า
  • สรุป: งาน อ่านเยอะ x-db ชนะชัดเจน / งานเขียนถี่มาก ๆ สูสีกัน (ต่างกันที่ระดับ durability)

โครงสร้าง

src/lib.rs             ไลบรารีหลัก: XDBWriter / XDBReader / BloomFilter / XDBIter
examples/bench.rs      benchmark
crates/xdb-node/       napi-rs binding: Rust → Node.js addon
typescript/            npm package "@elitjs/xdb": TS wrapper + tests + example

Cookbook — ตัวอย่างครบทุกเคส (รันได้จริง)

cd typescript && npm run cookbook

17 เคสใน typescript/src/cookbook.ts: ตารางเดี่ยว, binary, JSON, range/prefix/seek, realtime CRUD, batch import 10k rows, เลือก durability 3 โหมด, เก็บไฟล์+chunking, cache แบบ TTL, counter, session store + ล้างหมดอายุ, key design (เลขเรียงถูก), merge ฐาน+delta, compact มือ, error handling, multi-process patterns, performance tips

ใช้จาก TypeScript (native binding)

Build ครั้งแรก:

cd typescript
npm install
npm run native:build   # cargo build ผ่าน @napi-rs/cli → index.win32-x64-msvc.node
npm run build          # tsc → dist/

ใช้งาน:

import { writeTable, XdbReader } from "@elitjs/xdb";

writeTable("./demo.xdb", [
  ["alice", "engineer"],
  ["bob", "designer"],
  [new Uint8Array([0, 255, 128]), new Uint8Array([1, 2, 3])], // binary ก็ได้
]);

const reader = new XdbReader("./demo.xdb");
reader.getUtf8("alice");                   // "engineer"
reader.get(new Uint8Array([0, 255, 128])); // Uint8Array(3) [1, 2, 3]
reader.count;                              // 3

// iterate (เรียงตาม key)
for (const { key, value } of reader) { /* ... */ }

// merge/compaction: รวมหลายตาราง — ไฟล์หลังสุดชนะเมื่อ key ซ้ำ
import { mergeTables } from "@elitjs/xdb";
const count = mergeTables(["old.xdb", "new.xdb"], "merged.xdb");
  • writeTable รับ Array / Map / Object — ส่งไม่เรียงก็ได้ Rust จัดเรียงและกรอง key ซ้ำให้ (ตัวหลังสุดชนะ)
  • XdbReader เปิดไฟล์แบบ mmap ครั้งเดียว แล้ว lookup ได้ตลอดอายุ process
  • ไฟล์เสียหาย → throw error (ตรวจ CRC ครั้งแรกที่แตะแต่ละ block)

XDB — API เดียวจบ (แนะนำเริ่มที่นี่ — มีทั้ง TypeScript และ Rust)

รวมทุกความสามารถ (อ่าน/เขียน/อัพเดต/ลบ/ไล่/snapshot) ไว้ในคลาสเดียว บนไฟล์ .xdb ไฟล์เดียว:

import { XDB } from "@elitjs/xdb";

const db = new XDB("./app.xdb");                    // หรือ XDB.open(...)
// ตัวเลือก: { durability: "safe" | "balanced" | "fast", flushEntries, compactThreshold }

db.set("user:1", { name: "สมชาย", age: 30 });        // object → JSON ให้อัตโนมัติ
db.set("note", "hello");                             // string เก็บตรง / Uint8Array ก็ได้
db.setMany({ a: "1", b: "2" });                       // batch (ยิ่งเยอะยิ่งเร็ว)

db.get("user:1");        // { name: "สมชาย", age: 30 } — ถอด JSON กลับมาให้แล้ว
db.getBytes("note");      // อยากได้ bytes ดิบ
db.has("note");           // true
db.set("user:1", { name: "สมชาย", age: 31 });         // update บนไฟล์เดียวกัน
db.del("note", "a");      // ลบกี่ตัวก็ได้

for (const e of db.prefix("user:")) { /* ไล่/ช่วง/seek ครบ */ }
const snap = db.snapshot();  // XdbReader เร็วสุด (~1µs/get) แชร์ไฟล์ให้คนอื่นอ่าน

db.save();   // บีบเป็นไฟล์เดียวแบบ atomic (reader เก่าไม่พัง)
db.close();  // save + เหลือไฟล์เดียวพกไปไหนก็ได้ — เปิดใหม่ข้อมูลครบ

Rust ก็มี API เดียวกัน:

use x_db::XDB;

let db = XDB::open("app.xdb")?;                     // หรือ open_with(path, XDBOptions { durability, .. })
db.set("user:1", "สมชาย")?;                          // &str หรือ &[u8] ก็ได้
db.set_many(&[("a", "1"), ("b", "2")])?;
db.get_utf8("user:1")?;                              // Some("สมชาย")
db.del(&["a", "b"])?;
for entry in db.prefix(b"user:") { let (_k, _v) = entry?; }
db.save()?;                                           // atomic — reader เก่าไม่พัง
db.close()?;                                          // เหลือไฟล์เดียวพกไปไหนก็ได้

ไฟล์ที่ได้ใช้ร่วมกับ writeTable/XDBReader ได้ทั้งสองทาง

XdbSingleFile — ไฟล์ .xdb เดียวจบ (เขียน+อ่าน ไม่พัง)

อยากได้ไฟล์เดียวพกไปไหนก็ได้ แต่ยังแก้ไขข้อมูลได้ตลอด — ใช้ XdbSingleFile:

import { XdbSingleFile, XdbReader } from "@elitjs/xdb";

const db = new XdbSingleFile("./app.xdb");   // ไฟล์เดียว (มีห้องเครื่อง data.xdb.store ข้าง ๆ)
db.put(["alice", "1"], ["bob", "2"]);        // เขียน/แก้/ลบ แบบ realtime เหมือน XdbStore
db.getUtf8("alice");                          // อ่านเห็นของใหม่สุดเสมอ

db.save();                                    // ★ บีบทุกอย่างแทนที่ app.xdb แบบ atomic
new XdbReader("./app.xdb").getUtf8("alice"); // คนอื่นเปิดไฟล์เดียวนั้นได้เลย
// reader ตัวเก่าที่เปิดค้างระหว่าง save ก็ไม่พัง — ยังอ่าน snapshot เดิมของมันต่อได้

db.exportAndClose();                          // ปิด + ลบห้องเครื่อง → เหลือ app.xdb ไฟล์เดียวจริง ๆ
// เอาไฟล์นี้ไปเครื่องอื่น/แนบอีเมลได้ — เปิดครั้งหน้า seed จากไฟล์อัตโนมัติ

XDBStore — สำหรับแอปที่ update แบบ realtime

writeTable เหมาะกับข้อมูลนิ่ง แต่ถ้าแอปต้อง put/delete ตลอดเวลา ให้ใช้ XdbStore: put เขียน WAL + memtable ก่อน (single key ~0.9ms รวม fsync, batch ~2.2µs/key), memtable เต็ม (default 4096) จะ flush เป็น layer อัตโนมัติ, get = memtable → layers ตัวใหม่ชนะ, delete = tombstone, และ compact อัตโนมัติแบบ background เมื่อครบ 8 layers (บีบอัด LZ4 — writer ไม่ต้องรอ)

import { XdbStore } from "@elitjs/xdb";

const store = new XdbStore("./mydata");          // directory (สร้างให้เอง)
// ตัวเลือก: { compactThreshold: 8, flushEntries: 4096, sync: true, syncIntervalMs: 0 }
// sync: true  = fsync ทุก put (~1ms) — ไฟดับก็ไม่เสียข้อมูล
// sync: false = เร็วมาก (~5µs) แต่ไฟดับอาจเสีย put ท้ายสุด → ใช้ syncIntervalMs คุมช่องเสีย
//   เช่น { sync: false, syncIntervalMs: 200 } = put เร็วเท่าเดิม เสียได้สูงสุดแค่ 200ms ตอนไฟดับ

store.put([["alice", "engineer"]]);               // insert
store.put([["alice", "senior engineer"]]);        // update — ทันที
store.getUtf8("alice");                           // "senior engineer"
store.delete("alice");                            // delete (tombstone)
store.getUtf8("alice");                           // null

for (let i = 0; i < 100; i++) store.put([[`c:${i}`, String(i)]]); // realtime ได้เรื่อย ๆ

จาก Rust: XDBStore::open(dir) / open_with(dir, threshold) — API เดียวกัน

ความทนทาน: ทุก put ลง WAL ก่อนตอบกลับ — ปิดแอป/พังกลางทางแล้วเปิดใหม่ ข้อมูลถูก replay ครบ (มี test ทดสอบทั้ง crash กลาง batch และ tombstone replay) ส่วน compaction ของตารางใหญ่มีค่าใช้จ่าย — ถ้า base ใหญ่มากและเขียนถี่ แนะนำ batch หลาย keys ต่อ put

ใช้จาก Rust

use x_db::{XDBWriter, XDBReader};

let entries = [(b"key".as_slice(), b"value".as_slice())];
XDBWriter::write_table("demo.xdb", &entries)?;

let reader = XDBReader::open("demo.xdb")?;
let value: Option<&[u8]> = reader.get(b"key")?; // Err = ไฟล์เสีย, None = ไม่มี key

for entry in reader.iter() {
    let (key, value) = entry?;
    // ...
}

File format (v6)

[Header 32B]   magic "XDB1" + version + block size
[Blocks]       ต่อ block (16KB): payload = entries ([klen u16][vlen u32][key][value])
               + restart array (u32 × R, R: u16) — อาจถูกบีบอัด LZ4 (ถ้าคุ้ม)
               แล้วตามด้วย trailer [raw_len u32][CRC32]
[Bloom]        ขนาดปรับตามจำนวน entries (~1% false positive)
[Sparse Index] first key + offset + length + num_restarts ของแต่ละ block
               (เก็บ metadata ครบใน index → open อ่านแค่ 3 บริเวณต่อเนื่อง ไม่ต้องแตะ blocks เลย ~0.2ms)
[Footer 40B]   offsets/lengths + entry count + CRC32 ของ footer เอง + magic "ENDX"

การ lookup: bloom filter → binary search บน sparse index (เลือก block) → binary search บน restart points (เลือกช่วง 16 entries ใน block) → linear scan สั้น ๆ — เร็วขึ้น ~2 เท่าจาก การไล่ block ทั้งก้อนตรง ๆ

CLI (cargo install --path crates/xdb-cli หรือ ./target/release/xdb.exe)

xdb check  data.xdb                 # ตรวจไฟล์: CRC ทุก block + key เรียงถูก + จำนวนตรง footer
xdb stats  data.xdb                 # entries / blocks / bloom / อัตราบีบอัด
xdb dump   data.xdb --prefix u:42:  # แสดง entries (--start, --limit, --keys-only)
xdb get    data.xdb "user:42"       # ค้นหา key เดียว
xdb merge  out.xdb a.xdb b.xdb --compress   # รวมตาราง (บีบอัดได้)

รัน tests

cargo test                 # ไลบรารี Rust (44 tests) + CLI (8 integration tests)
cd typescript && npm test  # native binding จาก TS (27 tests)
npm run example            # ตัวอย่างการใช้งาน
cargo run --release --example bench   # benchmark

CI / build ข้าม platform

.github/workflows/ci.yml รัน tests ทั้ง Rust และ Node addon บน Linux/Windows/macOS ตัว .node ที่ build ในเครื่องนี้ใช้ได้เฉพาะ win32-x64 — การ distribute ให้ครบทุก platform ต้อง push ขึ้น GitHub แล้วเปิดใช้ release job (commented ไว้ใน workflow แล้ว) หรือใช้ napi build --zig เพื่อ cross-compile ในเครื่องเดียว

เหลือที่ยังไม่ได้ทำ

  • Publish npm package — โครงสร้างพร้อมแล้ว เหลือตั้งค่า release pipeline จริง (napi-rs publish workflow)