Skip to content

Commit 93f0d94

Browse files
committed
docs(nodejs): API docs, examples, pool, Node CI workflow
Content from PRs 191–194: API reference, examples, CHANGELOG, connection pool, pool tests, nodejs-workflow.yml. Stacked on nodejs-core.
1 parent c851676 commit 93f0d94

17 files changed

Lines changed: 1199 additions & 26 deletions

.github/workflows/nodejs-workflow.yml

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
name: Build Node.js Module
22

33
on:
4-
push:
5-
tags:
6-
- 'v*'
74
workflow_dispatch:
85
workflow_call:
96
inputs:
@@ -12,15 +9,9 @@ on:
129
required: true
1310
default: false
1411

15-
# Let heavy builds finish; new push queues instead of cancelling.
16-
concurrency:
17-
group: ${{ github.workflow }}-${{ github.ref }}
18-
cancel-in-progress: false
19-
2012
jobs:
2113
build-nodejs:
2214
runs-on: ${{ matrix.os }}
23-
timeout-minutes: 90
2415
strategy:
2516
matrix:
2617
include:
@@ -155,8 +146,9 @@ jobs:
155146
run: rm -rf package
156147

157148
# Push prebuilt/*.node to repo so pnpm add github:user/ladybug#path:tools/nodejs_api uses them without building.
149+
# Runs only on manual workflow_dispatch to avoid push loops.
158150
update-prebuilt:
159-
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))
151+
if: github.event_name == 'workflow_dispatch'
160152
needs: build-nodejs
161153
runs-on: ubuntu-latest
162154
permissions:
@@ -180,26 +172,12 @@ jobs:
180172
find "$d" -name "lbugjs-*.node" -exec cp {} tools/nodejs_api/prebuilt/ \;
181173
done
182174
ls -la tools/nodejs_api/prebuilt/
183-
cp -r tools/nodejs_api/prebuilt /tmp/prebuilt-backup
184-
185-
- name: Switch to default branch (on tag)
186-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
187-
run: |
188-
git fetch origin ${{ github.event.repository.default_branch }}
189-
git checkout ${{ github.event.repository.default_branch }}
190-
git pull origin ${{ github.event.repository.default_branch }} --no-rebase
191-
rm -rf tools/nodejs_api/prebuilt
192-
cp -r /tmp/prebuilt-backup tools/nodejs_api/prebuilt
193175
194176
- name: Commit and push prebuilt
195177
run: |
196178
git config user.name "github-actions[bot]"
197179
git config user.email "github-actions[bot]@users.noreply.github.com"
198-
git add -f tools/nodejs_api/prebuilt/
180+
git add tools/nodejs_api/prebuilt/
199181
git diff --staged --quiet && echo "No prebuilt changes" && exit 0
200182
git commit -m "chore(nodejs): update prebuilt addons from CI [skip ci]"
201-
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
202-
git push origin HEAD:${{ github.event.repository.default_branch }}
203-
else
204-
git push origin HEAD:"${GITHUB_REF#refs/heads/}"
205-
fi
183+
git push

tools/nodejs_api/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Changelog
2+
3+
### Unreleased
4+
5+
- **Breaking:** Drop support for Node.js versions lower than 20; the package now requires **Node.js 20 or later** (`engines.node: ">=20.0.0"`).
6+
- **Breaking:** Upgrade native build tooling to **`cmake-js` ^8.0.0** and **`node-addon-api` ^8.0.0**, aligning with the Node.js 20+ support window.
7+
- Clarify Node.js version requirement in the README.
8+
- Add **Node.js API testing guide** at `tools/nodejs_api/docs/nodejs_testing.md` for test authors and reviewers (assertions, isolation, data types, concurrency, errors, resource lifecycle, validation checklist). Remove `tools/nodejs_api/test/test_correctness_audit.md` in favor of this guide.
9+

