@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 +1 -0
- package/lib/methods/deserialize.js +35 -0
- package/package.json +1 -1
- package/src/objects/database.cpp +95 -0
- package/src/objects/database.hpp +1 -0
- package/src/objects/session.cpp +19 -0
- package/src/objects/session.hpp +1 -0
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.
|
|
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>",
|
package/src/objects/database.cpp
CHANGED
|
@@ -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);
|
package/src/objects/database.hpp
CHANGED
package/src/objects/session.cpp
CHANGED
|
@@ -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);
|