@stoolap/node 0.3.4 → 0.3.5

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
@@ -1,11 +1,13 @@
1
1
  # @stoolap/node
2
2
 
3
- High-performance Node.js driver for [Stoolap](https://github.com/stoolap/stoolap), a modern embedded SQL database with MVCC, time-travel queries, and full ACID compliance.
3
+ High-performance JavaScript driver for [Stoolap](https://github.com/stoolap/stoolap), a modern embedded SQL database with MVCC, time-travel queries, and full ACID compliance.
4
4
 
5
- Built with a native N-API C addon for minimal overhead. Provides both async and sync APIs.
5
+ Built with a native N-API C addon for minimal overhead. Works with Node.js, Bun, and Deno. Provides both async and sync APIs.
6
6
 
7
7
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
8
8
  [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
9
+ [![Bun](https://img.shields.io/badge/bun-compatible-f472b6.svg)](https://bun.sh)
10
+ [![Deno](https://img.shields.io/badge/deno-compatible-000000.svg)](https://deno.com)
9
11
 
10
12
  ## Installation
11
13
 
package/lib/database.js CHANGED
@@ -312,7 +312,7 @@ class Database {
312
312
 
313
313
  async begin() {
314
314
  const txPtr = await this._beginAsync();
315
- const tx = new Transaction(txPtr, this.#stoolap, this);
315
+ const tx = new Transaction(txPtr, this.#stoolap, this, this.#ptr);
316
316
  this._trackChild(tx);
317
317
  return tx;
318
318
  }
@@ -320,7 +320,7 @@ class Database {
320
320
  beginSync() {
321
321
  this.#checkOpen();
322
322
  const txPtr = this.#stoolap.txBegin(this.#ptr);
323
- const tx = new Transaction(txPtr, this.#stoolap, this);
323
+ const tx = new Transaction(txPtr, this.#stoolap, this, this.#ptr);
324
324
  this._trackChild(tx);
325
325
  return tx;
326
326
  }
@@ -6,15 +6,17 @@ const { parseRows, parseRaw } = require("./parse");
6
6
  class Transaction {
7
7
  #ptr = null;
8
8
  #stoolap = null;
9
+ #dbPtr = null;
9
10
  #pendingAsync = 0;
10
11
  #tail = Promise.resolve();
11
12
  #closingPromise = null;
12
13
  #owner = null;
13
14
 
14
- constructor(ptr, stoolap, owner) {
15
+ constructor(ptr, stoolap, owner, dbPtr) {
15
16
  this.#ptr = ptr;
16
17
  this.#stoolap = stoolap;
17
18
  this.#owner = owner || null;
19
+ this.#dbPtr = dbPtr || null;
18
20
  }
19
21
 
20
22
  _invalidate() {
@@ -177,7 +179,7 @@ class Transaction {
177
179
  executeBatchSync(sql, paramsArray) {
178
180
  this.#checkSyncReady();
179
181
  if (paramsArray.length > 0 && Array.isArray(paramsArray[0])) {
180
- return this.#stoolap.txExecBatch(this.#ptr, sql, paramsArray);
182
+ return this.#stoolap.txExecBatch(this.#dbPtr, this.#ptr, sql, paramsArray);
181
183
  }
182
184
  let totalChanges = 0;
183
185
  for (const params of paramsArray) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoolap/node",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "High-performance Node.js driver for Stoolap embedded SQL database",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -13,15 +13,16 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "install": "node-gyp rebuild",
16
+ "build": "node-gyp rebuild",
16
17
  "test": "node --test __test__/index.spec.mjs",
17
18
  "bench": "node benchmark.mjs"
18
19
  },
19
20
  "optionalDependencies": {
20
- "@stoolap/lib-darwin-arm64": "0.3.4",
21
- "@stoolap/lib-darwin-x64": "0.3.4",
22
- "@stoolap/lib-linux-arm64-gnu": "0.3.4",
23
- "@stoolap/lib-linux-x64-gnu": "0.3.4",
24
- "@stoolap/lib-win32-x64-msvc": "0.3.4"
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"
25
26
  },
26
27
  "devDependencies": {
27
28
  "better-sqlite3": "^12.6.2",
package/src/stoolap.c CHANGED
@@ -85,6 +85,8 @@ typedef int32_t (*fn_tx_exec)(StoolapTx*, const char*, int64_t*);
85
85
  typedef int32_t (*fn_tx_exec_p)(StoolapTx*, const char*, const StoolapValue*, int32_t, int64_t*);
86
86
  typedef int32_t (*fn_tx_query)(StoolapTx*, const char*, StoolapRows**);
87
87
  typedef int32_t (*fn_tx_query_p)(StoolapTx*, const char*, const StoolapValue*, int32_t, StoolapRows**);
88
+ typedef int32_t (*fn_tx_stmt_exec)(StoolapTx*, StoolapStmt*, const StoolapValue*, int32_t, int64_t*);
89
+ typedef int32_t (*fn_tx_stmt_query)(StoolapTx*, StoolapStmt*, const StoolapValue*, int32_t, StoolapRows**);
88
90
  typedef int32_t (*fn_tx_commit)(StoolapTx*);
89
91
  typedef int32_t (*fn_tx_rollback)(StoolapTx*);
90
92
  typedef const char* (*fn_tx_errmsg)(const StoolapTx*);
@@ -127,6 +129,8 @@ static struct {
127
129
  fn_tx_exec_p tx_exec_p;
128
130
  fn_tx_query tx_query;
129
131
  fn_tx_query_p tx_query_p;
132
+ fn_tx_stmt_exec tx_stmt_exec;
133
+ fn_tx_stmt_query tx_stmt_query;
130
134
  fn_tx_commit tx_commit;
131
135
  fn_tx_rollback tx_rollback;
132
136
  fn_tx_errmsg tx_errmsg;
@@ -1523,6 +1527,10 @@ static napi_value fn_load_library(napi_env env, napi_callback_info info) {
1523
1527
  LOAD(tx_exec_p, "stoolap_tx_exec_params");
1524
1528
  LOAD(tx_query, "stoolap_tx_query");
1525
1529
  LOAD(tx_query_p, "stoolap_tx_query_params");
1530
+ /* Optional symbols — not in all engine versions */
1531
+ S.tx_stmt_exec = (fn_tx_stmt_exec) DLSYM(lib_handle, "stoolap_tx_stmt_exec");
1532
+ S.tx_stmt_query = (fn_tx_stmt_query)DLSYM(lib_handle, "stoolap_tx_stmt_query");
1533
+
1526
1534
  LOAD(tx_commit, "stoolap_tx_commit");
1527
1535
  LOAD(tx_rollback, "stoolap_tx_rollback");
1528
1536
  LOAD(tx_errmsg, "stoolap_tx_errmsg");
@@ -2948,22 +2956,37 @@ static napi_value wrap_tx_rollback_async(napi_env env, napi_callback_info info)
2948
2956
  * Batch exec in C — copies SQL once, pre-allocates param buffers,
2949
2957
  * loops param sets in C. Eliminates N × JS↔C crossings + N×malloc/free. */
2950
2958
  static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
2951
- size_t argc = 3;
2952
- napi_value argv[3];
2959
+ size_t argc = 4;
2960
+ napi_value argv[4];
2953
2961
  napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
2954
2962
 
2963
+ StoolapDB* db;
2964
+ napi_get_value_external(env, argv[0], (void**)&db);
2965
+
2955
2966
  StoolapTx* tx;
2956
- napi_get_value_external(env, argv[0], (void**)&tx);
2967
+ napi_get_value_external(env, argv[1], (void**)&tx);
2957
2968
 
2958
- GET_SQL_STRING(env, argv[1], sql);
2969
+ GET_SQL_STRING(env, argv[2], sql);
2959
2970
 
2960
2971
  uint32_t batch_len;
2961
- napi_get_array_length(env, argv[2], &batch_len);
2972
+ napi_get_array_length(env, argv[3], &batch_len);
2962
2973
  if (batch_len == 0) return make_changes(env, 0);
2963
2974
 
2975
+ /* Prepare statement once if tx_stmt_exec is available */
2976
+ StoolapStmt* stmt = NULL;
2977
+ int use_stmt = (S.tx_stmt_exec != NULL);
2978
+ int32_t rc;
2979
+ if (use_stmt) {
2980
+ rc = S.prepare(db, sql, &stmt);
2981
+ if (rc != STOOLAP_OK) {
2982
+ const char* msg = S.errmsg(db);
2983
+ THROW(env, msg ? msg : "Prepare error");
2984
+ }
2985
+ }
2986
+
2964
2987
  /* Get param count from first element — pre-allocate ONCE */
2965
2988
  napi_value first_arr;
2966
- napi_get_element(env, argv[2], 0, &first_arr);
2989
+ napi_get_element(env, argv[3], 0, &first_arr);
2967
2990
  uint32_t param_count;
2968
2991
  napi_get_array_length(env, first_arr, &param_count);
2969
2992
 
@@ -2974,7 +2997,7 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
2974
2997
 
2975
2998
  for (uint32_t i = 0; i < batch_len; i++) {
2976
2999
  napi_value params_arr;
2977
- napi_get_element(env, argv[2], i, &params_arr);
3000
+ napi_get_element(env, argv[3], i, &params_arr);
2978
3001
 
2979
3002
  /* Convert params into pre-allocated buffers */
2980
3003
  int tbuf_cnt = 0;
@@ -2982,15 +3005,19 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
2982
3005
  napi_value elem;
2983
3006
  napi_get_element(env, params_arr, j, &elem);
2984
3007
  if (js_to_value(env, elem, &vals[j], tbufs, &tbuf_cnt) != 0) {
2985
- /* Cleanup on error */
2986
3008
  for (int k = 0; k < tbuf_cnt; k++) free(tbufs[k]);
2987
3009
  free(vals); free(tbufs);
3010
+ if (stmt) S.stmt_finalize(stmt);
2988
3011
  THROW(env, "Failed to convert parameters");
2989
3012
  }
2990
3013
  }
2991
3014
 
2992
3015
  int64_t affected = 0;
2993
- int32_t rc = S.tx_exec_p(tx, sql, vals, (int32_t)param_count, &affected);
3016
+ if (use_stmt) {
3017
+ rc = S.tx_stmt_exec(tx, stmt, vals, (int32_t)param_count, &affected);
3018
+ } else {
3019
+ rc = S.tx_exec_p(tx, sql, vals, (int32_t)param_count, &affected);
3020
+ }
2994
3021
 
2995
3022
  /* Free only text buffers, keep vals/tbufs arrays for reuse */
2996
3023
  for (int k = 0; k < tbuf_cnt; k++) { free(tbufs[k]); tbufs[k] = NULL; }
@@ -2998,6 +3025,7 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
2998
3025
  if (rc != STOOLAP_OK) {
2999
3026
  free(vals); free(tbufs);
3000
3027
  const char* msg = S.tx_errmsg(tx);
3028
+ if (stmt) S.stmt_finalize(stmt);
3001
3029
  THROW(env, msg ? msg : "Transaction batch exec error");
3002
3030
  }
3003
3031
 
@@ -3006,6 +3034,7 @@ static napi_value wrap_tx_exec_batch(napi_env env, napi_callback_info info) {
3006
3034
 
3007
3035
  free(vals);
3008
3036
  free(tbufs);
3037
+ if (stmt) S.stmt_finalize(stmt);
3009
3038
  return make_changes(env, total_affected);
3010
3039
  }
3011
3040
 
@@ -3170,21 +3199,28 @@ static napi_value wrap_db_exec_batch_buf(napi_env env, napi_callback_info info)
3170
3199
  int32_t rc = S.begin(db, &tx);
3171
3200
  if (rc != STOOLAP_OK) { const char* msg = S.errmsg(db); THROW(env, msg ? msg : "Begin error"); }
3172
3201
  StoolapStmt* stmt = NULL;
3173
- rc = S.prepare(db, sql, &stmt);
3174
- if (rc != STOOLAP_OK) { S.tx_rollback(tx); const char* msg = S.errmsg(db); THROW(env, msg ? msg : "Prepare error"); }
3202
+ int use_stmt = (S.tx_stmt_exec != NULL);
3203
+ if (use_stmt) {
3204
+ 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"); }
3206
+ }
3175
3207
  int64_t total = 0;
3176
3208
  for (uint32_t i = 0; i < row_count; i++) {
3177
3209
  StoolapValue vals[MAX_STACK_PARAMS];
3178
3210
  size_t consumed = 0;
3179
3211
  int decoded = decode_params_buf(ptr, (size_t)(end - ptr), vals, ppr, &consumed);
3180
- if (decoded != ppr) { S.stmt_finalize(stmt); S.tx_rollback(tx); THROW(env, "Batch param decode error"); }
3212
+ if (decoded != ppr) { if (stmt) S.stmt_finalize(stmt); S.tx_rollback(tx); THROW(env, "Batch param decode error"); }
3181
3213
  ptr += consumed;
3182
3214
  int64_t affected = 0;
3183
- rc = S.stmt_exec(stmt, vals, decoded, &affected);
3184
- if (rc != STOOLAP_OK) { const char* msg = S.stmt_errmsg(stmt); S.stmt_finalize(stmt); S.tx_rollback(tx); THROW(env, msg ? msg : "Batch exec error"); }
3215
+ if (use_stmt) {
3216
+ rc = S.tx_stmt_exec(tx, stmt, vals, decoded, &affected);
3217
+ } else {
3218
+ rc = S.tx_exec_p(tx, sql, vals, decoded, &affected);
3219
+ }
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"); }
3185
3221
  total += affected;
3186
3222
  }
3187
- S.stmt_finalize(stmt);
3223
+ if (stmt) S.stmt_finalize(stmt);
3188
3224
  rc = S.tx_commit(tx);
3189
3225
  if (rc != STOOLAP_OK) { const char* msg = S.errmsg(db); THROW(env, msg ? msg : "Commit error"); }
3190
3226
  return make_changes(env, total);