tools/nodejs_api/docs/API.md

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
# Ladybug Node.js API Reference
2+
3+
Detailed API documentation for the `lbug` package. For installation, quick start, and usage patterns see [README.md](../README.md).
4+
5+
---
6+
7+
## Module exports
8+
9+
**CommonJS:**
10+
11+
```js
12+
const lbug = require("lbug");
13+
// or
14+
const { Database, Connection, PreparedStatement, QueryResult, createPool, Pool, LBUG_DATABASE_LOCKED, VERSION, STORAGE_VERSION } = require("lbug");
15+
```
16+
17+
**ES Modules:**
18+
19+
```js
20+
import lbug from "lbug";
21+
// or
22+
import { Database, Connection, PreparedStatement, QueryResult, createPool, Pool, LBUG_DATABASE_LOCKED, VERSION, STORAGE_VERSION } from "lbug";
23+
```
24+
25+
| Export | Description |
26+
|--------|-------------|
27+
| `Database` | Database instance (path, options). |
28+
| `Connection` | Connection to a database; runs Cypher and manages streams. |
29+
| `PreparedStatement` | Prepared Cypher statement (from `Connection.prepare`). |
30+
| `QueryResult` | Result of `query()` / `execute()`; async iterable, stream, getAll, etc. |
31+
| `createPool` | Factory: `createPool(options)``Pool`. |
32+
| `Pool` | Connection pool (use `createPool`, not `new Pool`). |
33+
| `LBUG_DATABASE_LOCKED` | Error code string when DB file is locked. |
34+
| `VERSION` | Library version string. |
35+
| `STORAGE_VERSION` | Storage version (bigint). |
36+
37+
---
38+
39+
## Types (TypeScript / JSDoc)
40+
41+
### Value types
42+
43+
| Type | Description |
44+
|------|-------------|
45+
| `Nullable<T>` | `T \| null` |
46+
| `Callback<T>` | `(error: Error \| null, result?: T) => void` |
47+
| `ProgressCallback` | `(pipelineProgress, numPipelinesFinished, numPipelines) => void` |
48+
| `QueryOptions` | `{ signal?: AbortSignal; progressCallback?: ProgressCallback }` |
49+
| `NodeID` | `{ offset: number; table: number }` |
50+
| `NodeValue` | `{ _label: string \| null; _id: NodeID \| null; [key: string]: any }` |
51+
| `RelValue` | `{ _src, _dst, _label, _id; [key: string]: any }` |
52+
| `RecursiveRelValue` | `{ _nodes: any[]; _rels: any[] }` |
53+
| `LbugValue` | `null \| boolean \| number \| bigint \| string \| Date \| NodeValue \| RelValue \| RecursiveRelValue \| LbugValue[] \| { [key: string]: LbugValue }` |
54+
55+
### Config types
56+
57+
| Type | Description |
58+
|------|-------------|
59+
| `SystemConfig` | Database options (bufferPoolSize, enableCompression, readOnly, maxDBSize, autoCheckpoint, checkpointThreshold). |
60+
| `PoolDatabaseOptions` | Same shape as Database constructor options (no path): bufferManagerSize, enableCompression, readOnly, maxDBSize, autoCheckpoint, checkpointThreshold, throwOnWalReplayFailure, enableChecksums, openLockRetryMs. |
61+
| `PoolOptions` | databasePath?, databaseOptions?, minSize?, **maxSize**, acquireTimeoutMillis?, validateOnAcquire?. |
62+
| `QuerySummary` | `{ compilingTime: number; executionTime: number }` (milliseconds). |
63+
64+
---
65+
66+
## Database
67+
68+
In-process database instance. One database can be shared by multiple `Connection` instances (e.g. in a pool).
69+
70+
### Constructor
71+
72+
```ts
73+
new Database(
74+
databasePath?: string, // default ":memory:"
75+
bufferManagerSize?: number, // default 0
76+
enableCompression?: boolean, // default true
77+
readOnly?: boolean, // default false
78+
maxDBSize?: number, // default 0
79+
autoCheckpoint?: boolean, // default true
80+
checkpointThreshold?: number, // default -1
81+
throwOnWalReplayFailure?: boolean, // default true
82+
enableChecksums?: boolean, // default true
83+
openLockRetryMs?: number // default 5000; 0 = fail immediately on lock
84+
)
85+
```
86+
87+
- **databasePath**: `":memory:"` or path to directory. Empty/undefined → `":memory:"`.
88+
- **openLockRetryMs**: Only for async `init()`. Retry opening for up to this many ms when file is locked. Ignored for `:memory:`.
89+
90+
### Instance methods
91+
92+
| Method | Returns | Description |
93+
|--------|---------|-------------|
94+
| `init()` | `Promise<void>` | Initialize DB (optional; done on first use). Retries on lock for up to `openLockRetryMs`. |
95+
| `initSync()` | `void` | Initialize synchronously; blocks. No retry on lock. |
96+
| `close()` | `Promise<void>` | Close and release resources. |
97+
| `closeSync()` | `void` | Close synchronously. |
98+
99+
### Static methods
100+
101+
| Method | Returns | Description |
102+
|--------|---------|-------------|
103+
| `Database.getVersion()` | `string` | Library version. |
104+
| `Database.getStorageVersion()` | `number` | Storage version. |
105+
106+
### Errors
107+
108+
- Lock errors on init are normalized to `Error` with `code === LBUG_DATABASE_LOCKED`. See [database_locked.md](database_locked.md).
109+
110+
---
111+
112+
## Connection
113+
114+
Connection to a `Database`. Use for queries, prepared statements, transactions, streams, and metadata.
115+
116+
### Constructor
117+
118+
```ts
119+
new Connection(database: Database, numThreads?: number)
120+
```
121+
122+
- **numThreads**: Max threads for query execution. Can be set later with `setMaxNumThreadForExec(numThreads)`.
123+
124+
### Initialization
125+
126+
| Method | Returns | Description |
127+
|--------|---------|-------------|
128+
| `init()` | `Promise<void>` | Initialize connection (optional; done on first query). |
129+
| `initSync()` | `void` | Initialize synchronously; may block. |
130+
131+
### Query execution
132+
133+
| Method | Returns | Description |
134+
|--------|---------|-------------|
135+
| `query(statement, optionsOrProgressCallback?)` | `Promise<QueryResult \| QueryResult[]>` | Execute Cypher. Options: `{ signal?, progressCallback? }`. Rejects with `AbortError` if `signal` aborted. |
136+
| `querySync(statement)` | `QueryResult \| QueryResult[]` | Execute synchronously; blocks. |
137+
| `prepare(statement)` | `Promise<PreparedStatement>` | Prepare a statement. |
138+
| `prepareSync(statement)` | `PreparedStatement` | Prepare synchronously. |
139+
| `execute(preparedStatement, params?, optionsOrProgressCallback?)` | `Promise<QueryResult \| QueryResult[]>` | Execute prepared statement with `params` object. Same options as `query`. |
140+
| `executeSync(preparedStatement, params?)` | `QueryResult \| QueryResult[]` | Execute prepared statement synchronously. |
141+
142+
**params**: Plain object, e.g. `{ name: "Alice", age: 30 }`. Keys must match parameter names in the prepared Cypher.
143+
144+
### Transaction
145+
146+
| Method | Returns | Description |
147+
|--------|---------|-------------|
148+
| `transaction(fn)` | `Promise<T>` | Run `fn()` in a single write transaction. `BEGIN TRANSACTION` → fn() → `COMMIT` on success, `ROLLBACK` on throw. |
149+
150+
### Configuration and control
151+
152+
| Method | Returns | Description |
153+
|--------|---------|-------------|
154+
| `setMaxNumThreadForExec(numThreads)` | `void` | Max threads for execution. |
155+
| `setQueryTimeout(timeoutInMs)` | `void` | Query timeout in ms; queries aborted after this. |
156+
| `interrupt()` | `void` | Interrupt current query on this connection. No-op if none running. |
157+
158+
### Metadata and health
159+
160+
| Method | Returns | Description |
161+
|--------|---------|-------------|
162+
| `ping()` | `Promise<boolean>` | Liveness check; rejects if connection broken. |
163+
| `explain(statement)` | `Promise<string>` | Run EXPLAIN on Cypher; returns plan string (one row per line). |
164+
| `getNumNodes(nodeName)` | `number` | Count of nodes in node table. Connection must be initialized. |
165+
| `getNumRels(relName)` | `number` | Count of relationships in rel table. |
166+
167+
### Stream source (LOAD FROM)
168+
169+
| Method | Returns | Description |
170+
|--------|---------|-------------|
171+
| `registerStream(name, source, options)` | `Promise<void>` | Register AsyncIterable as `LOAD FROM name`. **options.columns** required: `[{ name, type }]`. Types: INT64, INT32, INT16, INT8, UINT64, UINT32, DOUBLE, FLOAT, STRING, BOOL, DATE, TIMESTAMP. |
172+
| `unregisterStream(name)` | `void` | Unregister stream by name. |
173+
174+
**source**: AsyncIterable of rows; each row is an array (column order) or object (column names).
175+
176+
### Lifecycle
177+
178+
| Method | Returns | Description |
179+
|--------|---------|-------------|
180+
| `close()` | `Promise<void>` | Close connection. |
181+
| `closeSync()` | `void` | Close synchronously. |
182+
183+
---
184+
185+
## PreparedStatement
186+
187+
Created by `Connection.prepare()` / `Connection.prepareSync()`. Do not construct directly.
188+
189+
### Instance methods
190+
191+
| Method | Returns | Description |
192+
|--------|---------|-------------|
193+
| `isSuccess()` | `boolean` | Whether preparation succeeded. |
194+
| `getErrorMessage()` | `string` | Error message if preparation failed. |
195+
196+
Execution is via `conn.execute(preparedStatement, params)` or `conn.executeSync(preparedStatement, params)`. If `!isSuccess()`, `execute` rejects with `getErrorMessage()`.
197+
198+
---
199+
200+
## QueryResult
201+
202+
Returned by `Connection.query()`, `Connection.querySync()`, `Connection.execute()`, `Connection.executeSync()`. Implements `AsyncIterable<Record<string, LbugValue> | null>`.
203+
204+
### Consumption (pick one style)
205+
206+
| Method / usage | Returns | Description |
207+
|----------------|---------|-------------|
208+
| `getAll()` | `Promise<Record[]>` | All rows (loads into memory). |
209+
| `getAllSync()` | `Record[]` | All rows synchronously. |
210+
| `getNext()` | `Promise<Record \| null>` | Next row; null when exhausted. |
211+
| `getNextSync()` | `Record \| null` | Next row synchronously. |
212+
| `hasNext()` | `boolean` | Whether more rows exist. |
213+
| `for await (const row of result)` || Async iteration; no full materialization. |
214+
| `toStream()` | `stream.Readable` | Node.js Readable (object mode), one row per chunk. |
215+
| `each(resultCb, doneCb, errorCb)` | `void` | Callback-based iteration. |
216+
| `all(resultCb, errorCb)` | `void` | Callback with all rows. |
217+
| `toString()` | `string` | Header + rows (or error message for failed query). |
218+
219+
### Metadata
220+
221+
| Method | Returns | Description |
222+
|--------|---------|-------------|
223+
| `getNumTuples()` | `number` | Number of rows. |
224+
| `getColumnNames()` | `Promise<string[]>` | Column names. |
225+
| `getColumnNamesSync()` | `string[]` | Column names synchronously. |
226+
| `getColumnDataTypes()` | `Promise<string[]>` | Column data types. |
227+
| `getColumnDataTypesSync()` | `string[]` | Column types synchronously. |
228+
| `getQuerySummary()` | `Promise<QuerySummary>` | `{ compilingTime, executionTime }` in ms. |
229+
| `getQuerySummarySync()` | `QuerySummary` | Same, synchronously. |
230+
231+
### Other
232+
233+
| Method | Returns | Description |
234+
|--------|---------|-------------|
235+
| `resetIterator()` | `void` | Reset cursor to start (for re-iteration). |
236+
| `close()` | `void` | Release resources. Optional if fully consumed. |
237+
238+
**Multiple results**: A batch of statements can return `QueryResult[]`. Single statement returns one `QueryResult`.
239+
240+
---
241+
242+
## Pool and createPool
243+
244+
Connection pool: one shared `Database`, up to `maxSize` `Connection` instances.
245+
246+
### createPool(options)
247+
248+
```ts
249+
function createPool(options: PoolOptions): Pool
250+
```
251+
252+
**PoolOptions:**
253+
254+
| Option | Type | Default | Description |
255+
|--------|------|---------|-------------|
256+
| `databasePath` | string | `":memory:"` | DB path. |
257+
| `databaseOptions` | PoolDatabaseOptions || Same shape as Database constructor (no path). |
258+
| `minSize` | number | 0 | Minimum connections to keep. |
259+
| `maxSize` | number | **required** | Maximum connections. |
260+
| `acquireTimeoutMillis` | number | 0 | Max wait for acquire (0 = wait forever). |
261+
| `validateOnAcquire` | boolean | false | If true, call `conn.ping()` before handing out. |
262+
263+
### Pool methods
264+
265+
| Method | Returns | Description |
266+
|--------|---------|-------------|
267+
| `acquire()` | `Promise<Connection>` | Get a connection; **must** call `release(conn)` when done. |
268+
| `release(conn)` | `void` | Return connection to pool. |
269+
| `run(fn)` | `Promise<T>` | Acquire, run `fn(conn)`, release in `finally`. Preferred over manual acquire/release. |
270+
| `close()` | `Promise<void>` | Reject new/pending acquire; close all connections and database. |
271+
272+
**Example:**
273+
274+
```js
275+
const pool = createPool({ databasePath: "./mydb", maxSize: 10 });
276+
const rows = await pool.run(async (conn) => {
277+
const result = await conn.query("MATCH (u:User) RETURN u.name LIMIT 5");
278+
const rows = await result.getAll();
279+
result.close();
280+
return rows;
281+
});
282+
await pool.close();
283+
```
284+
285+
---
286+
287+
## Constants
288+
289+
| Name | Type | Description |
290+
|------|------|-------------|
291+
| `LBUG_DATABASE_LOCKED` | `"LBUG_DATABASE_LOCKED"` | Error code when DB file is locked. Use with `err.code === LBUG_DATABASE_LOCKED`. |
292+
| `VERSION` | string | Library version (same as `Database.getVersion()`). |
293+
| `STORAGE_VERSION` | bigint | Storage version (same as `Database.getStorageVersion()`). |
294+
295+
---
296+
297+
## Query options and cancellation
298+
299+
- **signal**: Pass `AbortSignal` (e.g. from `AbortController`) in options to cancel `query()` or `execute()`. On abort, the promise rejects with `DOMException` "AbortError".
300+
- **progressCallback**: `(pipelineProgress, numPipelinesFinished, numPipelines) => void`. Optional progress updates during execution.
301+
302+
Legacy: you can pass a single function as the second argument to `query(statement, progressCallback)` or `execute(ps, params, progressCallback)` instead of an options object.
303+
304+
---
305+
306+
## Error handling
307+
308+
- **Database lock**: Async `init()` retries for `openLockRetryMs` (default 5s). Then throws with `code === LBUG_DATABASE_LOCKED`. See [database_locked.md](database_locked.md).
309+
- **Abort**: When `options.signal` is aborted, `query`/`execute` reject with `DOMException` "AbortError".
310+
- **Prepared statement**: If `!preparedStatement.isSuccess()`, `execute` rejects with `preparedStatement.getErrorMessage()`.
311+
- **Validation**: Invalid arguments (e.g. non-object params, wrong types) throw `Error` with descriptive messages.
312+
313+
---
314+
315+
## Related docs
316+
317+
- [README.md](../README.md) — Installation, quick start, transactions, stream loading, pool usage, prebuilt binaries.
318+
- [database_locked.md](database_locked.md) — Lock behavior, retry, read-only, best practices.
319+
- [execution_chain_analysis.md](execution_chain_analysis.md) — LOAD FROM stream execution chain (for implementers).

0 commit comments

Comments
 (0)