@stoolap/node 0.3.5 → 0.3.7

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 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 |
@@ -189,6 +190,24 @@ Controls the durability vs. performance trade-off:
189
190
  | `compression` | | Set both `wal_compression` and `snapshot_compression` |
190
191
  | `compression_threshold` | `64` | Minimum bytes before compressing an entry |
191
192
 
193
+ #### Cloning
194
+
195
+ `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.
196
+
197
+ ```js
198
+ const db = await Database.open('./mydata');
199
+ const db2 = db.clone();
200
+
201
+ // Both see the same data
202
+ await db.execute('INSERT INTO users VALUES ($1, $2)', [1, 'Alice']);
203
+ const row = db2.queryOneSync('SELECT * FROM users WHERE id = $1', [1]);
204
+ // { id: 1, name: 'Alice' }
205
+
206
+ // Each clone must be closed independently
207
+ await db2.close();
208
+ await db.close();
209
+ ```
210
+
192
211
  #### Raw Query Format
193
212
 
194
213
  `queryRaw` / `queryRawSync` return `{ columns: string[], rows: any[][] }` instead of an array of objects. Faster when you don't need named keys.
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoolap/node",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
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.3.5",
22
- "@stoolap/lib-darwin-x64": "0.3.5",
23
- "@stoolap/lib-linux-arm64-gnu": "0.3.5",
24
- "@stoolap/lib-linux-x64-gnu": "0.3.5",
25
- "@stoolap/lib-win32-x64-msvc": "0.3.5"
21
+ "@stoolap/lib-darwin-arm64": "0.3.7",
22
+ "@stoolap/lib-darwin-x64": "0.3.7",
23
+ "@stoolap/lib-linux-arm64-gnu": "0.3.7",
24
+ "@stoolap/lib-linux-x64-gnu": "0.3.7",
25
+ "@stoolap/lib-win32-x64-msvc": "0.3.7"
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>
@@ -68,6 +69,7 @@ typedef struct {
68
69
  typedef const char* (*fn_version)(void);
69
70
  typedef int32_t (*fn_open)(const char*, StoolapDB**);
70
71
  typedef int32_t (*fn_open_mem)(StoolapDB**);
72
+ typedef int32_t (*fn_clone)(StoolapDB*, StoolapDB**);
71
73
  typedef int32_t (*fn_close)(StoolapDB*);
72
74
  typedef const char* (*fn_errmsg)(const StoolapDB*);
73
75
  typedef int32_t (*fn_exec)(StoolapDB*, const char*, int64_t*);
@@ -112,6 +114,7 @@ static struct {
112
114
  fn_version version;
113
115
  fn_open open;
114
116
  fn_open_mem open_mem;
117
+ fn_clone clone;
115
118
  fn_close close;
116
119
  fn_errmsg errmsg;
117
120
  fn_exec exec;
@@ -156,6 +159,8 @@ static LibHandle lib_handle = NULL;
156
159
  /* ---- Helpers ---- */
157
160
 
158
161
  #define THROW(env, msg) do { napi_throw_error(env, NULL, msg); return NULL; } while(0)
162
+ #define ERR_SAVE(msg, fallback) \
163
+ char _eb[512]; snprintf(_eb, sizeof(_eb), "%s", (msg) ? (msg) : (fallback))
159
164
  #define CHECK(env, status) if ((status) != napi_ok) return NULL
160
165
 
161
166
  static napi_value make_changes(napi_env env, int64_t n) {
@@ -1510,6 +1515,7 @@ static napi_value fn_load_library(napi_env env, napi_callback_info info) {
1510
1515
  LOAD(version, "stoolap_version");
1511
1516
  LOAD(open, "stoolap_open");
1512
1517
  LOAD(open_mem, "stoolap_open_in_memory");
1518
+ LOAD(clone, "stoolap_clone");
1513
1519
  LOAD(close, "stoolap_close");
1514
1520
  LOAD(errmsg, "stoolap_errmsg");
1515
1521
  LOAD(exec, "stoolap_exec");
@@ -1586,6 +1592,27 @@ static napi_value fn_db_open(napi_env env, napi_callback_info info) {
1586
1592
  return ext;
1587
1593
  }
1588
1594
 
1595
+ /* dbClone(external): external */
1596
+ static napi_value fn_db_clone(napi_env env, napi_callback_info info) {
1597
+ size_t argc = 1;
1598
+ napi_value argv[1];
1599
+ napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
1600
+
1601
+ StoolapDB* db;
1602
+ napi_get_value_external(env, argv[0], (void**)&db);
1603
+
1604
+ StoolapDB* cloned = NULL;
1605
+ int32_t rc = S.clone(db, &cloned);
1606
+ if (rc != STOOLAP_OK) {
1607
+ const char* msg = S.errmsg(db);
1608
+ THROW(env, msg ? msg : "Failed to clone database");
1609
+ }
1610
+
1611
+ napi_value ext;
1612
+ napi_create_external(env, cloned, NULL, NULL, &ext);
1613
+ return ext;
1614
+ }
1615
+
1589
1616
  /* dbClose(external): void */
1590
1617
  static napi_value fn_db_close(napi_env env, napi_callback_info info) {
1591
1618
  size_t argc = 1;
@@ -3023,10 +3050,10 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
3023
3050
  for (int k = 0; k < tbuf_cnt; k++) { free(tbufs[k]); tbufs[k] = NULL; }
3024
3051
 
3025
3052
  if (rc != STOOLAP_OK) {
3053
+ ERR_SAVE(S.tx_errmsg(tx), "Transaction batch exec error");
3026
3054
  free(vals); free(tbufs);
3027
- const char* msg = S.tx_errmsg(tx);
3028
3055
  if (stmt) S.stmt_finalize(stmt);
3029
- THROW(env, msg ? msg : "Transaction batch exec error");
3056
+ THROW(env, _eb);
3030
3057
  }
3031
3058
 
3032
3059
  total_affected += affected;
@@ -3202,7 +3229,7 @@ static napi_value wrap_db_exec_batch_buf(napi_env env, napi_callback_info info)
3202
3229
  int use_stmt = (S.tx_stmt_exec != NULL);
3203
3230
  if (use_stmt) {
3204
3231
  rc = S.prepare(db, sql, &stmt);
3205
- if (rc != STOOLAP_OK) { S.tx_rollback(tx); const char* msg = S.errmsg(db); THROW(env, msg ? msg : "Prepare error"); }
3232
+ if (rc != STOOLAP_OK) { ERR_SAVE(S.errmsg(db), "Prepare error"); S.tx_rollback(tx); THROW(env, _eb); }
3206
3233
  }
3207
3234
  int64_t total = 0;
3208
3235
  for (uint32_t i = 0; i < row_count; i++) {
@@ -3217,12 +3244,12 @@ static napi_value wrap_db_exec_batch_buf(napi_env env, napi_callback_info info)
3217
3244
  } else {
3218
3245
  rc = S.tx_exec_p(tx, sql, vals, decoded, &affected);
3219
3246
  }
3220
- if (rc != STOOLAP_OK) { const char* msg = S.tx_errmsg(tx); if (stmt) S.stmt_finalize(stmt); S.tx_rollback(tx); THROW(env, msg ? msg : "Batch exec error"); }
3247
+ 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
3248
  total += affected;
3222
3249
  }
3223
3250
  if (stmt) S.stmt_finalize(stmt);
3224
3251
  rc = S.tx_commit(tx);
3225
- if (rc != STOOLAP_OK) { const char* msg = S.errmsg(db); THROW(env, msg ? msg : "Commit error"); }
3252
+ if (rc != STOOLAP_OK) { THROW(env, "Commit error"); }
3226
3253
  return make_changes(env, total);
3227
3254
  }
3228
3255
 
@@ -3278,6 +3305,7 @@ static napi_value Init(napi_env env, napi_value exports) {
3278
3305
  EXPORT_FN(env, exports, "loadLibrary", fn_load_library);
3279
3306
  EXPORT_FN(env, exports, "dbOpen", fn_db_open);
3280
3307
  EXPORT_FN(env, exports, "dbOpenAsync", fn_db_open_async);
3308
+ EXPORT_FN(env, exports, "dbClone", fn_db_clone);
3281
3309
  EXPORT_FN(env, exports, "dbClose", fn_db_close);
3282
3310
  EXPORT_FN(env, exports, "dbCloseAsync", fn_db_close_async);
3283
3311
  EXPORT_FN(env, exports, "dbExec", fn_db_exec);