@stoolap/node 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -22
- package/index.d.ts +6 -0
- package/lib/database.js +8 -0
- package/lib/transaction.js +6 -1
- package/package.json +6 -6
- package/src/stoolap.c +34 -5
package/README.md
CHANGED
|
@@ -108,6 +108,7 @@ Sync methods run on the main thread. Faster for simple operations but block the
|
|
|
108
108
|
| Method | Returns | Description |
|
|
109
109
|
|--------|---------|-------------|
|
|
110
110
|
| `Database.openSync(path)` | `Database` | Open a database |
|
|
111
|
+
| `clone()` | `Database` | Clone handle (shared engine, own state) |
|
|
111
112
|
| `executeSync(sql, params?)` | `RunResult` | Execute DML statement |
|
|
112
113
|
| `execSync(sql)` | `void` | Execute a DDL statement |
|
|
113
114
|
| `querySync(sql, params?)` | `Object[]` | Query rows as objects |
|
|
@@ -126,7 +127,7 @@ import { Database, RunResult } from '@stoolap/node';
|
|
|
126
127
|
|
|
127
128
|
#### Persistence
|
|
128
129
|
|
|
129
|
-
File-based databases persist data to disk using WAL (Write-Ahead Logging) and
|
|
130
|
+
File-based databases persist data to disk using WAL (Write-Ahead Logging) and an immutable volume-based storage engine. Hot data lives in memory, cold data is sealed into columnar `.vol` files with zone maps, bloom filters, and LZ4 compression. Data survives process restarts.
|
|
130
131
|
|
|
131
132
|
```js
|
|
132
133
|
const db = await Database.open('./mydata');
|
|
@@ -147,18 +148,18 @@ await db2.close();
|
|
|
147
148
|
Pass configuration as query parameters in the path:
|
|
148
149
|
|
|
149
150
|
```js
|
|
150
|
-
// Maximum durability: fsync on every
|
|
151
|
-
const db = await Database.open('./mydata?
|
|
151
|
+
// Maximum durability: fsync on every write
|
|
152
|
+
const db = await Database.open('./mydata?sync_mode=full');
|
|
152
153
|
|
|
153
|
-
// High throughput: no fsync,
|
|
154
|
-
const db = await Database.open('./mydata?
|
|
154
|
+
// High throughput: no fsync, data durable at checkpoint
|
|
155
|
+
const db = await Database.open('./mydata?sync_mode=none');
|
|
155
156
|
|
|
156
|
-
// Custom
|
|
157
|
-
const db = await Database.open('./mydata?
|
|
157
|
+
// Custom checkpoint interval with compression
|
|
158
|
+
const db = await Database.open('./mydata?checkpoint_interval=60&compression=on');
|
|
158
159
|
|
|
159
160
|
// Multiple options
|
|
160
161
|
const db = await Database.open(
|
|
161
|
-
'./mydata?
|
|
162
|
+
'./mydata?sync_mode=normal&checkpoint_interval=120&compact_threshold=4'
|
|
162
163
|
);
|
|
163
164
|
```
|
|
164
165
|
|
|
@@ -168,26 +169,40 @@ Controls the durability vs. performance trade-off:
|
|
|
168
169
|
|
|
169
170
|
| Mode | Value | Description |
|
|
170
171
|
|------|-------|-------------|
|
|
171
|
-
| `none` | `
|
|
172
|
-
| `normal` | `
|
|
173
|
-
| `full` | `
|
|
172
|
+
| `none` | `sync_mode=none` | No fsync. Data durable only after checkpoint |
|
|
173
|
+
| `normal` | `sync_mode=normal` | Fsync every 1 second (batched). DDL fsyncs immediately (default) |
|
|
174
|
+
| `full` | `sync_mode=full` | Fsync on every write. Maximum durability |
|
|
174
175
|
|
|
175
176
|
##### All Configuration Parameters
|
|
176
177
|
|
|
177
178
|
| Parameter | Default | Description |
|
|
178
179
|
|-----------|---------|-------------|
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `wal_buffer_size` | `65536` | WAL buffer size in bytes (64 KB) |
|
|
184
|
-
| `wal_max_size` | `67108864` | Max WAL file size before rotation (64 MB) |
|
|
185
|
-
| `commit_batch_size` | `100` | Commits to batch before syncing (normal mode) |
|
|
186
|
-
| `sync_interval_ms` | `10` | Minimum ms between syncs (normal mode) |
|
|
180
|
+
| `sync_mode` | `normal` | Sync mode: `none`, `normal`, or `full` |
|
|
181
|
+
| `checkpoint_interval` | `60` | Seconds between checkpoint cycles (seal + compact + WAL truncate) |
|
|
182
|
+
| `compact_threshold` | `4` | Number of cold volumes before compaction merges them |
|
|
183
|
+
| `checkpoint_on_close` | `on` | Seal all hot rows on clean shutdown for fast startup |
|
|
187
184
|
| `wal_compression` | `on` | LZ4 compression for WAL entries |
|
|
188
|
-
| `
|
|
189
|
-
| `compression` | |
|
|
190
|
-
| `
|
|
185
|
+
| `volume_compression` | `on` | LZ4 compression for cold volume files |
|
|
186
|
+
| `compression` | `on` | Shorthand: set both `wal_compression` and `volume_compression` |
|
|
187
|
+
| `keep_snapshots` | `5` | Number of backup snapshot files to retain |
|
|
188
|
+
|
|
189
|
+
#### Cloning
|
|
190
|
+
|
|
191
|
+
`clone()` creates a new `Database` handle that shares the same underlying engine (data, indexes, transactions) but has its own executor and error state. Useful for concurrent access patterns such as worker threads.
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
const db = await Database.open('./mydata');
|
|
195
|
+
const db2 = db.clone();
|
|
196
|
+
|
|
197
|
+
// Both see the same data
|
|
198
|
+
await db.execute('INSERT INTO users VALUES ($1, $2)', [1, 'Alice']);
|
|
199
|
+
const row = db2.queryOneSync('SELECT * FROM users WHERE id = $1', [1]);
|
|
200
|
+
// { id: 1, name: 'Alice' }
|
|
201
|
+
|
|
202
|
+
// Each clone must be closed independently
|
|
203
|
+
await db2.close();
|
|
204
|
+
await db.close();
|
|
205
|
+
```
|
|
191
206
|
|
|
192
207
|
#### Raw Query Format
|
|
193
208
|
|
package/index.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export declare class Database {
|
|
|
17
17
|
* Open a database synchronously.
|
|
18
18
|
*/
|
|
19
19
|
static openSync(path: string): Database;
|
|
20
|
+
/**
|
|
21
|
+
* Clone this database handle. The clone shares the same underlying engine
|
|
22
|
+
* (data, indexes, transactions) but has its own executor and error state.
|
|
23
|
+
* Each clone must be closed independently.
|
|
24
|
+
*/
|
|
25
|
+
clone(): Database;
|
|
20
26
|
/**
|
|
21
27
|
* Execute a DDL/DML statement. Returns Promise<{ changes: number }>.
|
|
22
28
|
*
|
package/lib/database.js
CHANGED
|
@@ -128,6 +128,14 @@ class Database {
|
|
|
128
128
|
return this.#stoolap.dbExecBatchBuf(this.#ptr, sql, batchBuf);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// -- Clone --
|
|
132
|
+
|
|
133
|
+
clone() {
|
|
134
|
+
this.#checkOpen();
|
|
135
|
+
const clonedPtr = this.#stoolap.dbClone(this.#ptr);
|
|
136
|
+
return new Database(clonedPtr, this.#stoolap);
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
// -- Execute (DML) --
|
|
132
140
|
|
|
133
141
|
async execute(sql, params) {
|
package/lib/transaction.js
CHANGED
|
@@ -179,7 +179,12 @@ class Transaction {
|
|
|
179
179
|
executeBatchSync(sql, paramsArray) {
|
|
180
180
|
this.#checkSyncReady();
|
|
181
181
|
if (paramsArray.length > 0 && Array.isArray(paramsArray[0])) {
|
|
182
|
-
return this.#stoolap.txExecBatch(
|
|
182
|
+
return this.#stoolap.txExecBatch(
|
|
183
|
+
this.#dbPtr,
|
|
184
|
+
this.#ptr,
|
|
185
|
+
sql,
|
|
186
|
+
paramsArray,
|
|
187
|
+
);
|
|
183
188
|
}
|
|
184
189
|
let totalChanges = 0;
|
|
185
190
|
for (const params of paramsArray) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stoolap/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "High-performance Node.js driver for Stoolap embedded SQL database",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"bench": "node benchmark.mjs"
|
|
19
19
|
},
|
|
20
20
|
"optionalDependencies": {
|
|
21
|
-
"@stoolap/lib-darwin-arm64": "0.
|
|
22
|
-
"@stoolap/lib-darwin-x64": "0.
|
|
23
|
-
"@stoolap/lib-linux-arm64-gnu": "0.
|
|
24
|
-
"@stoolap/lib-linux-x64-gnu": "0.
|
|
25
|
-
"@stoolap/lib-win32-x64-msvc": "0.
|
|
21
|
+
"@stoolap/lib-darwin-arm64": "0.4.0",
|
|
22
|
+
"@stoolap/lib-darwin-x64": "0.4.0",
|
|
23
|
+
"@stoolap/lib-linux-arm64-gnu": "0.4.0",
|
|
24
|
+
"@stoolap/lib-linux-x64-gnu": "0.4.0",
|
|
25
|
+
"@stoolap/lib-win32-x64-msvc": "0.4.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"better-sqlite3": "^12.6.2",
|
package/src/stoolap.c
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include <stdlib.h>
|
|
13
13
|
#include <string.h>
|
|
14
14
|
#include <stdint.h>
|
|
15
|
+
#include <stdio.h>
|
|
15
16
|
|
|
16
17
|
#ifdef _WIN32
|
|
17
18
|
#include <windows.h>
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
typedef HMODULE LibHandle;
|
|
24
25
|
#else
|
|
25
26
|
#include <dlfcn.h>
|
|
27
|
+
#include <alloca.h>
|
|
26
28
|
#define DLOPEN(path) dlopen(path, RTLD_NOW | RTLD_LOCAL)
|
|
27
29
|
#define DLSYM(lib, name) dlsym(lib, name)
|
|
28
30
|
#define DLERROR() dlerror()
|
|
@@ -68,6 +70,7 @@ typedef struct {
|
|
|
68
70
|
typedef const char* (*fn_version)(void);
|
|
69
71
|
typedef int32_t (*fn_open)(const char*, StoolapDB**);
|
|
70
72
|
typedef int32_t (*fn_open_mem)(StoolapDB**);
|
|
73
|
+
typedef int32_t (*fn_clone)(StoolapDB*, StoolapDB**);
|
|
71
74
|
typedef int32_t (*fn_close)(StoolapDB*);
|
|
72
75
|
typedef const char* (*fn_errmsg)(const StoolapDB*);
|
|
73
76
|
typedef int32_t (*fn_exec)(StoolapDB*, const char*, int64_t*);
|
|
@@ -112,6 +115,7 @@ static struct {
|
|
|
112
115
|
fn_version version;
|
|
113
116
|
fn_open open;
|
|
114
117
|
fn_open_mem open_mem;
|
|
118
|
+
fn_clone clone;
|
|
115
119
|
fn_close close;
|
|
116
120
|
fn_errmsg errmsg;
|
|
117
121
|
fn_exec exec;
|
|
@@ -156,6 +160,8 @@ static LibHandle lib_handle = NULL;
|
|
|
156
160
|
/* ---- Helpers ---- */
|
|
157
161
|
|
|
158
162
|
#define THROW(env, msg) do { napi_throw_error(env, NULL, msg); return NULL; } while(0)
|
|
163
|
+
#define ERR_SAVE(msg, fallback) \
|
|
164
|
+
char _eb[512]; snprintf(_eb, sizeof(_eb), "%s", (msg) ? (msg) : (fallback))
|
|
159
165
|
#define CHECK(env, status) if ((status) != napi_ok) return NULL
|
|
160
166
|
|
|
161
167
|
static napi_value make_changes(napi_env env, int64_t n) {
|
|
@@ -1510,6 +1516,7 @@ static napi_value fn_load_library(napi_env env, napi_callback_info info) {
|
|
|
1510
1516
|
LOAD(version, "stoolap_version");
|
|
1511
1517
|
LOAD(open, "stoolap_open");
|
|
1512
1518
|
LOAD(open_mem, "stoolap_open_in_memory");
|
|
1519
|
+
LOAD(clone, "stoolap_clone");
|
|
1513
1520
|
LOAD(close, "stoolap_close");
|
|
1514
1521
|
LOAD(errmsg, "stoolap_errmsg");
|
|
1515
1522
|
LOAD(exec, "stoolap_exec");
|
|
@@ -1586,6 +1593,27 @@ static napi_value fn_db_open(napi_env env, napi_callback_info info) {
|
|
|
1586
1593
|
return ext;
|
|
1587
1594
|
}
|
|
1588
1595
|
|
|
1596
|
+
/* dbClone(external): external */
|
|
1597
|
+
static napi_value fn_db_clone(napi_env env, napi_callback_info info) {
|
|
1598
|
+
size_t argc = 1;
|
|
1599
|
+
napi_value argv[1];
|
|
1600
|
+
napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
|
|
1601
|
+
|
|
1602
|
+
StoolapDB* db;
|
|
1603
|
+
napi_get_value_external(env, argv[0], (void**)&db);
|
|
1604
|
+
|
|
1605
|
+
StoolapDB* cloned = NULL;
|
|
1606
|
+
int32_t rc = S.clone(db, &cloned);
|
|
1607
|
+
if (rc != STOOLAP_OK) {
|
|
1608
|
+
const char* msg = S.errmsg(db);
|
|
1609
|
+
THROW(env, msg ? msg : "Failed to clone database");
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
napi_value ext;
|
|
1613
|
+
napi_create_external(env, cloned, NULL, NULL, &ext);
|
|
1614
|
+
return ext;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1589
1617
|
/* dbClose(external): void */
|
|
1590
1618
|
static napi_value fn_db_close(napi_env env, napi_callback_info info) {
|
|
1591
1619
|
size_t argc = 1;
|
|
@@ -3023,10 +3051,10 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
|
|
|
3023
3051
|
for (int k = 0; k < tbuf_cnt; k++) { free(tbufs[k]); tbufs[k] = NULL; }
|
|
3024
3052
|
|
|
3025
3053
|
if (rc != STOOLAP_OK) {
|
|
3054
|
+
ERR_SAVE(S.tx_errmsg(tx), "Transaction batch exec error");
|
|
3026
3055
|
free(vals); free(tbufs);
|
|
3027
|
-
const char* msg = S.tx_errmsg(tx);
|
|
3028
3056
|
if (stmt) S.stmt_finalize(stmt);
|
|
3029
|
-
THROW(env,
|
|
3057
|
+
THROW(env, _eb);
|
|
3030
3058
|
}
|
|
3031
3059
|
|
|
3032
3060
|
total_affected += affected;
|
|
@@ -3202,7 +3230,7 @@ static napi_value wrap_db_exec_batch_buf(napi_env env, napi_callback_info info)
|
|
|
3202
3230
|
int use_stmt = (S.tx_stmt_exec != NULL);
|
|
3203
3231
|
if (use_stmt) {
|
|
3204
3232
|
rc = S.prepare(db, sql, &stmt);
|
|
3205
|
-
if (rc != STOOLAP_OK) { S.
|
|
3233
|
+
if (rc != STOOLAP_OK) { ERR_SAVE(S.errmsg(db), "Prepare error"); S.tx_rollback(tx); THROW(env, _eb); }
|
|
3206
3234
|
}
|
|
3207
3235
|
int64_t total = 0;
|
|
3208
3236
|
for (uint32_t i = 0; i < row_count; i++) {
|
|
@@ -3217,12 +3245,12 @@ static napi_value wrap_db_exec_batch_buf(napi_env env, napi_callback_info info)
|
|
|
3217
3245
|
} else {
|
|
3218
3246
|
rc = S.tx_exec_p(tx, sql, vals, decoded, &affected);
|
|
3219
3247
|
}
|
|
3220
|
-
if (rc != STOOLAP_OK) {
|
|
3248
|
+
if (rc != STOOLAP_OK) { ERR_SAVE(S.tx_errmsg(tx), "Batch exec error"); if (stmt) S.stmt_finalize(stmt); S.tx_rollback(tx); THROW(env, _eb); }
|
|
3221
3249
|
total += affected;
|
|
3222
3250
|
}
|
|
3223
3251
|
if (stmt) S.stmt_finalize(stmt);
|
|
3224
3252
|
rc = S.tx_commit(tx);
|
|
3225
|
-
if (rc != STOOLAP_OK) {
|
|
3253
|
+
if (rc != STOOLAP_OK) { THROW(env, "Commit error"); }
|
|
3226
3254
|
return make_changes(env, total);
|
|
3227
3255
|
}
|
|
3228
3256
|
|
|
@@ -3278,6 +3306,7 @@ static napi_value Init(napi_env env, napi_value exports) {
|
|
|
3278
3306
|
EXPORT_FN(env, exports, "loadLibrary", fn_load_library);
|
|
3279
3307
|
EXPORT_FN(env, exports, "dbOpen", fn_db_open);
|
|
3280
3308
|
EXPORT_FN(env, exports, "dbOpenAsync", fn_db_open_async);
|
|
3309
|
+
EXPORT_FN(env, exports, "dbClone", fn_db_clone);
|
|
3281
3310
|
EXPORT_FN(env, exports, "dbClose", fn_db_close);
|
|
3282
3311
|
EXPORT_FN(env, exports, "dbCloseAsync", fn_db_close_async);
|
|
3283
3312
|
EXPORT_FN(env, exports, "dbExec", fn_db_exec);
|