Skip to content

Commit ffc4984

Browse files
committed
feat: migrate from ffi-napi to native Rust addon (napi-rs)
- Add native/ directory with Rust bindings using napi-rs - Update GitHub Actions to build native addon with Rust toolchain - Remove unstable ffi-napi dependencies from optionalDependencies - Simplify build process: cargo build + tsc - Fixes CI build failures with Node.js 20.x
1 parent 73c3253 commit ffc4984

163 files changed

Lines changed: 3098 additions & 6 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

native/Cargo.lock

Lines changed: 2592 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

native/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "toondb-nodejs-native"
3+
version = "0.3.6"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
napi = { version = "2", features = ["async"] }
11+
napi-derive = "2"
12+
13+
[dependencies.toondb-storage]
14+
path = "../../toondb/toondb-storage"
15+
16+
[build-dependencies]
17+
napi-build = "2"

native/build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
napi_build::setup();
3+
}

native/src/lib.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#![deny(clippy::all)]
2+
3+
use napi::bindgen_prelude::*;
4+
use napi_derive::napi;
5+
use toondb_storage as storage;
6+
7+
#[napi]
8+
pub struct Database {
9+
inner: storage::Database,
10+
}
11+
12+
#[napi]
13+
impl Database {
14+
#[napi(factory)]
15+
pub fn open(path: String) -> Result<Self> {
16+
let inner = storage::Database::open(&path)
17+
.map_err(|e| Error::from_reason(e.to_string()))?;
18+
Ok(Self { inner })
19+
}
20+
21+
#[napi]
22+
pub fn put(&self, key: Buffer, value: Buffer) -> Result<()> {
23+
self.inner.put(&key, &value)
24+
.map_err(|e| Error::from_reason(e.to_string()))
25+
}
26+
27+
#[napi]
28+
pub fn get(&self, key: Buffer) -> Result<Option<Buffer>> {
29+
let result = self.inner.get(&key)
30+
.map_err(|e| Error::from_reason(e.to_string()))?;
31+
Ok(result.map(|v| v.into()))
32+
}
33+
34+
#[napi]
35+
pub fn delete(&self, key: Buffer) -> Result<()> {
36+
self.inner.delete(&key)
37+
.map_err(|e| Error::from_reason(e.to_string()))
38+
}
39+
40+
#[napi]
41+
pub fn put_path(&self, path: String, value: Buffer) -> Result<()> {
42+
self.inner.put_path(&path, &value)
43+
.map_err(|e| Error::from_reason(e.to_string()))
44+
}
45+
46+
#[napi]
47+
pub fn get_path(&self, path: String) -> Result<Option<Buffer>> {
48+
let result = self.inner.get_path(&path)
49+
.map_err(|e| Error::from_reason(e.to_string()))?;
50+
Ok(result.map(|v| v.into()))
51+
}
52+
53+
#[napi]
54+
pub fn delete_path(&self, path: String) -> Result<()> {
55+
self.inner.delete_path(&path)
56+
.map_err(|e| Error::from_reason(e.to_string()))
57+
}
58+
59+
#[napi]
60+
pub fn begin_transaction(&self) -> Result<Transaction> {
61+
let txn = self.inner.begin_txn()
62+
.map_err(|e| Error::from_reason(e.to_string()))?;
63+
Ok(Transaction { inner: Some(txn) })
64+
}
65+
66+
#[napi]
67+
pub fn checkpoint(&self) -> Result<i64> {
68+
self.inner.checkpoint()
69+
.map_err(|e| Error::from_reason(e.to_string()))
70+
}
71+
72+
#[napi]
73+
pub fn stats(&self) -> Result<Stats> {
74+
let stats = self.inner.stats()
75+
.map_err(|e| Error::from_reason(e.to_string()))?;
76+
Ok(Stats {
77+
memtable_size_bytes: stats.memtable_size_bytes as i64,
78+
wal_size_bytes: stats.wal_size_bytes as i64,
79+
active_transactions: stats.active_transactions as i32,
80+
min_active_snapshot: stats.min_active_snapshot as i64,
81+
last_checkpoint_lsn: stats.last_checkpoint_lsn as i64,
82+
})
83+
}
84+
85+
#[napi]
86+
pub fn close(&mut self) {
87+
// Rust Drop handles cleanup
88+
}
89+
}
90+
91+
#[napi(object)]
92+
pub struct Stats {
93+
pub memtable_size_bytes: i64,
94+
pub wal_size_bytes: i64,
95+
pub active_transactions: i32,
96+
pub min_active_snapshot: i64,
97+
pub last_checkpoint_lsn: i64,
98+
}
99+
100+
#[napi]
101+
pub struct Transaction {
102+
inner: Option<storage::Transaction>,
103+
}
104+
105+
#[napi]
106+
impl Transaction {
107+
#[napi]
108+
pub fn put(&mut self, key: Buffer, value: Buffer) -> Result<()> {
109+
self.inner.as_mut()
110+
.ok_or_else(|| Error::from_reason("Transaction already committed or aborted"))?
111+
.put(&key, &value)
112+
.map_err(|e| Error::from_reason(e.to_string()))
113+
}
114+
115+
#[napi]
116+
pub fn get(&self, key: Buffer) -> Result<Option<Buffer>> {
117+
let txn = self.inner.as_ref()
118+
.ok_or_else(|| Error::from_reason("Transaction already committed or aborted"))?;
119+
let result = txn.get(&key)
120+
.map_err(|e| Error::from_reason(e.to_string()))?;
121+
Ok(result.map(|v| v.into()))
122+
}
123+
124+
#[napi]
125+
pub fn delete(&mut self, key: Buffer) -> Result<()> {
126+
self.inner.as_mut()
127+
.ok_or_else(|| Error::from_reason("Transaction already committed or aborted"))?
128+
.delete(&key)
129+
.map_err(|e| Error::from_reason(e.to_string()))
130+
}
131+
132+
#[napi]
133+
pub fn put_path(&mut self, path: String, value: Buffer) -> Result<()> {
134+
self.inner.as_mut()
135+
.ok_or_else(|| Error::from_reason("Transaction already committed or aborted"))?
136+
.put_path(&path, &value)
137+
.map_err(|e| Error::from_reason(e.to_string()))
138+
}
139+
140+
#[napi]
141+
pub fn get_path(&self, path: String) -> Result<Option<Buffer>> {
142+
let txn = self.inner.as_ref()
143+
.ok_or_else(|| Error::from_reason("Transaction already committed or aborted"))?;
144+
let result = txn.get_path(&path)
145+
.map_err(|e| Error::from_reason(e.to_string()))?;
146+
Ok(result.map(|v| v.into()))
147+
}
148+
149+
#[napi]
150+
pub fn commit(&mut self) -> Result<()> {
151+
let txn = self.inner.take()
152+
.ok_or_else(|| Error::from_reason("Transaction already committed or aborted"))?;
153+
txn.commit()
154+
.map_err(|e| Error::from_reason(e.to_string()))?;
155+
Ok(())
156+
}
157+
158+
#[napi]
159+
pub fn abort(&mut self) {
160+
if let Some(txn) = self.inner.take() {
161+
txn.abort();
162+
}
163+
}
164+
}

native/target/.rustc_info.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"rustc_fingerprint":9616641580413405637,"outputs":{"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.92.0 (ded5c06cf 2025-12-08)\nbinary: rustc\ncommit-hash: ded5c06cf21d2b93bffd5d884aa6e96934ee4234\ncommit-date: 2025-12-08\nhost: aarch64-apple-darwin\nrelease: 1.92.0\nLLVM version: 21.1.3\n","stderr":""},"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/sushanth/.rustup/toolchains/stable-aarch64-apple-darwin\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""}},"successes":{}}

native/target/CACHEDIR.TAG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Signature: 8a477f597d28d172789f06886806bc55
2+
# This file is a cache directory tag created by cargo.
3+
# For information about cache directory tags see https://bford.info/cachedir/

native/target/release/.cargo-lock

Whitespace-only changes.
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This file has an mtime of when this was started.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
95625d7fe2b3bfb0

0 commit comments

Comments
 (0)