Skip to content

Commit 6d182d9

Browse files
authored
Add async connect() to promise API (#198)
2 parents f1730b6 + ac2c110 commit 6d182d9

6 files changed

Lines changed: 123 additions & 105 deletions

File tree

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface Options {
1414
encryptionKey?: string
1515
remoteEncryptionKey?: string
1616
}
17+
export declare function connect(path: string, opts?: Options | undefined | null): Promise<Database>
1718
/** Result of a database sync operation. */
1819
export interface SyncResult {
1920
/** The number of frames synced. */

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,10 @@ if (!nativeBinding) {
310310
throw new Error(`Failed to load native binding`)
311311
}
312312

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

315315
module.exports.Database = Database
316+
module.exports.connect = connect
316317
module.exports.databasePrepareSync = databasePrepareSync
317318
module.exports.databaseSyncSync = databaseSyncSync
318319
module.exports.databaseExecSync = databaseExecSync

integration-tests/tests/async.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -414,9 +414,9 @@ const connect = async (path_opt, options = {}) => {
414414
const path = path_opt ?? "hello.db";
415415
const provider = process.env.PROVIDER;
416416
const database = process.env.LIBSQL_DATABASE ?? path;
417-
const x = await import("libsql/promise");
418-
const db = new x.default(database, options);
419-
return [db, x.SqliteError];
417+
const libsql = await import("libsql/promise");
418+
const db = await libsql.connect(database, options);
419+
return [db, libsql.SqliteError];
420420
};
421421

422422
/// Generate a unique database filename

integration-tests/tests/concurrency.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ test("Concurrent operations with timeout should handle busy database", async (t)
148148

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

156156
const cleanup = async (context) => {

promise.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22

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

@@ -28,6 +28,17 @@ function convertError(err) {
2828
return err;
2929
}
3030

31+
/**
32+
* Creates a new database connection.
33+
*
34+
* @param {string} path - Path to the database file.
35+
* @param {object} opts - Options.
36+
*/
37+
const connect = async (path, opts) => {
38+
const db = await nativeConnect(path, opts);
39+
return new Database(db);
40+
};
41+
3142
/**
3243
* Database represents a connection that can prepare and execute SQL statements.
3344
*/
@@ -38,10 +49,9 @@ class Database {
3849
* @constructor
3950
* @param {string} path - Path to the database file.
4051
*/
41-
constructor(path, opts) {
42-
this.db = new NativeDb(path, opts);
52+
constructor(db) {
53+
this.db = db;
4354
this.memory = this.db.memory
44-
const db = this.db;
4555
Object.defineProperties(this, {
4656
inTransaction: {
4757
get() {
@@ -346,6 +356,9 @@ class Statement {
346356
}
347357
}
348358

349-
module.exports = Database;
350-
module.exports.SqliteError = SqliteError;
351-
module.exports.Authorization = Authorization;
359+
module.exports = {
360+
Database,
361+
connect,
362+
SqliteError,
363+
Authorization,
364+
};

src/lib.rs

Lines changed: 94 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -233,111 +233,114 @@ impl Drop for Database {
233233
}
234234

235235
#[napi]
236-
impl Database {
237-
/// Creates a new database instance.
238-
///
239-
/// # Arguments
240-
///
241-
/// * `path` - The path to the database file.
242-
/// * `opts` - The database options.
243-
#[napi(constructor)]
244-
pub fn new(path: String, opts: Option<Options>) -> Result<Self> {
245-
ensure_logger();
246-
let rt = runtime()?;
247-
let remote = is_remote_path(&path);
248-
let db = if remote {
249-
let auth_token = opts
236+
pub async fn connect(path: String, opts: Option<Options>) -> Result<Database> {
237+
let remote = is_remote_path(&path);
238+
let db = if remote {
239+
let auth_token = opts
240+
.as_ref()
241+
.and_then(|o| o.authToken.as_ref())
242+
.cloned()
243+
.unwrap_or_default();
244+
let mut builder = libsql::Builder::new_remote(path.clone(), auth_token);
245+
if let Some(encryption_key) = opts
246+
.as_ref()
247+
.and_then(|o| o.remoteEncryptionKey.as_ref())
248+
.cloned()
249+
{
250+
let encryption_context = libsql::EncryptionContext {
251+
key: libsql::EncryptionKey::Base64Encoded(encryption_key),
252+
};
253+
builder = builder.remote_encryption(encryption_context);
254+
}
255+
builder.build().await.map_err(Error::from)?
256+
} else if let Some(options) = &opts {
257+
if let Some(sync_url) = &options.syncUrl {
258+
let auth_token = options.authToken.as_ref().cloned().unwrap_or_default();
259+
260+
let encryption_cipher: String = opts
250261
.as_ref()
251-
.and_then(|o| o.authToken.as_ref())
262+
.and_then(|o| o.encryptionCipher.as_ref())
252263
.cloned()
253-
.unwrap_or_default();
254-
let mut builder = libsql::Builder::new_remote(path.clone(), auth_token);
255-
if let Some(encryption_key) = opts
264+
.unwrap_or("aes256cbc".to_string());
265+
let cipher = libsql::Cipher::from_str(&encryption_cipher).map_err(|_| {
266+
throw_sqlite_error(
267+
"Invalid encryption cipher".to_string(),
268+
"SQLITE_INVALID_ENCRYPTION_CIPHER".to_string(),
269+
0,
270+
)
271+
})?;
272+
let encryption_key = opts
256273
.as_ref()
257-
.and_then(|o| o.remoteEncryptionKey.as_ref())
274+
.and_then(|o| o.encryptionKey.as_ref())
258275
.cloned()
259-
{
276+
.unwrap_or("".to_string());
277+
278+
let mut builder =
279+
libsql::Builder::new_remote_replica(path.clone(), sync_url.clone(), auth_token);
280+
281+
let read_your_writes = options.readYourWrites.unwrap_or(true);
282+
builder = builder.read_your_writes(read_your_writes);
283+
284+
if encryption_key.len() > 0 {
285+
let encryption_config =
286+
libsql::EncryptionConfig::new(cipher, encryption_key.into());
287+
builder = builder.encryption_config(encryption_config);
288+
}
289+
290+
if let Some(remote_encryption_key) = &options.remoteEncryptionKey {
260291
let encryption_context = libsql::EncryptionContext {
261-
key: libsql::EncryptionKey::Base64Encoded(encryption_key),
292+
key: libsql::EncryptionKey::Base64Encoded(remote_encryption_key.to_string()),
262293
};
263294
builder = builder.remote_encryption(encryption_context);
264295
}
265-
rt.block_on(builder.build()).map_err(Error::from)?
266-
} else if let Some(options) = &opts {
267-
if let Some(sync_url) = &options.syncUrl {
268-
let auth_token = options.authToken.as_ref().cloned().unwrap_or_default();
269-
270-
let encryption_cipher: String = opts
271-
.as_ref()
272-
.and_then(|o| o.encryptionCipher.as_ref())
273-
.cloned()
274-
.unwrap_or("aes256cbc".to_string());
275-
let cipher = libsql::Cipher::from_str(&encryption_cipher).map_err(|_| {
276-
throw_sqlite_error(
277-
"Invalid encryption cipher".to_string(),
278-
"SQLITE_INVALID_ENCRYPTION_CIPHER".to_string(),
279-
0,
280-
)
281-
})?;
282-
let encryption_key = opts
283-
.as_ref()
284-
.and_then(|o| o.encryptionKey.as_ref())
285-
.cloned()
286-
.unwrap_or("".to_string());
287-
288-
let mut builder =
289-
libsql::Builder::new_remote_replica(path.clone(), sync_url.clone(), auth_token);
290-
291-
let read_your_writes = options.readYourWrites.unwrap_or(true);
292-
builder = builder.read_your_writes(read_your_writes);
293-
294-
if encryption_key.len() > 0 {
295-
let encryption_config =
296-
libsql::EncryptionConfig::new(cipher, encryption_key.into());
297-
builder = builder.encryption_config(encryption_config);
298-
}
299-
300-
if let Some(remote_encryption_key) = &options.remoteEncryptionKey {
301-
let encryption_context = libsql::EncryptionContext {
302-
key: libsql::EncryptionKey::Base64Encoded(
303-
remote_encryption_key.to_string(),
304-
),
305-
};
306-
builder = builder.remote_encryption(encryption_context);
307-
}
308296

309-
if let Some(period) = options.syncPeriod {
310-
if period > 0.0 {
311-
builder = builder.sync_interval(std::time::Duration::from_secs_f64(period));
312-
}
297+
if let Some(period) = options.syncPeriod {
298+
if period > 0.0 {
299+
builder = builder.sync_interval(std::time::Duration::from_secs_f64(period));
313300
}
314-
315-
rt.block_on(builder.build()).map_err(Error::from)?
316-
} else {
317-
let builder = libsql::Builder::new_local(&path);
318-
rt.block_on(builder.build()).map_err(Error::from)?
319301
}
302+
303+
builder.build().await.map_err(Error::from)?
320304
} else {
321305
let builder = libsql::Builder::new_local(&path);
322-
rt.block_on(builder.build()).map_err(Error::from)?
323-
};
324-
let conn = db.connect().map_err(Error::from)?;
325-
let default_safe_integers = AtomicBool::new(false);
326-
let memory = path == ":memory:";
327-
let timeout = match opts {
328-
Some(ref opts) => opts.timeout.unwrap_or(0.0),
329-
None => 0.0,
330-
};
331-
if timeout > 0.0 {
332-
conn.busy_timeout(Duration::from_millis(timeout as u64))
333-
.map_err(Error::from)?
306+
builder.build().await.map_err(Error::from)?
334307
}
335-
Ok(Database {
336-
db,
337-
conn: Some(Arc::new(conn)),
338-
default_safe_integers,
339-
memory,
340-
})
308+
} else {
309+
let builder = libsql::Builder::new_local(&path);
310+
builder.build().await.map_err(Error::from)?
311+
};
312+
let conn = db.connect().map_err(Error::from)?;
313+
let default_safe_integers = AtomicBool::new(false);
314+
let memory = path == ":memory:";
315+
let timeout = match opts {
316+
Some(ref opts) => opts.timeout.unwrap_or(0.0),
317+
None => 0.0,
318+
};
319+
if timeout > 0.0 {
320+
conn.busy_timeout(Duration::from_millis(timeout as u64))
321+
.map_err(Error::from)?
322+
}
323+
Ok(Database {
324+
db,
325+
conn: Some(Arc::new(conn)),
326+
default_safe_integers,
327+
memory,
328+
})
329+
}
330+
331+
#[napi]
332+
impl Database {
333+
/// Creates a new database instance.
334+
///
335+
/// # Arguments
336+
///
337+
/// * `path` - The path to the database file.
338+
/// * `opts` - The database options.
339+
#[napi(constructor)]
340+
pub fn new(path: String, opts: Option<Options>) -> Result<Self> {
341+
ensure_logger();
342+
let rt = runtime()?;
343+
rt.block_on(connect(path, opts))
341344
}
342345

343346
/// Returns whether the database is in memory-only mode.

0 commit comments

Comments
 (0)