Add seekdb-js: JavaScript bindings for libseekdb - #54
dengfuping wants to merge 5 commits into
Conversation
Modeled on the Python bindings in python/: a node-addon-api addon that links libseekdb dynamically and ships the seekdb server binary, exposed as the seekdb-js npm package (async-only, Promise-first API). - js/: package skeleton (seekdb-js@1.4.0.dev1, N-API v8), binding.gyp (POSIX only), native layer (instance/connection/cursor/types), JS layer with TypeScript definitions, node:test smoke tests, README - CI: node-test job (Node LTS 18/20/22) reusing the seekdb binary from prepare-seekdb; format-check globs for js/src/* - README: document JS bindings build, usage, platform support (no Windows)
- Format js/src/*.{cpp,hpp} with clang-format 14 (repo .clang-format)
- node-test job: add setup-python step; the shared Configure step
requires Python >= 3.11 (python/CMakeLists.txt find_package)
… env teardown Module-static Napi::FunctionReference members (SeekdbInstance/Connection/Cursor constructors) were destroyed after V8 had already shut down, dereferencing a dead v8impl::Reference (EXC_BAD_ACCESS in v8impl::Reference::~Reference). Only Node 22's teardown ordering hid the crash; Node 18/20 segfaulted after all tests passed. Register an env cleanup hook in each Init that resets the static reference, so the static destructor is a no-op.
On Linux CI the seekdb server process spawned by libseekdb shuts down asynchronously after the last instance.close(), and may still be writing to <dbDir>/log when the after() hook runs fs.rmSync, producing ENOTEMPTY. Retry for up to 2s so the runner no longer fails Node 20 (which in turn cancelled the Node 18 matrix leg even though its tests all passed).
There was a problem hiding this comment.
Pull request overview
Adds JavaScript bindings for libseekdb, including an N-API native layer, Promise-based JavaScript API, tests, packaging metadata, CI, and documentation.
Changes:
- Implements native instance, connection, cursor, error, and value conversion layers.
- Adds JavaScript/TypeScript APIs and Node.js integration tests.
- Adds node-gyp packaging, CI coverage, and usage documentation.
Reviewed changes
Copilot reviewed 14 out of 16 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
README.md |
Documents JavaScript bindings and usage. |
js/.gitignore |
Ignores generated JavaScript build artifacts. |
js/README.md |
Provides package build and API documentation. |
js/binding.gyp |
Configures native addon compilation and linking. |
js/package.json |
Defines package metadata, dependencies, and scripts. |
js/package-lock.json |
Locks npm dependencies. |
js/lib/index.js |
Implements the public Promise-based JavaScript API. |
js/lib/seekdb.d.ts |
Defines TypeScript interfaces. |
js/src/addon.cpp |
Initializes exports and error translation. |
js/src/connection.cpp |
Implements connections and transactions. |
js/src/cursor.cpp |
Implements query execution and fetching. |
js/src/instance.cpp |
Implements shared seekdb instance lifecycle. |
js/src/internal.hpp |
Declares native binding state and wrappers. |
js/src/types.cpp |
Converts seekdb values into JavaScript values. |
js/test/seekdb.test.js |
Tests queries, types, transactions, and errors. |
.github/workflows/pr-ci.yml |
Adds Node.js build and test jobs. |
Files not reviewed (1)
- js/package-lock.json: Generated file
Suppressed comments (3)
js/src/connection.cpp:278
- This disconnects immediately even when live cursors retain
ConnectionState. ASeekdbResultstores the connection's borrowedMYSQL *(lib/src/seekdb.c:1067), while disconnect frees it (lib/src/seekdb.c:950-958); fetching that cursor to EOF later dereferences the freed pointer inseekdb_result_next. Invalidate/free all cursors before disconnecting, or defer disconnect until cursor references are released.
auto deferred = Napi::Promise::Deferred::New(env);
auto *worker = new DisconnectWorker(env, deferred, std::move(to_close));
worker->Queue();
js/lib/index.js:150
- Opening a different directory clears and replaces an active module default, contrary to the documented “first open becomes the module default” behavior and the Python binding. This silently redirects subsequent module-level
connect()calls. Return the newly opened instance, but retain an existing active default.
defaultInstance = null;
return binding.open(dbDir).then((instance) => {
defaultInstance = new SeekdbInstance(instance);
js/package.json:26
- The declared Node 16 support conflicts with the locked toolchain:
node-addon-api@8.9.2declares Node^18 || ^20 || >=21, andnode-gyp@11.5.0requires^18.17.0 || >=20.5.0. Node 16 is also absent from CI. Either use dependencies that support and test Node 16, or raise this engine requirement and both READMEs to the actual minimum.
"engines": {
"node": ">=16"
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| void Execute() override | ||
| { | ||
| if (conn_) | ||
| conn_->reset(); |
| void OnOK() override | ||
| { | ||
| if (erase_ && instance_) | ||
| ReleaseInstance(db_dir_, instance_.get()); |
| close() { | ||
| return this._native.close().catch(rethrow); | ||
| } |
| "files": [ | ||
| "lib/", | ||
| "src/", | ||
| "binding.gyp", | ||
| "README.md" |
| 'js/src/*.cc' \ | ||
| 'js/src/*.cpp' \ | ||
| 'js/src/*.h' |
| ```js | ||
| const seekdb = require('seekdb-js'); | ||
|
|
||
| // Async API (Promise-based) | ||
| const instance = await seekdb.open('./seekdb.db'); |
| ```js | ||
| const seekdb = require('seekdb-js'); | ||
|
|
||
| // All blocking operations are Promise-based and run off the event loop | ||
| const instance = await seekdb.open('./seekdb.db'); |
| if (!result_) | ||
| return false; | ||
| if (seekdb_result_next(result_) != SEEKDB_SUCCESS) | ||
| return false; | ||
| int64_t ncol = 0; | ||
| SDB_CHECK(seekdb_result_column_count(result_, &ncol)); |
| { | ||
| Napi::Object obj = SeekdbInstance::constructor.New({}); | ||
| SeekdbInstance *inst = Napi::ObjectWrap<SeekdbInstance>::Unwrap(obj); | ||
| inst->Adopt(db_dir_, instance_); |
|
Thanks for adding the JavaScript bindings. I reviewed the native lifecycle, async API, packaging, and CI paths. I found three issues that I think should block merging:
Additional issues:
The two crash reproductions were run in isolated Node processes. No repository files were changed during this review. |
Summary
Adds
js/, a JavaScript binding package (seekdb-js) for the seekdb C clientlibrary, modeled on the Python bindings in
python/. The N-API addon linkslibseekdbdynamically and ships theseekdbserver binary inside thepackage, matching the Python wheel deployment layout.
The same addon runs under Node.js and Bun; Deno 2 loads it through npm
packages (
nodeModulesDir: "auto"plus--allow-ffi).What's included
js/package:seekdb-js@1.4.0.dev1, N-API v8, node-gyp build (POSIXonly: Linux x86_64/aarch64, macOS arm64; no Windows)
src/):SeekdbInstanceshared-instance table,Connection,Cursor, asyncAsyncWorkerwrappers, type/value conversion,SeekdbErrornormalizationlib/): Promise-first API (open/close/connect/execute/ fetchOne/fetchAll), module default instance, TypeScript definitionstest/):node:testsmoke tests covering create/insert/hybridsearch, transactions, and error codes
pr-ci.yml): newnode-testjob on Node LTS 18/20/22, reusing theseekdbbinary fromprepare-seekdb; clang-format globs forjs/src/*js/README.mdAPI notes
loop; only
cursor(),closed,dbDirare synchronous.NULL→null,INT64/UINT64→number(
bigintbeyondNumber.MAX_SAFE_INTEGER),DECIMAL→string,DATE/DATETIME/TIMESTAMP→string,VARCHAR→string.Validation
cd js && npm run build, thennode --test test/on Node 18, 20,and 22 — 4/4 tests pass with a clean exit (no crash)
test, ASan/UBSan, Format check, Prepare seekdb binary, license/cla
clang-format(v14) clean onjs/src/*.{cpp,hpp}Two runtime issues surfaced by the first CI run were fixed in this PR:
Napi::FunctionReferenceswere destroyed after V8 teardown; fixed by resetting them from an
Env::AddCleanupHookin eachInit.ENOTEMPTYteardown failure on Linux CI: the spawned seekdb server was stillflushing its log when the test
afterhook removed the temp db dir; fixed byretrying the removal.
Not run
delivers the local source-install path only