import { dxSqliteDB as SqliteDB_base_class } from './libvbar-m-dxsqlitedb.so';
|
import dxMap from './dxMap.js';
|
import log from './dxLogger.js';
|
import std from './dxStd.js';
|
/**
|
* SQLite Database Module (dxSqliteDB)
|
*
|
* @description
|
* Provides a multi-instance, thread-safe interface to SQLite databases. It manages database
|
* connections by their file path, allowing different parts of an application, including
|
* different threads, to safely access and manipulate the same database file.
|
*
|
* @feature
|
* - **Multi-Instance**: Manage multiple database files simultaneously based on their paths.
|
* - **Thread-Safe**: Uses a C++ layer with a serialized transaction model, allowing safe
|
* database access from multiple JavaScript threads without manual locking.
|
* - **Cross-Thread Coordination**: Leverages `dxMap` as a global registry to ensure that
|
* once a database is initialized in any thread, other threads can get a connection to it
|
* simply by calling `init()` with the same path.
|
*
|
* @threadingModel
|
* Each JS thread (main or worker) has its own memory heap, so JS objects cannot be
|
* directly shared. This module handles this by:
|
* 1. Using a shared `dxMap` (`__dxSqliteDB_instances__`) to store the `path` of each
|
* initialized database, which acts as a global registry.
|
* 2. Each thread maintains its own local cache of `dxSqliteDB` instances, keyed by path.
|
* 3. A call to `init(path)` will first check the local cache, then the global registry,
|
* ensuring that each thread gets a valid local instance pointing to the shared
|
* database file.
|
*
|
* @example
|
* // --- Main Thread ---
|
* import dxSqliteDB from 'dxSqliteDB';
|
* const dbPath = '/data/app.db';
|
*
|
* try {
|
* const db = dxSqliteDB.init(dbPath);
|
* db.exec(`
|
* CREATE TABLE IF NOT EXISTS events (
|
* id INTEGER PRIMARY KEY AUTOINCREMENT,
|
* type TEXT NOT NULL,
|
* timestamp INTEGER
|
* )
|
* `);
|
* db.exec(`INSERT INTO events (type, timestamp) VALUES ('start', ${Date.now()})`);
|
* } catch (e) {
|
* log.error('Main thread DB error:', e.message);
|
* }
|
*
|
* // --- Worker Thread ---
|
* import dxSqliteDB from 'dxSqliteDB';
|
* const dbPath = '/data/app.db';
|
*
|
* try {
|
* // Calling init() again with the same path is safe. It will return a cached
|
* // local instance or create a new one connected to the same shared database file.
|
* const db = dxSqliteDB.init(dbPath);
|
* if (db) {
|
* const events = db.select('SELECT * FROM events ORDER BY id DESC LIMIT 1');
|
* // events would be: [{ id: 1, type: 'start', timestamp: 1678886400000 }]
|
* }
|
* } catch (e) {
|
* log.error('Worker thread DB error:', e.message);
|
* }
|
*/
|
|
// A thread-local cache for database instances, keyed by path. This map is NOT shared between threads.
|
const localInstances = new Map();
|
|
// A shared, cross-thread registry using dxMap to store registered paths.
|
const globalRegistry = dxMap.get('__dxSqliteDB_instances__');
|
|
|
const dxSqliteDB = {
|
/**
|
* Initializes and returns a database instance for a given path.
|
*
|
* @description
|
* This function is the single entry point for getting a database connection. It's safe
|
* to call this function multiple times with the same path from any thread.
|
*
|
* - If an instance for this path already exists in the current thread's cache, it's returned immediately.
|
* - If not, it checks a global registry. If another thread has already initialized this path,
|
* a new local instance is created for the current thread, connected to the same database file.
|
* - If it's the first time this path is initialized by any thread, it will be registered globally.
|
*
|
* @param {string} path - The full, absolute path to the database file. This path is used as the unique identifier.
|
* @returns {SqliteDB_base_class} The database instance object. This object has the following methods:
|
* - **.exec(sql: string): void** - Executes a non-query SQL statement (e.g., CREATE, INSERT, UPDATE, DELETE). Throws an error on failure.
|
* - **.select(sql: string): object[]** - Executes a query SQL statement (SELECT) and returns an array of result objects.
|
* - **.begin(): void** - Begins a new transaction.
|
* - **.commit(): void** - Commits the current transaction.
|
* - **.rollback(): void** - Rolls back the current transaction.
|
* @throws {Error} Throws an error if the 'path' is missing or empty, or if the underlying database connection fails.
|
*/
|
init: function (path) {
|
if (!path) {
|
throw new Error("dxSqliteDB.init requires a 'path'");
|
}
|
|
// 1. Check thread-local cache first.
|
if (localInstances.has(path)) {
|
return localInstances.get(path);
|
}
|
|
// 2. Check if the path is in the global registry (initialized by any thread).
|
const isGloballyRegistered = globalRegistry.has(path);
|
|
try {
|
// Create a new local instance regardless.
|
const dbInstance = new SqliteDB_base_class();
|
std.ensurePathExists(path);
|
dbInstance.init(path);
|
|
// Store in the thread-local cache.
|
localInstances.set(path, dbInstance);
|
|
// If it wasn't globally registered before, register it now.
|
if (!isGloballyRegistered) {
|
globalRegistry.put(path, "1"); // Value indicates presence.
|
}
|
|
return dbInstance;
|
} catch (e) {
|
log.error(`Failed to initialize dxSqliteDB instance for path '${path}':`, e.message);
|
// In case of failure, only clean up the global registry if this was the first attempt.
|
if (!isGloballyRegistered) {
|
globalRegistry.del(path);
|
}
|
throw e;
|
}
|
},
|
|
/**
|
* Closes the database connection for the current thread.
|
*
|
* @description
|
* This function should be called when a database is no longer needed in the current thread.
|
* It closes the database connection and cleans up resources for the local instance.
|
* NOTE: This is a thread-local operation and does not affect the global registry.
|
*
|
* @param {string} path - The path of the database instance to deinitialize.
|
*/
|
deinit: function (path) {
|
if (!path) return;
|
|
// Clean up the local instance if it exists in this thread.
|
if (localInstances.has(path)) {
|
const dbInstance = localInstances.get(path);
|
try {
|
dbInstance.deinit();
|
} catch (e) {
|
log.error(`Error during deinit of local dxSqliteDB instance for path '${path}':`, e.message);
|
} finally {
|
localInstances.delete(path);
|
}
|
}
|
}
|
};
|
|
export default dxSqliteDB;
|