Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface Options {
encryptionKey?: string
remoteEncryptionKey?: string
}
export declare function connect(path: string, opts?: Options | undefined | null): Promise<Database>
/** Result of a database sync operation. */
export interface SyncResult {
/** The number of frames synced. */
Expand Down
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,10 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { Database, databasePrepareSync, databaseSyncSync, databaseExecSync, Statement, statementGetSync, statementRunSync, statementIterateSync, RowsIterator, iteratorNextSync, Record } = nativeBinding
const { Database, connect, databasePrepareSync, databaseSyncSync, databaseExecSync, Statement, statementGetSync, statementRunSync, statementIterateSync, RowsIterator, iteratorNextSync, Record } = nativeBinding

module.exports.Database = Database
module.exports.connect = connect
module.exports.databasePrepareSync = databasePrepareSync
module.exports.databaseSyncSync = databaseSyncSync
module.exports.databaseExecSync = databaseExecSync
Expand Down
6 changes: 3 additions & 3 deletions integration-tests/tests/async.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,9 +414,9 @@ const connect = async (path_opt, options = {}) => {
const path = path_opt ?? "hello.db";
const provider = process.env.PROVIDER;
const database = process.env.LIBSQL_DATABASE ?? path;
const x = await import("libsql/promise");
const db = new x.default(database, options);
return [db, x.SqliteError];
const libsql = await import("libsql/promise");
const db = await libsql.connect(database, options);
return [db, libsql.SqliteError];
};

/// Generate a unique database filename
Expand Down
6 changes: 3 additions & 3 deletions integration-tests/tests/concurrency.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ test("Concurrent operations with timeout should handle busy database", async (t)

const connect = async (path_opt, options = {}) => {
const path = path_opt ?? `test-${crypto.randomBytes(8).toString('hex')}.db`;
const x = await import("libsql/promise");
const db = new x.default(process.env.LIBSQL_DATABASE ?? path, options);
return [db, x.SqliteError, path];
const libsql = await import("libsql/promise");
const db = await libsql.connect(process.env.LIBSQL_DATABASE ?? path, options);
return [db, libsql.SqliteError, path];
};

const cleanup = async (context) => {
Expand Down
27 changes: 20 additions & 7 deletions promise.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

const { Database: NativeDb } = require("./index.js");
const { Database: NativeDb, connect: nativeConnect } = require("./index.js");
const SqliteError = require("./sqlite-error.js");
const Authorization = require("./auth");

Expand Down Expand Up @@ -28,6 +28,17 @@ function convertError(err) {
return err;
}

/**
* Creates a new database connection.
*
* @param {string} path - Path to the database file.
* @param {object} opts - Options.
*/
const connect = async (path, opts) => {
const db = await nativeConnect(path, opts);
return new Database(db);
};

/**
* Database represents a connection that can prepare and execute SQL statements.
*/
Expand All @@ -38,10 +49,9 @@ class Database {
* @constructor
* @param {string} path - Path to the database file.
*/
constructor(path, opts) {
this.db = new NativeDb(path, opts);
constructor(db) {
this.db = db;
this.memory = this.db.memory
const db = this.db;
Object.defineProperties(this, {
inTransaction: {
get() {
Expand Down Expand Up @@ -346,6 +356,9 @@ class Statement {
}
}

module.exports = Database;
module.exports.SqliteError = SqliteError;
module.exports.Authorization = Authorization;
module.exports = {
Database,
connect,
SqliteError,
Authorization,
};
185 changes: 94 additions & 91 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,111 +233,114 @@ impl Drop for Database {
}

#[napi]
impl Database {
/// Creates a new database instance.
///
/// # Arguments
///
/// * `path` - The path to the database file.
/// * `opts` - The database options.
#[napi(constructor)]
pub fn new(path: String, opts: Option<Options>) -> Result<Self> {
ensure_logger();
let rt = runtime()?;
let remote = is_remote_path(&path);
let db = if remote {
let auth_token = opts
pub async fn connect(path: String, opts: Option<Options>) -> Result<Database> {
let remote = is_remote_path(&path);
let db = if remote {
let auth_token = opts
.as_ref()
.and_then(|o| o.authToken.as_ref())
.cloned()
.unwrap_or_default();
let mut builder = libsql::Builder::new_remote(path.clone(), auth_token);
if let Some(encryption_key) = opts
.as_ref()
.and_then(|o| o.remoteEncryptionKey.as_ref())
.cloned()
{
let encryption_context = libsql::EncryptionContext {
key: libsql::EncryptionKey::Base64Encoded(encryption_key),
};
builder = builder.remote_encryption(encryption_context);
}
builder.build().await.map_err(Error::from)?
} else if let Some(options) = &opts {
if let Some(sync_url) = &options.syncUrl {
let auth_token = options.authToken.as_ref().cloned().unwrap_or_default();

let encryption_cipher: String = opts
.as_ref()
.and_then(|o| o.authToken.as_ref())
.and_then(|o| o.encryptionCipher.as_ref())
.cloned()
.unwrap_or_default();
let mut builder = libsql::Builder::new_remote(path.clone(), auth_token);
if let Some(encryption_key) = opts
.unwrap_or("aes256cbc".to_string());
let cipher = libsql::Cipher::from_str(&encryption_cipher).map_err(|_| {
throw_sqlite_error(
"Invalid encryption cipher".to_string(),
"SQLITE_INVALID_ENCRYPTION_CIPHER".to_string(),
0,
)
})?;
let encryption_key = opts
.as_ref()
.and_then(|o| o.remoteEncryptionKey.as_ref())
.and_then(|o| o.encryptionKey.as_ref())
.cloned()
{
.unwrap_or("".to_string());

let mut builder =
libsql::Builder::new_remote_replica(path.clone(), sync_url.clone(), auth_token);

let read_your_writes = options.readYourWrites.unwrap_or(true);
builder = builder.read_your_writes(read_your_writes);

if encryption_key.len() > 0 {
let encryption_config =
libsql::EncryptionConfig::new(cipher, encryption_key.into());
builder = builder.encryption_config(encryption_config);
}

if let Some(remote_encryption_key) = &options.remoteEncryptionKey {
let encryption_context = libsql::EncryptionContext {
key: libsql::EncryptionKey::Base64Encoded(encryption_key),
key: libsql::EncryptionKey::Base64Encoded(remote_encryption_key.to_string()),
};
builder = builder.remote_encryption(encryption_context);
}
rt.block_on(builder.build()).map_err(Error::from)?
} else if let Some(options) = &opts {
if let Some(sync_url) = &options.syncUrl {
let auth_token = options.authToken.as_ref().cloned().unwrap_or_default();

let encryption_cipher: String = opts
.as_ref()
.and_then(|o| o.encryptionCipher.as_ref())
.cloned()
.unwrap_or("aes256cbc".to_string());
let cipher = libsql::Cipher::from_str(&encryption_cipher).map_err(|_| {
throw_sqlite_error(
"Invalid encryption cipher".to_string(),
"SQLITE_INVALID_ENCRYPTION_CIPHER".to_string(),
0,
)
})?;
let encryption_key = opts
.as_ref()
.and_then(|o| o.encryptionKey.as_ref())
.cloned()
.unwrap_or("".to_string());

let mut builder =
libsql::Builder::new_remote_replica(path.clone(), sync_url.clone(), auth_token);

let read_your_writes = options.readYourWrites.unwrap_or(true);
builder = builder.read_your_writes(read_your_writes);

if encryption_key.len() > 0 {
let encryption_config =
libsql::EncryptionConfig::new(cipher, encryption_key.into());
builder = builder.encryption_config(encryption_config);
}

if let Some(remote_encryption_key) = &options.remoteEncryptionKey {
let encryption_context = libsql::EncryptionContext {
key: libsql::EncryptionKey::Base64Encoded(
remote_encryption_key.to_string(),
),
};
builder = builder.remote_encryption(encryption_context);
}

if let Some(period) = options.syncPeriod {
if period > 0.0 {
builder = builder.sync_interval(std::time::Duration::from_secs_f64(period));
}
if let Some(period) = options.syncPeriod {
if period > 0.0 {
builder = builder.sync_interval(std::time::Duration::from_secs_f64(period));
}

rt.block_on(builder.build()).map_err(Error::from)?
} else {
let builder = libsql::Builder::new_local(&path);
rt.block_on(builder.build()).map_err(Error::from)?
}

builder.build().await.map_err(Error::from)?
} else {
let builder = libsql::Builder::new_local(&path);
rt.block_on(builder.build()).map_err(Error::from)?
};
let conn = db.connect().map_err(Error::from)?;
let default_safe_integers = AtomicBool::new(false);
let memory = path == ":memory:";
let timeout = match opts {
Some(ref opts) => opts.timeout.unwrap_or(0.0),
None => 0.0,
};
if timeout > 0.0 {
conn.busy_timeout(Duration::from_millis(timeout as u64))
.map_err(Error::from)?
builder.build().await.map_err(Error::from)?
}
Ok(Database {
db,
conn: Some(Arc::new(conn)),
default_safe_integers,
memory,
})
} else {
let builder = libsql::Builder::new_local(&path);
builder.build().await.map_err(Error::from)?
};
let conn = db.connect().map_err(Error::from)?;
let default_safe_integers = AtomicBool::new(false);
let memory = path == ":memory:";
let timeout = match opts {
Some(ref opts) => opts.timeout.unwrap_or(0.0),
None => 0.0,
};
if timeout > 0.0 {
conn.busy_timeout(Duration::from_millis(timeout as u64))
.map_err(Error::from)?
}
Ok(Database {
db,
conn: Some(Arc::new(conn)),
default_safe_integers,
memory,
})
}

#[napi]
impl Database {
/// Creates a new database instance.
///
/// # Arguments
///
/// * `path` - The path to the database file.
/// * `opts` - The database options.
#[napi(constructor)]
pub fn new(path: String, opts: Option<Options>) -> Result<Self> {
ensure_logger();
let rt = runtime()?;
rt.block_on(connect(path, opts))
}

/// Returns whether the database is in memory-only mode.
Expand Down
Loading