@barakxyz/better-sqlite3 12.6.2 → 12.7.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/lib/database.js CHANGED
@@ -77,6 +77,7 @@ Database.prototype.transaction = require('./methods/transaction');
77
77
  Database.prototype.pragma = require('./methods/pragma');
78
78
  Database.prototype.backup = require('./methods/backup');
79
79
  Database.prototype.serialize = require('./methods/serialize');
80
+ Database.prototype.deserialize = require('./methods/deserialize');
80
81
  Database.prototype.function = require('./methods/function');
81
82
  Database.prototype.aggregate = require('./methods/aggregate');
82
83
  Database.prototype.table = require('./methods/table');
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+ const { cppdb } = require('../util');
3
+
4
+ /**
5
+ * Deserialize a buffer into the database.
6
+ *
7
+ * Uses the safe pattern:
8
+ * 1. Create temp in-memory database
9
+ * 2. Deserialize buffer into temp
10
+ * 3. Backup from temp to current database
11
+ * 4. Close temp database
12
+ *
13
+ * This preserves file-based persistence and matches wa-sqlite's import pattern.
14
+ * REF: packages/@livestore/sqlite-wasm/src/make-sqlite-db.ts import implementation
15
+ *
16
+ * @param {Buffer} buffer - The serialized database buffer
17
+ * @param {Object} [options] - Options object
18
+ * @param {string} [options.attached='main'] - The attached database name
19
+ * @returns {this} The database instance for chaining
20
+ */
21
+ module.exports = function deserialize(buffer, options) {
22
+ if (options == null) options = {};
23
+
24
+ // Validate arguments
25
+ if (!Buffer.isBuffer(buffer)) throw new TypeError('Expected first argument to be a buffer');
26
+ if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
27
+
28
+ // Interpret and validate options
29
+ const attachedName = 'attached' in options ? options.attached : 'main';
30
+ if (typeof attachedName !== 'string') throw new TypeError('Expected the "attached" option to be a string');
31
+ if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
32
+
33
+ this[cppdb].deserialize(buffer, attachedName);
34
+ return this; // Return the JS Database instance for chaining
35
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barakxyz/better-sqlite3",
3
- "version": "12.6.2",
3
+ "version": "12.7.0",
4
4
  "description": "The fastest and simplest library for SQLite in Node.js. Fork with session extension enabled.",
5
5
  "homepage": "https://github.com/BarakXYZ/better-sqlite3",
6
6
  "author": "Joshua Wise <joshuathomaswise@gmail.com>",
@@ -132,6 +132,7 @@ INIT(Database::Init) {
132
132
  SetPrototypeMethod(isolate, data, t, "exec", JS_exec);
133
133
  SetPrototypeMethod(isolate, data, t, "backup", JS_backup);
134
134
  SetPrototypeMethod(isolate, data, t, "serialize", JS_serialize);
135
+ SetPrototypeMethod(isolate, data, t, "deserialize", JS_deserialize);
135
136
  SetPrototypeMethod(isolate, data, t, "function", JS_function);
136
137
  SetPrototypeMethod(isolate, data, t, "aggregate", JS_aggregate);
137
138
  SetPrototypeMethod(isolate, data, t, "table", JS_table);
@@ -295,6 +296,100 @@ NODE_METHOD(Database::JS_serialize) {
295
296
  );
296
297
  }
297
298
 
299
+ NODE_METHOD(Database::JS_deserialize) {
300
+ // Deserialize a buffer into the current database using the safe pattern:
301
+ // 1. Create temp in-memory database
302
+ // 2. Deserialize buffer into temp
303
+ // 3. Backup from temp to current database
304
+ // 4. Close temp database
305
+ //
306
+ // This preserves file-based persistence and matches wa-sqlite's import pattern.
307
+ // REF: packages/@livestore/sqlite-wasm/src/make-sqlite-db.ts import implementation
308
+ // REF: packages/@livestore/wa-sqlite/src/sqlite-api.js backup function
309
+
310
+ Database* db = Unwrap<Database>(info.This());
311
+ REQUIRE_DATABASE_OPEN(db);
312
+ REQUIRE_DATABASE_NOT_BUSY(db);
313
+ REQUIRE_DATABASE_NO_ITERATORS(db);
314
+
315
+ UseAddon;
316
+ UseIsolate;
317
+
318
+ // First argument: buffer containing serialized database
319
+ v8::Local<v8::Value> bufferArg = info[0];
320
+ if (!node::Buffer::HasInstance(bufferArg)) {
321
+ ThrowTypeError("Expected first argument to be a buffer");
322
+ return;
323
+ }
324
+ v8::Local<v8::Object> buffer = bufferArg.As<v8::Object>();
325
+
326
+ // Second argument: attached database name (default "main")
327
+ v8::Local<v8::String> attachedName;
328
+ if (info.Length() > 1 && !info[1]->IsUndefined()) {
329
+ if (!info[1]->IsString()) {
330
+ ThrowTypeError("Expected second argument to be a string");
331
+ return;
332
+ }
333
+ attachedName = info[1].As<v8::String>();
334
+ } else {
335
+ attachedName = StringFromUtf8(isolate, "main", -1);
336
+ }
337
+ v8::String::Utf8Value attached_name(isolate, attachedName);
338
+
339
+ // Step 1: Create a temporary in-memory database
340
+ sqlite3* temp_handle;
341
+ int status = sqlite3_open_v2(":memory:", &temp_handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
342
+ if (status != SQLITE_OK) {
343
+ ThrowSqliteError(addon, "Failed to create temporary database", status);
344
+ return;
345
+ }
346
+
347
+ // Step 2: Deserialize the buffer into the temporary database
348
+ size_t length = node::Buffer::Length(buffer);
349
+ unsigned char* data = (unsigned char*)sqlite3_malloc64(length);
350
+ unsigned int flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE;
351
+
352
+ if (length) {
353
+ if (!data) {
354
+ sqlite3_close(temp_handle);
355
+ ThrowError("Out of memory");
356
+ return;
357
+ }
358
+ memcpy(data, node::Buffer::Data(buffer), length);
359
+ }
360
+
361
+ status = sqlite3_deserialize(temp_handle, "main", data, length, length, flags);
362
+ if (status != SQLITE_OK) {
363
+ sqlite3_close(temp_handle);
364
+ ThrowSqliteError(addon, status == SQLITE_ERROR ? "unable to deserialize database" : sqlite3_errstr(status), status);
365
+ return;
366
+ }
367
+
368
+ // Step 3: Backup from temp to current database
369
+ // sqlite3_backup_init(dest, destName, source, sourceName)
370
+ // REF: wa-sqlite sqlite-api.js - backup(dest, destName, source, sourceName)
371
+ sqlite3_backup* backup_handle = sqlite3_backup_init(db->db_handle, *attached_name, temp_handle, "main");
372
+ if (backup_handle == NULL) {
373
+ sqlite3_close(temp_handle);
374
+ db->ThrowDatabaseError();
375
+ return;
376
+ }
377
+
378
+ // Copy all pages in one step (-1 = all pages)
379
+ status = sqlite3_backup_step(backup_handle, -1);
380
+ sqlite3_backup_finish(backup_handle);
381
+
382
+ // Step 4: Close temporary database
383
+ sqlite3_close(temp_handle);
384
+
385
+ if (status != SQLITE_DONE) {
386
+ ThrowSqliteError(addon, "Failed to restore database from buffer", status);
387
+ return;
388
+ }
389
+
390
+ info.GetReturnValue().Set(info.This());
391
+ }
392
+
298
393
  NODE_METHOD(Database::JS_function) {
299
394
  Database* db = Unwrap<Database>(info.This());
300
395
  REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> fn);
@@ -81,6 +81,7 @@ private:
81
81
  static NODE_METHOD(JS_exec);
82
82
  static NODE_METHOD(JS_backup);
83
83
  static NODE_METHOD(JS_serialize);
84
+ static NODE_METHOD(JS_deserialize);
84
85
  static NODE_METHOD(JS_function);
85
86
  static NODE_METHOD(JS_aggregate);
86
87
  static NODE_METHOD(JS_table);
@@ -30,6 +30,7 @@ INIT(Session::Init) {
30
30
  v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Session");
31
31
  SetPrototypeMethod(isolate, data, t, "attach", JS_attach);
32
32
  SetPrototypeMethod(isolate, data, t, "changeset", JS_changeset);
33
+ SetPrototypeMethod(isolate, data, t, "enable", JS_enable);
33
34
  SetPrototypeMethod(isolate, data, t, "close", JS_close);
34
35
  return t->GetFunction(OnlyContext).ToLocalChecked();
35
36
  }
@@ -92,6 +93,24 @@ NODE_METHOD(Session::JS_attach) {
92
93
  }
93
94
  }
94
95
 
96
+ NODE_METHOD(Session::JS_enable) {
97
+ Session* session = Unwrap<Session>(info.This());
98
+ if (!session->alive) return ThrowTypeError("The session has been closed");
99
+ REQUIRE_DATABASE_OPEN(session->db->GetState());
100
+
101
+ UseIsolate;
102
+
103
+ // Require a boolean argument (matches wa-sqlite API pattern)
104
+ if (info.Length() < 1 || !info[0]->IsBoolean()) {
105
+ return ThrowTypeError("Expected first argument to be a boolean");
106
+ }
107
+
108
+ int enable_flag = info[0]->BooleanValue(isolate) ? 1 : 0;
109
+ sqlite3session_enable(session->session_handle, enable_flag);
110
+
111
+ // Return void (matches wa-sqlite API)
112
+ }
113
+
95
114
  // Custom destructor for sqlite3_free
96
115
  static void FreeSqliteMemory(char* data, void* hint) {
97
116
  sqlite3_free(data);
@@ -24,6 +24,7 @@ private:
24
24
  static NODE_METHOD(JS_new);
25
25
  static NODE_METHOD(JS_attach);
26
26
  static NODE_METHOD(JS_changeset);
27
+ static NODE_METHOD(JS_enable);
27
28
  static NODE_METHOD(JS_close);
28
29
 
29
30
  Database* const db;