Add Node.js integration: package, tests, and CI#4
Conversation
Ship an npm package that exports the path to the cr-sqlite loadable extension for use with node:sqlite (Node >= 22.5) or better-sqlite3. - Add package.json with ESM + CJS dual exports - Rewrite nodejs-helper.js with usage docs, add nodejs-helper.cjs - Add 12 integration tests covering extension loading, CRR operations, merge/sync between databases, and last-write-wins conflict resolution - Add GitHub Actions workflow (node-tests.yaml) running on ubuntu + macOS - Remove stale nodejs-install-helper.js and pnpm-lock.yaml (referenced deleted package.json and old vlcn-io release URLs) https://claude.ai/code/session_0156tM6LmjAC3xYz5xwETvym
There was a problem hiding this comment.
Pull request overview
This PR adds a Node.js integration surface for cr-sqlite by introducing an npm package (dual ESM/CJS) that exposes the loadable extension path, along with Node-based integration tests and a GitHub Actions workflow to run them.
Changes:
- Added
core/package.jsonand Node helper entrypoints (nodejs-helper.js/nodejs-helper.cjs+ typings) to ship an npm package exporting the extension path. - Added Node integration tests (
core/test/integration.test.mjs) covering extension loading and replication/merge behaviors. - Added CI workflow (
.github/workflows/node-tests.yaml) to build the extension and run Node integration tests on ubuntu/macOS; removed stale Node install helper and pnpm lockfile.
Reviewed changes
Copilot reviewed 6 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| core/test/integration.test.mjs | Adds Node node:test integration coverage for extension loading, CRR operations, and merge/sync scenarios. |
| core/package.json | Defines the npm package metadata, dual exports (ESM/CJS), build/test scripts, and engine requirement. |
| core/nodejs-helper.js | ESM helper exporting extensionPath with usage docs for node:sqlite and better-sqlite3. |
| core/nodejs-helper.cjs | CJS entry point exporting extensionPath. |
| core/nodejs-helper.d.ts | Type declaration for extensionPath. |
| .github/workflows/node-tests.yaml | CI job to build the extension and run Node integration tests on ubuntu/macOS. |
| core/nodejs-install-helper.js | Removed obsolete prebuild/downloader install helper script. |
| core/pnpm-lock.yaml | Removed obsolete lockfile for the deleted pnpm-based Node setup. |
Files not reviewed (1)
- core/pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Prefer build/ (dev) over dist/ (packaged). | ||
| const buildPath = join(root, "build", "crsqlite"); | ||
| const distPath = join(root, "dist", "crsqlite"); | ||
| const extensionPath = existsSync(buildPath + ".so") || existsSync(buildPath + ".dylib") |
There was a problem hiding this comment.
extensionPath selection only checks for build/crsqlite.so and build/crsqlite.dylib. On Windows builds, CMake will typically produce build/crsqlite.dll, so this logic will incorrectly fall back to dist/ (and may fail to load if dist/ isn’t present). Include a .dll existence check (and/or prefer checking for any of the platform suffixes in both build/ and dist/).
| const extensionPath = existsSync(buildPath + ".so") || existsSync(buildPath + ".dylib") | |
| const hasExtensionBinary = (basePath) => | |
| [".so", ".dylib", ".dll"].some((suffix) => existsSync(basePath + suffix)); | |
| const extensionPath = hasExtensionBinary(buildPath) |
| // Get changes from db2 and apply to db1 | ||
| const changes2 = db2.prepare("SELECT * FROM crsql_changes WHERE db_version > 0").all(); | ||
| const insertStmt1 = db1.prepare( | ||
| "INSERT INTO crsql_changes ([table], pk, cid, val, col_version, db_version, site_id, cl, seq) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" | ||
| ); | ||
| for (const c of changes2) { | ||
| // Only apply changes from db2's site | ||
| const db2SiteId = db2.prepare("SELECT crsql_site_id() AS sid").get().sid; | ||
| const changeSiteId = c.site_id; | ||
| // site_id comparison: apply non-local changes |
There was a problem hiding this comment.
This sync-back loop computes db2SiteId/changeSiteId but never uses them, and it re-inserts all rows from db2.crsql_changes (including changes that were just imported from db1). That means the test isn’t actually exercising the intended “only send non-local changes” behavior and does extra work. Filter the SELECT by site_id IS NOT (SELECT crsql_site_id() FROM db1) (like core/src/crsqlite.test.c’s syncLeftToRight) and/or filter to only db2-local changes before inserting into db1.
| // Get changes from db2 and apply to db1 | |
| const changes2 = db2.prepare("SELECT * FROM crsql_changes WHERE db_version > 0").all(); | |
| const insertStmt1 = db1.prepare( | |
| "INSERT INTO crsql_changes ([table], pk, cid, val, col_version, db_version, site_id, cl, seq) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" | |
| ); | |
| for (const c of changes2) { | |
| // Only apply changes from db2's site | |
| const db2SiteId = db2.prepare("SELECT crsql_site_id() AS sid").get().sid; | |
| const changeSiteId = c.site_id; | |
| // site_id comparison: apply non-local changes | |
| // Get only db2-local changes and apply to db1 | |
| const db2SiteId = db2.prepare("SELECT crsql_site_id() AS sid").get().sid; | |
| const changes2 = db2 | |
| .prepare("SELECT * FROM crsql_changes WHERE db_version > 0 AND site_id = ?") | |
| .all(db2SiteId); | |
| const insertStmt1 = db1.prepare( | |
| "INSERT INTO crsql_changes ([table], pk, cid, val, col_version, db_version, site_id, cl, seq) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" | |
| ); | |
| for (const c of changes2) { |
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "22" |
There was a problem hiding this comment.
The workflow pins Node to major version "22", but these tests require Node >= 22.5 for node:sqlite. To avoid CI picking an older 22.x on some runners/caches, set an explicit minimum (e.g. 22.5.0) or a constraint that guarantees >=22.5.
| node-version: "22" | |
| node-version: "22.5.0" |
Ship an npm package that exports the path to the cr-sqlite loadable extension for use with node:sqlite (Node >= 22.5) or better-sqlite3.
https://claude.ai/code/session_0156tM6LmjAC3xYz5xwETvym